@builder.io/sdk-svelte 5.2.15 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/README.md +4 -0
  2. package/lib/browser/components/block/block.helpers.d.ts +1 -3
  3. package/lib/browser/components/block/block.helpers.js +31 -4
  4. package/lib/browser/components/block/block.svelte +14 -1
  5. package/lib/browser/components/content/components/styles.helpers.d.ts +1 -1
  6. package/lib/browser/components/content/components/styles.helpers.js +1 -1
  7. package/lib/browser/components/content/content.helpers.d.ts +5 -1
  8. package/lib/browser/components/content/content.helpers.js +39 -2
  9. package/lib/browser/components/content/content.svelte +4 -17
  10. package/lib/browser/constants/builder-registered-components.d.ts +1 -0
  11. package/lib/browser/constants/builder-registered-components.js +40 -33
  12. package/lib/browser/constants/device-sizes.js +14 -2
  13. package/lib/browser/constants/sdk-version.d.ts +1 -1
  14. package/lib/browser/constants/sdk-version.js +1 -1
  15. package/lib/browser/functions/evaluate/browser-runtime/browser.js +13 -1
  16. package/lib/browser/functions/evaluate/evaluate.js +21 -5
  17. package/lib/browser/functions/evaluate/node-runtime/node-runtime.js +114 -23
  18. package/lib/browser/functions/evaluate/safe-state-expression.d.ts +33 -0
  19. package/lib/browser/functions/evaluate/safe-state-expression.js +236 -0
  20. package/lib/browser/functions/evaluate/should-force-browser-runtime-in-node.js +3 -1
  21. package/lib/browser/functions/extract-localized-values.d.ts +4 -0
  22. package/lib/browser/functions/extract-localized-values.js +65 -26
  23. package/lib/browser/functions/get-block-properties.js +2 -1
  24. package/lib/browser/functions/get-processed-block.d.ts +0 -1
  25. package/lib/browser/functions/get-processed-block.js +45 -57
  26. package/lib/browser/functions/is-editing-or-previewing.d.ts +1 -0
  27. package/lib/browser/functions/is-editing-or-previewing.js +3 -0
  28. package/lib/browser/functions/set.d.ts +29 -1
  29. package/lib/browser/functions/set.js +87 -3
  30. package/lib/browser/helpers/bounded-cache.d.ts +4 -0
  31. package/lib/browser/helpers/bounded-cache.js +12 -0
  32. package/lib/browser/helpers/copy-plain-data.d.ts +6 -0
  33. package/lib/browser/helpers/copy-plain-data.js +40 -0
  34. package/lib/edge/components/block/block.helpers.d.ts +1 -3
  35. package/lib/edge/components/block/block.helpers.js +31 -4
  36. package/lib/edge/components/block/block.svelte +14 -1
  37. package/lib/edge/components/content/components/styles.helpers.d.ts +1 -1
  38. package/lib/edge/components/content/components/styles.helpers.js +1 -1
  39. package/lib/edge/components/content/content.helpers.d.ts +5 -1
  40. package/lib/edge/components/content/content.helpers.js +39 -2
  41. package/lib/edge/components/content/content.svelte +4 -17
  42. package/lib/edge/constants/builder-registered-components.d.ts +1 -0
  43. package/lib/edge/constants/builder-registered-components.js +40 -33
  44. package/lib/edge/constants/device-sizes.js +14 -2
  45. package/lib/edge/constants/sdk-version.d.ts +1 -1
  46. package/lib/edge/constants/sdk-version.js +1 -1
  47. package/lib/edge/functions/evaluate/browser-runtime/browser.js +13 -1
  48. package/lib/edge/functions/evaluate/evaluate.js +21 -5
  49. package/lib/edge/functions/evaluate/node-runtime/node-runtime.js +114 -23
  50. package/lib/edge/functions/evaluate/safe-state-expression.d.ts +33 -0
  51. package/lib/edge/functions/evaluate/safe-state-expression.js +236 -0
  52. package/lib/edge/functions/evaluate/should-force-browser-runtime-in-node.js +3 -1
  53. package/lib/edge/functions/extract-localized-values.d.ts +4 -0
  54. package/lib/edge/functions/extract-localized-values.js +65 -26
  55. package/lib/edge/functions/get-block-properties.js +2 -1
  56. package/lib/edge/functions/get-processed-block.d.ts +0 -1
  57. package/lib/edge/functions/get-processed-block.js +45 -57
  58. package/lib/edge/functions/is-editing-or-previewing.d.ts +1 -0
  59. package/lib/edge/functions/is-editing-or-previewing.js +3 -0
  60. package/lib/edge/functions/set.d.ts +29 -1
  61. package/lib/edge/functions/set.js +87 -3
  62. package/lib/edge/helpers/bounded-cache.d.ts +4 -0
  63. package/lib/edge/helpers/bounded-cache.js +12 -0
  64. package/lib/edge/helpers/copy-plain-data.d.ts +6 -0
  65. package/lib/edge/helpers/copy-plain-data.js +40 -0
  66. package/lib/node/components/block/block.helpers.d.ts +1 -3
  67. package/lib/node/components/block/block.helpers.js +31 -4
  68. package/lib/node/components/block/block.svelte +14 -1
  69. package/lib/node/components/content/components/styles.helpers.d.ts +1 -1
  70. package/lib/node/components/content/components/styles.helpers.js +1 -1
  71. package/lib/node/components/content/content.helpers.d.ts +5 -1
  72. package/lib/node/components/content/content.helpers.js +39 -2
  73. package/lib/node/components/content/content.svelte +4 -17
  74. package/lib/node/constants/builder-registered-components.d.ts +1 -0
  75. package/lib/node/constants/builder-registered-components.js +40 -33
  76. package/lib/node/constants/device-sizes.js +14 -2
  77. package/lib/node/constants/sdk-version.d.ts +1 -1
  78. package/lib/node/constants/sdk-version.js +1 -1
  79. package/lib/node/functions/evaluate/browser-runtime/browser.js +13 -1
  80. package/lib/node/functions/evaluate/evaluate.js +21 -5
  81. package/lib/node/functions/evaluate/node-runtime/node-runtime.js +114 -23
  82. package/lib/node/functions/evaluate/safe-state-expression.d.ts +33 -0
  83. package/lib/node/functions/evaluate/safe-state-expression.js +236 -0
  84. package/lib/node/functions/evaluate/should-force-browser-runtime-in-node.js +3 -1
  85. package/lib/node/functions/extract-localized-values.d.ts +4 -0
  86. package/lib/node/functions/extract-localized-values.js +65 -26
  87. package/lib/node/functions/get-block-properties.js +2 -1
  88. package/lib/node/functions/get-processed-block.d.ts +0 -1
  89. package/lib/node/functions/get-processed-block.js +45 -57
  90. package/lib/node/functions/is-editing-or-previewing.d.ts +1 -0
  91. package/lib/node/functions/is-editing-or-previewing.js +3 -0
  92. package/lib/node/functions/set.d.ts +29 -1
  93. package/lib/node/functions/set.js +87 -3
  94. package/lib/node/helpers/bounded-cache.d.ts +4 -0
  95. package/lib/node/helpers/bounded-cache.js +12 -0
  96. package/lib/node/helpers/copy-plain-data.d.ts +6 -0
  97. package/lib/node/helpers/copy-plain-data.js +40 -0
  98. package/package.json +1 -1
  99. package/src/components/block/block.helpers.ts +38 -5
  100. package/src/components/block/block.svelte +14 -1
  101. package/src/components/content/components/styles.helpers.ts +1 -1
  102. package/src/components/content/content.helpers.ts +45 -4
  103. package/src/components/content/content.svelte +7 -24
  104. package/src/constants/builder-registered-components.ts +40 -34
  105. package/src/constants/device-sizes.ts +14 -3
  106. package/src/constants/sdk-version.ts +1 -1
  107. package/src/functions/evaluate/browser-runtime/browser.ts +14 -1
  108. package/src/functions/evaluate/evaluate.ts +22 -5
  109. package/src/functions/evaluate/node-runtime/node-runtime.ts +116 -25
  110. package/src/functions/evaluate/safe-state-expression.ts +224 -0
  111. package/src/functions/evaluate/should-force-browser-runtime-in-node.ts +3 -1
  112. package/src/functions/extract-localized-values.ts +61 -27
  113. package/src/functions/get-block-properties.ts +5 -1
  114. package/src/functions/get-processed-block.ts +46 -57
  115. package/src/functions/is-editing-or-previewing.ts +3 -0
  116. package/src/functions/set.ts +91 -3
  117. package/src/helpers/bounded-cache.ts +12 -0
  118. package/src/helpers/copy-plain-data.ts +36 -0
@@ -1,64 +1,25 @@
1
- import { TARGET } from '../constants/target';
2
- import { omit } from '../helpers/omit';
3
1
  import { evaluate } from './evaluate/index';
4
2
  import { resolveLocalizedValues } from './extract-localized-values';
5
- import { fastClone } from './fast-clone';
6
- import { set } from './set';
3
+ import { isEditingOrPreviewing } from './is-editing-or-previewing';
4
+ import { setCopyOnWrite } from './set';
7
5
  import { transformBlock } from './transform-block';
8
- // Deep clone a block but without cloning any child blocks
9
- export function deepCloneWithConditions(obj) {
10
- if (obj === null || typeof obj !== 'object') {
11
- return obj;
12
- }
13
- if (Array.isArray(obj)) {
14
- return obj.map((item) => deepCloneWithConditions(item));
15
- }
16
- if (obj['@type'] === '@builder.io/sdk:Element') {
17
- return obj;
18
- }
19
- const clonedObj = {};
20
- for (const key in obj) {
21
- if (key !== 'meta' && Object.prototype.hasOwnProperty.call(obj, key)) {
22
- clonedObj[key] = deepCloneWithConditions(obj[key]);
23
- }
24
- }
25
- return clonedObj;
26
- }
27
- const IS_SDK_WITHOUT_CACHED_PROCESSED_BLOCK = ['svelte', 'vue', 'angular', 'qwik', 'solid'].includes(TARGET);
28
- const getCopy = (block) => {
29
- if (IS_SDK_WITHOUT_CACHED_PROCESSED_BLOCK) {
30
- const copy = fastClone(block);
31
- const copied = {
32
- ...copy,
33
- properties: {
34
- ...copy.properties
35
- },
36
- actions: {
37
- ...copy.actions
38
- }
39
- };
40
- return copied;
41
- }
42
- else {
43
- const copy = deepCloneWithConditions(omit(block, 'children', 'meta'));
44
- return {
45
- ...copy,
46
- properties: {
47
- ...copy.properties
48
- },
49
- actions: {
50
- ...copy.actions
51
- },
52
- children: block.children,
53
- meta: block.meta
54
- };
55
- }
56
- };
57
6
  const evaluateBindings = ({ block, context, localState, rootState, rootSetState }) => {
58
7
  if (!block.bindings) {
59
8
  return block;
60
9
  }
61
- const copied = getCopy(block);
10
+ // Only objects along a bound path are copied: the block (and content) passed in is never mutated.
11
+ const copied = new WeakSet();
12
+ const copy = {
13
+ ...block,
14
+ properties: {
15
+ ...block.properties
16
+ },
17
+ actions: {
18
+ ...block.actions
19
+ }
20
+ };
21
+ copied.add(copy.properties);
22
+ copied.add(copy.actions);
62
23
  for (const binding in block.bindings) {
63
24
  const expression = block.bindings[binding];
64
25
  const value = evaluate({
@@ -68,11 +29,32 @@ const evaluateBindings = ({ block, context, localState, rootState, rootSetState
68
29
  rootSetState,
69
30
  context
70
31
  });
71
- set(copied, binding, value);
32
+ setCopyOnWrite(copy, binding, value, copied);
72
33
  }
73
- return copied;
34
+ return copy;
35
+ };
36
+ /**
37
+ * A block without bindings processes to the same result for a given locale, so
38
+ * it is computed once per block object instead of on every render.
39
+ * Only the latest locale is kept, so a block holds at most one cached result.
40
+ */
41
+ const PROCESSED_BLOCKS_WITHOUT_BINDINGS = new WeakMap();
42
+ const hasBindings = (block) => {
43
+ if (!block.bindings)
44
+ return false;
45
+ for (const _ in block.bindings)
46
+ return true;
47
+ return false;
74
48
  };
75
49
  export function getProcessedBlock({ block, context, localState, rootState, rootSetState }) {
50
+ const locale = rootState.locale;
51
+ const canUseCache = !hasBindings(block) && typeof block === 'object' && block !== null && !isEditingOrPreviewing();
52
+ const cacheKey = locale ?? '';
53
+ if (canUseCache) {
54
+ const cached = PROCESSED_BLOCKS_WITHOUT_BINDINGS.get(block);
55
+ if (cached && cached.locale === cacheKey)
56
+ return cached.block;
57
+ }
76
58
  let transformedBlock = transformBlock(block);
77
59
  transformedBlock = evaluateBindings({
78
60
  block: transformedBlock,
@@ -81,6 +63,12 @@ export function getProcessedBlock({ block, context, localState, rootState, rootS
81
63
  rootSetState,
82
64
  context
83
65
  });
84
- transformedBlock = resolveLocalizedValues(transformedBlock, rootState.locale);
66
+ transformedBlock = resolveLocalizedValues(transformedBlock, locale);
67
+ if (canUseCache) {
68
+ PROCESSED_BLOCKS_WITHOUT_BINDINGS.set(block, {
69
+ locale: cacheKey,
70
+ block: transformedBlock
71
+ });
72
+ }
85
73
  return transformedBlock;
86
74
  }
@@ -0,0 +1 @@
1
+ export declare const isEditingOrPreviewing: () => boolean;
@@ -0,0 +1,3 @@
1
+ import { isEditing } from './is-editing';
2
+ import { isPreviewing } from './is-previewing';
3
+ export const isEditingOrPreviewing = () => isEditing() || isPreviewing();
@@ -2,6 +2,34 @@
2
2
  * Minimal implementation of lodash's _.set
3
3
  * https://lodash.com/docs/4.17.15#set
4
4
  *
5
- * See ./set.test.ts for usage examples
5
+ * Mutates `obj` in place, creating missing objects/arrays along the path:
6
+ *
7
+ * const obj = { a: { b: 1 } };
8
+ * set(obj, 'a.c', 2); // obj is { a: { b: 1, c: 2 } }
9
+ * set(obj, 'list.0', 'x'); // obj.list is ['x']
10
+ *
11
+ * See ./set.test.ts for more usage examples
6
12
  */
7
13
  export declare const set: (obj: any, _path: string | string[], value: any) => any;
14
+ /**
15
+ * Like `set`, but every object or array along `path` that is not in `copied`
16
+ * is shallow-copied before being written to, so objects shared with the
17
+ * original are never mutated. `obj` itself must already be a copy.
18
+ *
19
+ * const block = { options: { text: 'a' }, style: { color: 'red' } };
20
+ * const copy = { ...block };
21
+ * setCopyOnWrite(copy, 'options.text', 'b', new WeakSet());
22
+ * // copy.options is a new object: { text: 'b' }
23
+ * // block.options.text is still 'a'
24
+ * // copy.style === block.style (untouched branches stay shared)
25
+ */
26
+ export declare const setCopyOnWrite: (obj: any, _path: string, value: any, copied: WeakSet<object>) => any;
27
+ /**
28
+ * Deletes the property at `path`, the inverse of `set` for an array path.
29
+ * Mutates `obj` in place and does nothing when a parent is missing:
30
+ *
31
+ * const obj = { a: { b: 1, c: 2 } };
32
+ * unset(obj, ['a', 'b']); // obj is { a: { c: 2 } }
33
+ * unset(obj, ['x', 'y']); // no-op
34
+ */
35
+ export declare const unset: (obj: any, path: string[]) => any;
@@ -1,14 +1,98 @@
1
+ const parsePath = (path) => path.toString().match(/[^.[\]]+/g);
2
+ const isArrayIndex = (key) => Math.abs(Number(key)) >> 0 === +key;
3
+ /**
4
+ * `__proto__` is never allowed. `constructor` and `prototype` are allowed only as
5
+ * the final key, where they are written as ordinary own properties.
6
+ */
7
+ const hasUnsafeSegment = (path) => path.some((key, i) => key === '__proto__' || i < path.length - 1 && (key === 'constructor' || key === 'prototype'));
8
+ /**
9
+ * Own properties only, so a path can never walk into a value shared through a
10
+ * prototype (e.g. `Object.prototype.toString`) and mutate it.
11
+ */
12
+ const getOwn = (obj, key) => Object.prototype.hasOwnProperty.call(obj, key) ? obj[key] : undefined;
1
13
  /**
2
14
  * Minimal implementation of lodash's _.set
3
15
  * https://lodash.com/docs/4.17.15#set
4
16
  *
5
- * See ./set.test.ts for usage examples
17
+ * Mutates `obj` in place, creating missing objects/arrays along the path:
18
+ *
19
+ * const obj = { a: { b: 1 } };
20
+ * set(obj, 'a.c', 2); // obj is { a: { b: 1, c: 2 } }
21
+ * set(obj, 'list.0', 'x'); // obj.list is ['x']
22
+ *
23
+ * See ./set.test.ts for more usage examples
6
24
  */
7
25
  export const set = (obj, _path, value) => {
8
26
  if (Object(obj) !== obj) {
9
27
  return obj;
10
28
  }
11
- const path = Array.isArray(_path) ? _path : _path.toString().match(/[^.[\]]+/g);
12
- path.slice(0, -1).reduce((a, c, i) => Object(a[c]) === a[c] ? a[c] : a[c] = Math.abs(Number(path[i + 1])) >> 0 === +path[i + 1] ? [] : {}, obj)[path[path.length - 1]] = value;
29
+ const path = Array.isArray(_path) ? _path : parsePath(_path);
30
+ if (!path || hasUnsafeSegment(path)) {
31
+ return obj;
32
+ }
33
+ path.slice(0, -1).reduce((a, c, i) => {
34
+ const next = getOwn(a, c);
35
+ return Object(next) === next ? next : a[c] = isArrayIndex(path[i + 1]) ? [] : {};
36
+ }, obj)[path[path.length - 1]] = value;
37
+ return obj;
38
+ };
39
+ /**
40
+ * Like `set`, but every object or array along `path` that is not in `copied`
41
+ * is shallow-copied before being written to, so objects shared with the
42
+ * original are never mutated. `obj` itself must already be a copy.
43
+ *
44
+ * const block = { options: { text: 'a' }, style: { color: 'red' } };
45
+ * const copy = { ...block };
46
+ * setCopyOnWrite(copy, 'options.text', 'b', new WeakSet());
47
+ * // copy.options is a new object: { text: 'b' }
48
+ * // block.options.text is still 'a'
49
+ * // copy.style === block.style (untouched branches stay shared)
50
+ */
51
+ export const setCopyOnWrite = (obj, _path, value, copied) => {
52
+ if (Object(obj) !== obj) {
53
+ return obj;
54
+ }
55
+ const path = parsePath(_path);
56
+ if (!path || hasUnsafeSegment(path)) {
57
+ return obj;
58
+ }
59
+ copied.add(obj);
60
+ let current = obj;
61
+ for (let i = 0; i < path.length - 1; i++) {
62
+ const key = path[i];
63
+ let next = getOwn(current, key);
64
+ if (Object(next) !== next) {
65
+ next = isArrayIndex(path[i + 1]) ? [] : {};
66
+ copied.add(next);
67
+ current[key] = next;
68
+ }
69
+ else if (typeof next === 'object' && !copied.has(next)) {
70
+ next = Array.isArray(next) ? next.slice() : {
71
+ ...next
72
+ };
73
+ copied.add(next);
74
+ current[key] = next;
75
+ }
76
+ current = next;
77
+ }
78
+ current[path[path.length - 1]] = value;
79
+ return obj;
80
+ };
81
+ /**
82
+ * Deletes the property at `path`, the inverse of `set` for an array path.
83
+ * Mutates `obj` in place and does nothing when a parent is missing:
84
+ *
85
+ * const obj = { a: { b: 1, c: 2 } };
86
+ * unset(obj, ['a', 'b']); // obj is { a: { c: 2 } }
87
+ * unset(obj, ['x', 'y']); // no-op
88
+ */
89
+ export const unset = (obj, path) => {
90
+ if (Object(obj) !== obj || !path.length || hasUnsafeSegment(path)) {
91
+ return obj;
92
+ }
93
+ const parent = path.slice(0, -1).reduce((a, c) => Object(a) === a ? getOwn(a, c) : undefined, obj);
94
+ if (Object(parent) === parent) {
95
+ delete parent[path[path.length - 1]];
96
+ }
13
97
  return obj;
14
98
  };
@@ -0,0 +1,4 @@
1
+ export declare const createBoundedCache: <V>(max: number) => {
2
+ get: (key: string) => V | undefined;
3
+ set: (key: string, value: V) => void;
4
+ };
@@ -0,0 +1,12 @@
1
+ export const createBoundedCache = (max) => {
2
+ const map = new Map();
3
+ return {
4
+ get: (key) => map.get(key),
5
+ set: (key, value) => {
6
+ if (!map.has(key) && map.size >= max) {
7
+ map.delete(map.keys().next().value);
8
+ }
9
+ map.set(key, value);
10
+ }
11
+ };
12
+ };
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Deep-copies plain objects and arrays (anything else is kept by reference), so
3
+ * that state writes made while rendering never reach the content object, which
4
+ * apps often cache and share between requests.
5
+ */
6
+ export declare const copyPlainData: <T>(value: T, seen?: WeakMap<object, any>) => T;
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Deep-copies plain objects and arrays (anything else is kept by reference), so
3
+ * that state writes made while rendering never reach the content object, which
4
+ * apps often cache and share between requests.
5
+ */
6
+ export const copyPlainData = (value, seen = new WeakMap()) => {
7
+ if (value === null || typeof value !== 'object')
8
+ return value;
9
+ if (seen.has(value))
10
+ return seen.get(value);
11
+ if (Array.isArray(value)) {
12
+ const copy = [];
13
+ seen.set(value, copy);
14
+ for (let i = 0; i < value.length; i++) {
15
+ copy[i] = copyPlainData(value[i], seen);
16
+ }
17
+ return copy;
18
+ }
19
+ const proto = Object.getPrototypeOf(value);
20
+ if (proto !== Object.prototype && proto !== null)
21
+ return value;
22
+ const copy = {};
23
+ seen.set(value, copy);
24
+ for (const key of Object.keys(value)) {
25
+ const copied = copyPlainData(value[key], seen);
26
+ if (key === '__proto__') {
27
+ // Assigning would call the prototype setter; JSON can carry `__proto__` as an own key.
28
+ Object.defineProperty(copy, key, {
29
+ value: copied,
30
+ enumerable: true,
31
+ writable: true,
32
+ configurable: true
33
+ });
34
+ }
35
+ else {
36
+ copy[key] = copied;
37
+ }
38
+ }
39
+ return copy;
40
+ };
@@ -22,9 +22,7 @@ export declare const provideLinkComponent: (block: RegisteredComponent | null |
22
22
  builderLinkComponent?: undefined;
23
23
  };
24
24
  export declare const provideRegisteredComponents: (block: RegisteredComponent | null | undefined, registeredComponents: RegisteredComponents, model: string) => {
25
- builderComponents: {
26
- [k: string]: RegisteredComponent;
27
- };
25
+ builderComponents: RegisteredComponents;
28
26
  } | {
29
27
  builderComponents?: undefined;
30
28
  };
@@ -1,6 +1,7 @@
1
1
  import { evaluate } from '../../functions/evaluate/index';
2
2
  import { extractTextStyles } from '../../functions/extract-text-styles';
3
3
  import { getStyle } from '../../functions/get-style';
4
+ import { isEditingOrPreviewing } from '../../functions/is-editing-or-previewing';
4
5
  const checkIsComponentRestricted = (component, model) => {
5
6
  if (!component)
6
7
  return true;
@@ -25,12 +26,27 @@ export const getComponent = ({ block, registeredComponents, model }) => {
25
26
  return ref;
26
27
  }
27
28
  };
29
+ // Reused per block so repeated items keep a stable block identity across renders.
30
+ const BLOCKS_WITHOUT_REPEAT = new WeakMap();
31
+ const getBlockWithoutRepeat = (block) => {
32
+ if (isEditingOrPreviewing()) {
33
+ const { repeat: _repeat, ...rest } = block;
34
+ return rest;
35
+ }
36
+ let blockWithoutRepeat = BLOCKS_WITHOUT_REPEAT.get(block);
37
+ if (!blockWithoutRepeat) {
38
+ const { repeat: _repeat, ...rest } = block;
39
+ blockWithoutRepeat = rest;
40
+ BLOCKS_WITHOUT_REPEAT.set(block, blockWithoutRepeat);
41
+ }
42
+ return blockWithoutRepeat;
43
+ };
28
44
  export const getRepeatItemData = ({ block, context }) => {
29
45
  /**
30
46
  * we don't use `state.processedBlock` here because the processing done within its logic includes evaluating the block's bindings,
31
47
  * which will not work if there is a repeat.
32
48
  */
33
- const { repeat, ...blockWithoutRepeat } = block;
49
+ const { repeat } = block;
34
50
  if (!repeat?.collection) {
35
51
  return undefined;
36
52
  }
@@ -46,6 +62,7 @@ export const getRepeatItemData = ({ block, context }) => {
46
62
  }
47
63
  const collectionName = repeat.collection.split('.').pop();
48
64
  const itemNameToUse = repeat.itemName || (collectionName ? collectionName + 'Item' : 'item');
65
+ const blockWithoutRepeat = getBlockWithoutRepeat(block);
49
66
  const repeatArray = itemsArray.map((item, index) => ({
50
67
  context: {
51
68
  ...context,
@@ -78,11 +95,21 @@ export const provideLinkComponent = (block, linkComponent) => {
78
95
  };
79
96
  return {};
80
97
  };
98
+ const FILTERED_REGISTERED_COMPONENTS = new WeakMap();
81
99
  export const provideRegisteredComponents = (block, registeredComponents, model) => {
82
100
  if (block?.shouldReceiveBuilderProps?.builderComponents) {
83
- const filteredRegisteredComponents = Object.fromEntries(Object.entries(registeredComponents).filter(([_, component]) => {
84
- return !checkIsComponentRestricted(component, model);
85
- }));
101
+ let byModel = FILTERED_REGISTERED_COMPONENTS.get(registeredComponents);
102
+ if (!byModel) {
103
+ byModel = new Map();
104
+ FILTERED_REGISTERED_COMPONENTS.set(registeredComponents, byModel);
105
+ }
106
+ let filteredRegisteredComponents = byModel.get(model);
107
+ if (!filteredRegisteredComponents) {
108
+ filteredRegisteredComponents = Object.fromEntries(Object.entries(registeredComponents).filter(([_, component]) => {
109
+ return !checkIsComponentRestricted(component, model);
110
+ }));
111
+ byModel.set(model, filteredRegisteredComponents);
112
+ }
86
113
  return {
87
114
  builderComponents: filteredRegisteredComponents
88
115
  };
@@ -73,7 +73,15 @@ $: childrenWithoutParentComponent = () => {
73
73
  : [];
74
74
  };
75
75
  $: componentRefProps = () => {
76
- return {
76
+ const deps = [
77
+ processedBlock(),
78
+ $context,
79
+ $context.rootState,
80
+ $context.localState,
81
+ registeredComponents,
82
+ linkComponent,
83
+ ];
84
+ const componentRefProps = {
77
85
  blockChildren: processedBlock().children ?? [],
78
86
  componentRef: blockComponent?.()?.component,
79
87
  componentOptions: {
@@ -90,11 +98,16 @@ $: componentRefProps = () => {
90
98
  includeBlockProps: blockComponent?.()?.noWrap === true,
91
99
  isInteractive: !(blockComponent?.()?.isRSC && TARGET === "rsc"),
92
100
  };
101
+ return componentRefProps;
93
102
  };
94
103
  let _processedBlock = {
95
104
  value: null,
96
105
  update: false,
97
106
  };
107
+ let _componentRefProps = {
108
+ value: null,
109
+ deps: [],
110
+ };
98
111
  onMount(() => {
99
112
  const blockId = processedBlock().id;
100
113
  const animations = processedBlock().animations;
@@ -13,5 +13,5 @@ export declare const getCss: ({ cssCode, contentId }: {
13
13
  cssCode?: string | undefined;
14
14
  contentId?: string | undefined;
15
15
  }) => string;
16
- export declare const getDefaultStyles: (isNested: boolean | undefined) => "" | "\n.builder-button {\n all: unset;\n}\n\n.builder-text > p:first-of-type, .builder-text > .builder-paragraph:first-of-type {\n margin: 0;\n}\n.builder-text > p, .builder-text > .builder-paragraph {\n color: inherit;\n line-height: inherit;\n letter-spacing: inherit;\n font-weight: inherit;\n font-size: inherit;\n text-align: inherit;\n font-family: inherit;\n}\n";
16
+ export declare const getDefaultStyles: (isNested: boolean | undefined) => "" | "\n:where(.builder-button) {\n all: unset;\n}\n\n.builder-text > p:first-of-type, .builder-text > .builder-paragraph:first-of-type {\n margin: 0;\n}\n.builder-text > p, .builder-text > .builder-paragraph {\n color: inherit;\n line-height: inherit;\n letter-spacing: inherit;\n font-weight: inherit;\n font-size: inherit;\n text-align: inherit;\n font-family: inherit;\n}\n";
17
17
  export declare const getWrapperClassName: (variationId?: string) => string;
@@ -57,7 +57,7 @@ export const getCss = ({ cssCode, contentId }) => {
57
57
  return cssCode?.replace(/&/g, `div[builder-content-id="${contentId}"]`) || '';
58
58
  };
59
59
  const DEFAULT_STYLES = `
60
- .builder-button {
60
+ :where(.builder-button) {
61
61
  all: unset;
62
62
  }
63
63
 
@@ -1,6 +1,10 @@
1
+ import type { RegisteredComponent, RegisteredComponents } from '../../context/types';
1
2
  import type { BuilderContent } from '../../types/builder-content';
2
- import type { Nullable } from '../../types/typescript';
3
+ import type { ComponentInfo } from '../../types/components';
4
+ import type { Dictionary, Nullable } from '../../types/typescript';
3
5
  import type { ContentProps } from './content.types';
6
+ export declare const getRegisteredComponents: (registrations: RegisteredComponent[], wrapComponent: (component: any) => any) => RegisteredComponents;
7
+ export declare const getComponentInfos: (registrations: RegisteredComponent[]) => Dictionary<ComponentInfo>;
4
8
  export declare const getRootStateInitialValue: ({ content, data, locale }: Pick<ContentProps, 'content' | 'data' | 'locale'>) => {
5
9
  locale?: string | undefined;
6
10
  };
@@ -1,10 +1,47 @@
1
+ import { serializeIncludingFunctions } from '../../functions/register-component';
2
+ import { copyPlainData } from '../../helpers/copy-plain-data';
3
+ // Registrations are usually module constants, so their derived forms are built once per object.
4
+ const SERIALIZED_COMPONENT_INFOS = new WeakMap();
5
+ const REGISTERED_COMPONENT_ENTRIES = new WeakMap();
6
+ const getSerializedComponentInfo = (registration) => {
7
+ const cached = SERIALIZED_COMPONENT_INFOS.get(registration);
8
+ if (cached)
9
+ return cached;
10
+ const { component: _, ...rest } = registration;
11
+ const info = serializeIncludingFunctions(rest);
12
+ SERIALIZED_COMPONENT_INFOS.set(registration, info);
13
+ return info;
14
+ };
15
+ export const getRegisteredComponents = (registrations, wrapComponent) => {
16
+ const registeredComponents = {};
17
+ for (const registration of registrations) {
18
+ let entry = REGISTERED_COMPONENT_ENTRIES.get(registration);
19
+ if (!entry) {
20
+ entry = {
21
+ component: wrapComponent(registration.component),
22
+ ...getSerializedComponentInfo(registration)
23
+ };
24
+ REGISTERED_COMPONENT_ENTRIES.set(registration, entry);
25
+ }
26
+ registeredComponents[registration.name] = entry;
27
+ }
28
+ return registeredComponents;
29
+ };
30
+ export const getComponentInfos = (registrations) => {
31
+ const componentInfos = {};
32
+ for (const registration of registrations) {
33
+ componentInfos[registration.name] = getSerializedComponentInfo(registration);
34
+ }
35
+ return componentInfos;
36
+ };
1
37
  export const getRootStateInitialValue = ({ content, data, locale }) => {
2
38
  const defaultValues = {};
3
- const initialState = content?.data?.state || {};
39
+ const seen = new WeakMap();
40
+ const initialState = copyPlainData(content?.data?.state || {}, seen);
4
41
  // set default values for content state inputs
5
42
  content?.data?.inputs?.forEach(input => {
6
43
  if (input.name && input.defaultValue !== undefined) {
7
- defaultValues[input.name] = input.defaultValue;
44
+ defaultValues[input.name] = copyPlainData(input.defaultValue, seen);
8
45
  }
9
46
  });
10
47
  return {
@@ -4,7 +4,6 @@ import { getDefaultRegisteredComponents } from "../../constants/builder-register
4
4
  import { TARGET } from "../../constants/target";
5
5
  import ComponentsContext from "../../context/components.context";
6
6
  import { evaluate } from "../../functions/evaluate/evaluate";
7
- import { serializeIncludingFunctions } from "../../functions/register-component";
8
7
  import { logger } from "../../helpers/logger";
9
8
  import Blocks from "../blocks/blocks.svelte";
10
9
  import { getUpdateVariantVisibilityScript } from "../content-variants/helpers";
@@ -12,7 +11,7 @@ import "../dynamic-div.svelte";
12
11
  import InlinedScript from "../inlined-script.svelte";
13
12
  import EnableEditor from "./components/enable-editor.svelte";
14
13
  import ContentStyles from "./components/styles.svelte";
15
- import { getContentInitialValue, getRootStateInitialValue, } from "./content.helpers";
14
+ import { getComponentInfos, getContentInitialValue, getRegisteredComponents, getRootStateInitialValue, } from "./content.helpers";
16
15
  import "./wrap-component-ref";
17
16
  export let content;
18
17
  export let data = undefined;
@@ -45,16 +44,7 @@ let scriptStr = getUpdateVariantVisibilityScript({
45
44
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
46
45
  contentId: content?.id,
47
46
  });
48
- let registeredComponents = [
49
- ...getDefaultRegisteredComponents(),
50
- ...(customComponents || []),
51
- ].reduce((acc, { component, ...info }) => ({
52
- ...acc,
53
- [info.name]: {
54
- component: component,
55
- ...serializeIncludingFunctions(info),
56
- },
57
- }), {});
47
+ let registeredComponents = getRegisteredComponents([...getDefaultRegisteredComponents(), ...(customComponents || [])], (component) => component);
58
48
  let builderContextSignal = writable({
59
49
  content: getContentInitialValue({
60
50
  content: content,
@@ -71,13 +61,10 @@ let builderContextSignal = writable({
71
61
  canTrack: canTrack,
72
62
  apiKey: apiKey,
73
63
  apiVersion: apiVersion,
74
- componentInfos: [
64
+ componentInfos: getComponentInfos([
75
65
  ...getDefaultRegisteredComponents(),
76
66
  ...(customComponents || []),
77
- ].reduce((acc, { component: _, ...info }) => ({
78
- ...acc,
79
- [info.name]: serializeIncludingFunctions(info),
80
- }), {}),
67
+ ]),
81
68
  inheritedStyles: {},
82
69
  BlocksWrapper: blocksWrapper || "div",
83
70
  BlocksWrapperProps: blocksWrapperProps || {},
@@ -2,5 +2,6 @@ import type { RegisteredComponent } from '../context/types';
2
2
  /**
3
3
  * Returns a list of all registered components.
4
4
  * NOTE: This needs to be a function to work around ESM circular dependencies.
5
+ * The list is built once, on first use, so its entries keep a stable identity.
5
6
  */
6
7
  export declare const getDefaultRegisteredComponents: () => RegisteredComponent[];