@builder.io/sdk-svelte 3.0.5 → 3.0.7

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 (103) hide show
  1. package/lib/browser/blocks/personalization-container/component-info.d.ts +2 -0
  2. package/lib/browser/blocks/personalization-container/component-info.js +36 -0
  3. package/lib/browser/blocks/personalization-container/helpers/inlined-fns.d.ts +8 -0
  4. package/lib/browser/blocks/personalization-container/helpers/inlined-fns.js +87 -0
  5. package/lib/browser/blocks/personalization-container/helpers.d.ts +27 -0
  6. package/lib/browser/blocks/personalization-container/helpers.js +58 -0
  7. package/lib/browser/blocks/personalization-container/index.d.ts +1 -0
  8. package/lib/browser/blocks/personalization-container/index.js +1 -0
  9. package/lib/browser/blocks/personalization-container/personalization-container.svelte +118 -0
  10. package/lib/browser/blocks/personalization-container/personalization-container.svelte.d.ts +21 -0
  11. package/lib/browser/blocks/personalization-container/personalization-container.types.d.ts +14 -0
  12. package/lib/browser/blocks/personalization-container/personalization-container.types.js +1 -0
  13. package/lib/browser/components/block/components/block-styles.svelte +10 -0
  14. package/lib/browser/components/blocks/blocks-wrapper.svelte +6 -1
  15. package/lib/browser/constants/builder-registered-components.js +6 -1
  16. package/lib/browser/constants/device-sizes.d.ts +3 -2
  17. package/lib/browser/constants/device-sizes.js +20 -3
  18. package/lib/browser/constants/sdk-version.d.ts +1 -1
  19. package/lib/browser/constants/sdk-version.js +1 -1
  20. package/lib/browser/helpers/canTrack.js +6 -1
  21. package/lib/browser/helpers/user-attributes.d.ts +17 -0
  22. package/lib/browser/helpers/user-attributes.js +46 -0
  23. package/lib/browser/index-helpers/blocks-exports.d.ts +1 -0
  24. package/lib/browser/index-helpers/blocks-exports.js +1 -0
  25. package/lib/browser/scripts/init-editing.js +3 -1
  26. package/lib/browser/types/builder-block.d.ts +0 -1
  27. package/lib/browser/types/builder-content.d.ts +1 -0
  28. package/lib/browser/types/input.d.ts +1 -0
  29. package/lib/edge/blocks/personalization-container/component-info.d.ts +2 -0
  30. package/lib/edge/blocks/personalization-container/component-info.js +36 -0
  31. package/lib/edge/blocks/personalization-container/helpers/inlined-fns.d.ts +8 -0
  32. package/lib/edge/blocks/personalization-container/helpers/inlined-fns.js +87 -0
  33. package/lib/edge/blocks/personalization-container/helpers.d.ts +27 -0
  34. package/lib/edge/blocks/personalization-container/helpers.js +58 -0
  35. package/lib/edge/blocks/personalization-container/index.d.ts +1 -0
  36. package/lib/edge/blocks/personalization-container/index.js +1 -0
  37. package/lib/edge/blocks/personalization-container/personalization-container.svelte +118 -0
  38. package/lib/edge/blocks/personalization-container/personalization-container.svelte.d.ts +21 -0
  39. package/lib/edge/blocks/personalization-container/personalization-container.types.d.ts +14 -0
  40. package/lib/edge/blocks/personalization-container/personalization-container.types.js +1 -0
  41. package/lib/edge/components/block/components/block-styles.svelte +10 -0
  42. package/lib/edge/components/blocks/blocks-wrapper.svelte +6 -1
  43. package/lib/edge/constants/builder-registered-components.js +6 -1
  44. package/lib/edge/constants/device-sizes.d.ts +3 -2
  45. package/lib/edge/constants/device-sizes.js +20 -3
  46. package/lib/edge/constants/sdk-version.d.ts +1 -1
  47. package/lib/edge/constants/sdk-version.js +1 -1
  48. package/lib/edge/helpers/canTrack.js +6 -1
  49. package/lib/edge/helpers/user-attributes.d.ts +17 -0
  50. package/lib/edge/helpers/user-attributes.js +46 -0
  51. package/lib/edge/index-helpers/blocks-exports.d.ts +1 -0
  52. package/lib/edge/index-helpers/blocks-exports.js +1 -0
  53. package/lib/edge/scripts/init-editing.js +3 -1
  54. package/lib/edge/types/builder-block.d.ts +0 -1
  55. package/lib/edge/types/builder-content.d.ts +1 -0
  56. package/lib/edge/types/input.d.ts +1 -0
  57. package/lib/node/blocks/personalization-container/component-info.d.ts +2 -0
  58. package/lib/node/blocks/personalization-container/component-info.js +36 -0
  59. package/lib/node/blocks/personalization-container/helpers/inlined-fns.d.ts +8 -0
  60. package/lib/node/blocks/personalization-container/helpers/inlined-fns.js +87 -0
  61. package/lib/node/blocks/personalization-container/helpers.d.ts +27 -0
  62. package/lib/node/blocks/personalization-container/helpers.js +58 -0
  63. package/lib/node/blocks/personalization-container/index.d.ts +1 -0
  64. package/lib/node/blocks/personalization-container/index.js +1 -0
  65. package/lib/node/blocks/personalization-container/personalization-container.svelte +118 -0
  66. package/lib/node/blocks/personalization-container/personalization-container.svelte.d.ts +21 -0
  67. package/lib/node/blocks/personalization-container/personalization-container.types.d.ts +14 -0
  68. package/lib/node/blocks/personalization-container/personalization-container.types.js +1 -0
  69. package/lib/node/components/block/components/block-styles.svelte +10 -0
  70. package/lib/node/components/blocks/blocks-wrapper.svelte +6 -1
  71. package/lib/node/constants/builder-registered-components.js +6 -1
  72. package/lib/node/constants/device-sizes.d.ts +3 -2
  73. package/lib/node/constants/device-sizes.js +20 -3
  74. package/lib/node/constants/sdk-version.d.ts +1 -1
  75. package/lib/node/constants/sdk-version.js +1 -1
  76. package/lib/node/helpers/canTrack.js +6 -1
  77. package/lib/node/helpers/user-attributes.d.ts +17 -0
  78. package/lib/node/helpers/user-attributes.js +46 -0
  79. package/lib/node/index-helpers/blocks-exports.d.ts +1 -0
  80. package/lib/node/index-helpers/blocks-exports.js +1 -0
  81. package/lib/node/scripts/init-editing.js +3 -1
  82. package/lib/node/types/builder-block.d.ts +0 -1
  83. package/lib/node/types/builder-content.d.ts +1 -0
  84. package/lib/node/types/input.d.ts +1 -0
  85. package/package.json +1 -1
  86. package/src/blocks/personalization-container/component-info.ts +37 -0
  87. package/src/blocks/personalization-container/helpers/inlined-fns.ts +88 -0
  88. package/src/blocks/personalization-container/helpers.ts +84 -0
  89. package/src/blocks/personalization-container/index.ts +1 -0
  90. package/src/blocks/personalization-container/personalization-container.svelte +155 -0
  91. package/src/blocks/personalization-container/personalization-container.types.ts +14 -0
  92. package/src/components/block/components/block-styles.svelte +16 -0
  93. package/src/components/blocks/blocks-wrapper.svelte +6 -1
  94. package/src/constants/builder-registered-components.ts +6 -1
  95. package/src/constants/device-sizes.ts +26 -7
  96. package/src/constants/sdk-version.ts +1 -1
  97. package/src/helpers/canTrack.ts +6 -1
  98. package/src/helpers/user-attributes.ts +49 -0
  99. package/src/index-helpers/blocks-exports.ts +2 -1
  100. package/src/scripts/init-editing.ts +3 -1
  101. package/src/types/builder-block.ts +0 -1
  102. package/src/types/builder-content.ts +1 -0
  103. package/src/types/input.ts +1 -0
@@ -0,0 +1,46 @@
1
+ import { isBrowser } from '../functions/is-browser';
2
+ import { getCookieSync, setCookie } from './cookie';
3
+ export const USER_ATTRIBUTES_COOKIE_NAME = 'builder.userAttributes';
4
+ export function createUserAttributesService() {
5
+ let canTrack = true;
6
+ const subscribers = new Set();
7
+ return {
8
+ setUserAttributes(newAttrs) {
9
+ if (!isBrowser()) {
10
+ return;
11
+ }
12
+ const userAttributes = {
13
+ ...this.getUserAttributes(),
14
+ ...newAttrs
15
+ };
16
+ setCookie({
17
+ name: USER_ATTRIBUTES_COOKIE_NAME,
18
+ value: JSON.stringify(userAttributes),
19
+ canTrack
20
+ });
21
+ subscribers.forEach(callback => callback(userAttributes));
22
+ },
23
+ getUserAttributes() {
24
+ if (!isBrowser()) {
25
+ return {};
26
+ }
27
+ return JSON.parse(getCookieSync({
28
+ name: USER_ATTRIBUTES_COOKIE_NAME,
29
+ canTrack
30
+ }) || '{}');
31
+ },
32
+ subscribeOnUserAttributesChange(callback) {
33
+ subscribers.add(callback);
34
+ return () => {
35
+ subscribers.delete(callback);
36
+ };
37
+ },
38
+ setCanTrack(value) {
39
+ canTrack = value;
40
+ }
41
+ };
42
+ }
43
+ export const userAttributesService = createUserAttributesService();
44
+ export const setClientUserAttributes = (attributes) => {
45
+ userAttributesService.setUserAttributes(attributes);
46
+ };
@@ -12,3 +12,4 @@ export { default as Content } from '../components/content-variants/index';
12
12
  * Builder Context
13
13
  */
14
14
  export { BuilderContext } from '../context/index';
15
+ export { setClientUserAttributes } from '../helpers/user-attributes';
@@ -12,3 +12,4 @@ export { default as Content } from '../components/content-variants/index';
12
12
  * Builder Context
13
13
  */
14
14
  export { BuilderContext } from '../context/index';
15
+ export { setClientUserAttributes } from '../helpers/user-attributes';
@@ -42,7 +42,9 @@ 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
+ supportsXSmallBreakpoint: TARGET === 'reactNative' ? false : true,
47
+ blockLevelPersonalization: true
46
48
  }
47
49
  }, '*');
48
50
  window.parent?.postMessage({
@@ -37,7 +37,6 @@ export interface BuilderBlock {
37
37
  large?: Partial<CSSStyleDeclaration>;
38
38
  medium?: Partial<CSSStyleDeclaration>;
39
39
  small?: Partial<CSSStyleDeclaration>;
40
- /** @deprecated */
41
40
  xsmall?: Partial<CSSStyleDeclaration>;
42
41
  };
43
42
  component?: {
@@ -2,6 +2,7 @@ import type { BuilderBlock } from './builder-block';
2
2
  import type { Input } from './input';
3
3
  import type { Nullable } from './typescript';
4
4
  export interface Breakpoints {
5
+ xsmall?: number;
5
6
  small: number;
6
7
  medium: number;
7
8
  }
@@ -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.5",
4
+ "version": "3.0.7",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "lib",
@@ -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
@@ -39,9 +39,13 @@
39
39
  const sizesWithUpdatedBreakpoints = getSizesForBreakpoints(
40
40
  content?.meta?.breakpoints || {}
41
41
  );
42
+ const contentHasXSmallBreakpoint = Boolean(
43
+ content?.meta?.breakpoints?.xsmall
44
+ );
42
45
  const largeStyles = styles?.large;
43
46
  const mediumStyles = styles?.medium;
44
47
  const smallStyles = styles?.small;
48
+ const xsmallStyles = styles?.xsmall;
45
49
  const className = processedBlock.id;
46
50
  if (!className) {
47
51
  return "";
@@ -72,6 +76,17 @@
72
76
  ),
73
77
  })
74
78
  : "";
79
+ const xsmallStylesClass =
80
+ xsmallStyles && contentHasXSmallBreakpoint
81
+ ? createCssClass({
82
+ className,
83
+ styles: xsmallStyles,
84
+ mediaQuery: getMaxWidthQueryForSize(
85
+ "xsmall",
86
+ sizesWithUpdatedBreakpoints
87
+ ),
88
+ })
89
+ : "";
75
90
  const hoverAnimation =
76
91
  processedBlock.animations &&
77
92
  processedBlock.animations.find((item) => item.trigger === "hover");
@@ -96,6 +111,7 @@
96
111
  largeStylesClass,
97
112
  mediumStylesClass,
98
113
  smallStylesClass,
114
+ xsmallStylesClass,
99
115
  hoverStylesClass,
100
116
  ].join(" ");
101
117
  };
@@ -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,13 +1,18 @@
1
1
  import { fastClone } from '../functions/fast-clone';
2
- export type SizeName = 'large' | 'medium' | 'small';
2
+ export type SizeName = 'large' | 'medium' | 'small' | 'xsmall';
3
3
  interface Size {
4
4
  min: number;
5
5
  default: number;
6
6
  max: number;
7
7
  }
8
8
  const SIZES: Record<SizeName, Size> = {
9
+ xsmall: {
10
+ min: 0,
11
+ default: 160,
12
+ max: 320
13
+ },
9
14
  small: {
10
- min: 320,
15
+ min: 321,
11
16
  default: 321,
12
17
  max: 640
13
18
  },
@@ -24,19 +29,33 @@ const SIZES: Record<SizeName, Size> = {
24
29
  };
25
30
  export const getMaxWidthQueryForSize = (size: SizeName, sizeValues = SIZES) => `@media (max-width: ${sizeValues[size].max}px)`;
26
31
  interface Breakpoints {
32
+ xsmall?: number;
27
33
  small?: number;
28
34
  medium?: number;
29
35
  }
30
- export const getSizesForBreakpoints = ({
31
- small,
32
- medium
33
- }: Breakpoints) => {
36
+ export const getSizesForBreakpoints = (breakpoints: Breakpoints) => {
34
37
  const newSizes = fastClone(SIZES); // Note: this helps to get a deep clone of fields like small, medium etc
35
38
 
39
+ if (!breakpoints) {
40
+ return newSizes;
41
+ }
42
+ const {
43
+ xsmall,
44
+ small,
45
+ medium
46
+ } = breakpoints;
47
+ if (xsmall) {
48
+ const xsmallMin = Math.floor(xsmall / 2);
49
+ newSizes.xsmall = {
50
+ max: xsmall,
51
+ min: xsmallMin,
52
+ default: xsmallMin + 1
53
+ };
54
+ }
36
55
  if (!small || !medium) {
37
56
  return newSizes;
38
57
  }
39
- const smallMin = Math.floor(small / 2);
58
+ const smallMin = xsmall ? newSizes.xsmall.max + 1 : Math.floor(small / 2);
40
59
  newSizes.small = {
41
60
  max: small,
42
61
  min: smallMin,
@@ -1 +1 @@
1
- export const SDK_VERSION = "3.0.5"
1
+ export const SDK_VERSION = "3.0.7"
@@ -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
+ }