@builder.io/sdk-svelte 0.2.3 → 0.3.1

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 (46) hide show
  1. package/package/--package.json +3 -1
  2. package/package/CHANGELOG.md +4 -0
  3. package/package/blocks/columns/columns.svelte +1 -1
  4. package/package/blocks/symbol/symbol.svelte +1 -1
  5. package/package/components/render-block/block-styles.svelte +3 -1
  6. package/package/components/render-block/render-block.helpers.d.ts +0 -1
  7. package/package/components/render-block/render-block.helpers.js +8 -20
  8. package/package/components/render-block/render-block.svelte +21 -15
  9. package/package/components/render-block/render-repeated-block.svelte +3 -2
  10. package/package/components/render-content/render-content.svelte +15 -8
  11. package/package/components/render-inlined-styles.svelte +2 -2
  12. package/package/constants/sdk-version.d.ts +1 -0
  13. package/package/constants/sdk-version.js +1 -0
  14. package/package/context/builder.context.d.ts +3 -2
  15. package/package/context/builder.context.js +3 -2
  16. package/package/context/types.d.ts +17 -2
  17. package/package/functions/evaluate.d.ts +4 -3
  18. package/package/functions/evaluate.js +23 -2
  19. package/package/functions/evaluate.test.d.ts +1 -0
  20. package/package/functions/evaluate.test.js +17 -0
  21. package/package/functions/get-block-actions-handler.d.ts +1 -1
  22. package/package/functions/get-block-actions-handler.js +3 -1
  23. package/package/functions/get-block-actions.d.ts +1 -1
  24. package/package/functions/get-processed-block.d.ts +2 -2
  25. package/package/functions/get-processed-block.js +16 -4
  26. package/package/functions/get-processed-block.test.js +3 -1
  27. package/package/scripts/init-editing.js +2 -0
  28. package/package.json +1 -4
  29. package/src/blocks/columns/columns.svelte +1 -5
  30. package/src/blocks/symbol/symbol.svelte +1 -1
  31. package/src/components/render-block/block-styles.svelte +3 -1
  32. package/src/components/render-block/render-block.helpers.ts +9 -31
  33. package/src/components/render-block/render-block.svelte +20 -15
  34. package/src/components/render-block/render-repeated-block.svelte +3 -2
  35. package/src/components/render-content/render-content.svelte +15 -8
  36. package/src/components/render-inlined-styles.svelte +2 -2
  37. package/src/constants/sdk-version.ts +1 -0
  38. package/src/context/builder.context.ts +3 -2
  39. package/src/context/types.ts +17 -2
  40. package/src/functions/evaluate.test.ts +21 -0
  41. package/src/functions/evaluate.ts +46 -4
  42. package/src/functions/get-block-actions-handler.ts +7 -2
  43. package/src/functions/get-block-actions.ts +4 -1
  44. package/src/functions/get-processed-block.test.ts +3 -1
  45. package/src/functions/get-processed-block.ts +28 -6
  46. package/src/scripts/init-editing.ts +2 -0
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@builder.io/sdk-svelte",
3
3
  "description": "Builder.io SDK for Svelte",
4
- "version": "0.2.2",
4
+ "version": "0.3.1",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "package",
@@ -81,10 +81,12 @@
81
81
  "./components/render-inlined-styles.svelte": "./components/render-inlined-styles.svelte",
82
82
  "./constants/builder-registered-components": "./constants/builder-registered-components.js",
83
83
  "./constants/device-sizes": "./constants/device-sizes.js",
84
+ "./constants/sdk-version": "./constants/sdk-version.js",
84
85
  "./constants/target": "./constants/target.js",
85
86
  "./context/builder.context": "./context/builder.context.js",
86
87
  "./context/types": "./context/types.js",
87
88
  "./functions/camel-to-kebab-case": "./functions/camel-to-kebab-case.js",
89
+ "./functions/evaluate.test": "./functions/evaluate.test.js",
88
90
  "./functions/evaluate": "./functions/evaluate.js",
89
91
  "./functions/event-handler-name": "./functions/event-handler-name.js",
90
92
  "./functions/extract-text-styles": "./functions/extract-text-styles.js",
@@ -1,3 +1,7 @@
1
+ ### 0.2.3
2
+
3
+ - No Changes.
4
+
1
5
  ### 0.2.2
2
6
 
3
7
  - Fix: dynamic bindings for Link URLs.
@@ -43,7 +43,7 @@ function columnCssVars(index) {
43
43
  };
44
44
  }
45
45
  const width = getColumnCssWidth(index);
46
- const gutterPixels = `${gutterSize}px`;
46
+ const gutterPixels = `${gutter}px`;
47
47
  const mobileWidth = "100%";
48
48
  const mobileMarginLeft = 0;
49
49
  return {
@@ -104,7 +104,7 @@ $: onUpdateFn_0(...[symbol]);
104
104
  customComponents={Object.values(builderContext.registeredComponents)}
105
105
  data={{
106
106
  ...symbol?.data,
107
- ...builderContext.state,
107
+ ...builderContext.localState,
108
108
  ...contentToUse?.data?.state,
109
109
  }}
110
110
  model={symbol?.model}
@@ -11,7 +11,9 @@ export let context;
11
11
  $: useBlock = () => {
12
12
  return getProcessedBlock({
13
13
  block: block,
14
- state: context.state,
14
+ localState: context.localState,
15
+ rootState: context.rootState,
16
+ rootSetState: context.rootSetState,
15
17
  context: context.context,
16
18
  shouldEvaluateBindings: true,
17
19
  });
@@ -10,4 +10,3 @@ export declare const getRepeatItemData: ({ block, context, }: {
10
10
  block: BuilderBlock;
11
11
  context: BuilderContextInterface;
12
12
  }) => RepeatData[] | undefined;
13
- export declare const getProxyState: (context: BuilderContextInterface) => BuilderContextInterface['state'];
@@ -27,7 +27,9 @@ export const isEmptyHtmlElement = (tagName) => {
27
27
  export const getComponent = ({ block, context, }) => {
28
28
  const componentName = getProcessedBlock({
29
29
  block,
30
- state: context.state,
30
+ localState: context.localState,
31
+ rootState: context.rootState,
32
+ rootSetState: context.rootSetState,
31
33
  context: context.context,
32
34
  shouldEvaluateBindings: false,
33
35
  }).component?.name;
@@ -57,7 +59,9 @@ export const getRepeatItemData = ({ block, context, }) => {
57
59
  }
58
60
  const itemsArray = evaluate({
59
61
  code: repeat.collection,
60
- state: context.state,
62
+ localState: context.localState,
63
+ rootState: context.rootState,
64
+ rootSetState: context.rootSetState,
61
65
  context: context.context,
62
66
  });
63
67
  if (!Array.isArray(itemsArray)) {
@@ -68,8 +72,8 @@ export const getRepeatItemData = ({ block, context, }) => {
68
72
  const repeatArray = itemsArray.map((item, index) => ({
69
73
  context: {
70
74
  ...context,
71
- state: {
72
- ...context.state,
75
+ localState: {
76
+ ...context.localState,
73
77
  $index: index,
74
78
  $item: item,
75
79
  [itemNameToUse]: item,
@@ -80,19 +84,3 @@ export const getRepeatItemData = ({ block, context, }) => {
80
84
  }));
81
85
  return repeatArray;
82
86
  };
83
- export const getProxyState = (context) => {
84
- if (typeof Proxy === 'undefined') {
85
- console.error('no Proxy available in this environment, cannot proxy state.');
86
- return context.state;
87
- }
88
- const useState = new Proxy(context.state, {
89
- set: (obj, prop, value) => {
90
- // set the value on the state object, so that the event handler instantly gets the update.
91
- obj[prop] = value;
92
- // set the value in the context, so that the rest of the app gets the update.
93
- context.setState?.(obj);
94
- return true;
95
- },
96
- });
97
- return useState;
98
- };
@@ -5,7 +5,7 @@ import { getBlockComponentOptions } from "../../functions/get-block-component-op
5
5
  import { getBlockProperties } from "../../functions/get-block-properties.js";
6
6
  import { getProcessedBlock } from "../../functions/get-processed-block.js";
7
7
  import BlockStyles from "./block-styles.svelte";
8
- import { getComponent, getProxyState, getRepeatItemData, isEmptyHtmlElement, } from "./render-block.helpers.js";
8
+ import { getComponent, getRepeatItemData, isEmptyHtmlElement, } from "./render-block.helpers.js";
9
9
  import RenderRepeatedBlock from "./render-repeated-block.svelte";
10
10
  import { TARGET } from "../../constants/target.js";
11
11
  import { extractTextStyles } from "../../functions/extract-text-styles.js";
@@ -29,12 +29,20 @@ const setAttrs = (node, attrs = {}) => {
29
29
  };
30
30
  export let block;
31
31
  export let context;
32
+ $: repeatItem = () => {
33
+ return getRepeatItemData({
34
+ block: block,
35
+ context: context,
36
+ });
37
+ };
32
38
  $: useBlock = () => {
33
- return repeatItemData
39
+ return repeatItem()
34
40
  ? block
35
41
  : getProcessedBlock({
36
42
  block: block,
37
- state: context.state,
43
+ localState: context.localState,
44
+ rootState: context.rootState,
45
+ rootSetState: context.rootSetState,
38
46
  context: context.context,
39
47
  shouldEvaluateBindings: true,
40
48
  });
@@ -51,7 +59,9 @@ $: canShowBlock = () => {
51
59
  $: actions = () => {
52
60
  return getBlockActions({
53
61
  block: useBlock(),
54
- state: TARGET === "qwik" ? context.state : proxyState,
62
+ rootState: context.rootState,
63
+ rootSetState: context.rootSetState,
64
+ localState: context.localState,
55
65
  context: context.context,
56
66
  });
57
67
  };
@@ -77,7 +87,7 @@ $: childrenWithoutParentComponent = () => {
77
87
  * NOTE: We make sure not to render this if `repeatItemData` is non-null, because that means we are rendering an array of
78
88
  * blocks, and the children will be repeated within those blocks.
79
89
  */
80
- const shouldRenderChildrenOutsideRef = !component?.component && !repeatItemData;
90
+ const shouldRenderChildrenOutsideRef = !component?.component && !repeatItem();
81
91
  return shouldRenderChildrenOutsideRef ? useBlock().children ?? [] : [];
82
92
  };
83
93
  $: childrenContext = () => {
@@ -94,10 +104,11 @@ $: childrenContext = () => {
94
104
  return {
95
105
  apiKey: context.apiKey,
96
106
  apiVersion: context.apiVersion,
97
- state: context.state,
107
+ localState: context.localState,
108
+ rootState: context.rootState,
109
+ rootSetState: context.rootSetState,
98
110
  content: context.content,
99
111
  context: context.context,
100
- setState: context.setState,
101
112
  registeredComponents: context.registeredComponents,
102
113
  inheritedStyles: getInheritedTextStyles(),
103
114
  };
@@ -128,12 +139,7 @@ let component = getComponent({
128
139
  block: block,
129
140
  context: context,
130
141
  });
131
- let repeatItemData = getRepeatItemData({
132
- block: block,
133
- context: context,
134
- });
135
142
  let tag = block.tagName || "div";
136
- let proxyState = getProxyState(context);
137
143
  </script>
138
144
 
139
145
  {#if canShowBlock()}
@@ -142,13 +148,13 @@ let proxyState = getProxyState(context);
142
148
  <svelte:element this={tag} {...attributes()} use:setAttrs={actions()} />
143
149
  {/if}
144
150
 
145
- {#if !isEmptyHtmlElement(tag) && repeatItemData}
146
- {#each repeatItemData as data, index (index)}
151
+ {#if !isEmptyHtmlElement(tag) && repeatItem()}
152
+ {#each repeatItem() as data, index (index)}
147
153
  <RenderRepeatedBlock repeatContext={data.context} block={data.block} />
148
154
  {/each}
149
155
  {/if}
150
156
 
151
- {#if !isEmptyHtmlElement(tag) && !repeatItemData}
157
+ {#if !isEmptyHtmlElement(tag) && !repeatItem()}
152
158
  <svelte:element this={tag} {...attributes()} use:setAttrs={actions()}>
153
159
  <RenderComponent {...renderComponentProps()} />
154
160
 
@@ -16,8 +16,9 @@ export let block;
16
16
  export let repeatContext;
17
17
  setContext(BuilderContext.key, {
18
18
  content: repeatContext.content,
19
- state: repeatContext.state,
20
- setState: repeatContext.setState,
19
+ localState: repeatContext.localState,
20
+ rootState: repeatContext.rootState,
21
+ rootSetState: repeatContext.rootSetState,
21
22
  context: repeatContext.context,
22
23
  apiKey: repeatContext.apiKey,
23
24
  registeredComponents: repeatContext.registeredComponents,
@@ -51,8 +51,8 @@ function setBreakpoints(breakpoints) {
51
51
  },
52
52
  };
53
53
  }
54
- function setContextState(newState) {
55
- contentState = newState;
54
+ function contentSetState(newRootState) {
55
+ contentState = newRootState;
56
56
  }
57
57
  function processMessage(event) {
58
58
  const { data } = event;
@@ -97,7 +97,9 @@ function evaluateJsCode() {
97
97
  evaluate({
98
98
  code: jsCode,
99
99
  context: context || {},
100
- state: contentState,
100
+ localState: undefined,
101
+ rootState: contentState,
102
+ rootSetState: contentSetState,
101
103
  });
102
104
  }
103
105
  }
@@ -123,7 +125,9 @@ function evalExpression(expression) {
123
125
  return expression.replace(/{{([^}]+)}}/g, (_match, group) => evaluate({
124
126
  code: group,
125
127
  context: context || {},
126
- state: contentState,
128
+ localState: undefined,
129
+ rootState: contentState,
130
+ rootSetState: contentSetState,
127
131
  }));
128
132
  }
129
133
  function handleRequest({ url, key }) {
@@ -134,7 +138,7 @@ function handleRequest({ url, key }) {
134
138
  ...contentState,
135
139
  [key]: json,
136
140
  };
137
- setContextState(newState);
141
+ contentSetState(newState);
138
142
  })
139
143
  .catch((err) => {
140
144
  console.error("error fetching dynamic data", url, err);
@@ -287,11 +291,14 @@ setContext(builderContext.key, {
287
291
  get content() {
288
292
  return useContent;
289
293
  },
290
- get state() {
294
+ get localState() {
295
+ return undefined;
296
+ },
297
+ get rootState() {
291
298
  return contentState;
292
299
  },
293
- get setState() {
294
- return setContextState;
300
+ get rootSetState() {
301
+ return TARGET === "qwik" ? undefined : contentSetState;
295
302
  },
296
303
  get context() {
297
304
  return context || {};
@@ -3,9 +3,9 @@
3
3
  <script>import { TARGET } from "../constants/target.js";
4
4
  export let styles;
5
5
  $: tag = () => {
6
- // NOTE: we have to obfusctate the name of the tag due to a limitation in the svelte-preprocessor plugin.
6
+ // NOTE: we have to obfuscate the name of the tag due to a limitation in the svelte-preprocessor plugin.
7
7
  // https://github.com/sveltejs/vite-plugin-svelte/issues/315#issuecomment-1109000027
8
- return "sty" + "le";
8
+ return ("sty" + "le");
9
9
  };
10
10
  $: injectedStyleScript = () => {
11
11
  return `<${tag()}>${styles}</${tag()}>`;
@@ -0,0 +1 @@
1
+ export declare const SDK_VERSION = "0.3.1";
@@ -0,0 +1 @@
1
+ export const SDK_VERSION = "0.3.1";
@@ -2,8 +2,9 @@ declare const _default: {
2
2
  Builder: {
3
3
  content: null;
4
4
  context: {};
5
- state: {};
6
- setState(): void;
5
+ localState: undefined;
6
+ rootSetState(): void;
7
+ rootState: {};
7
8
  apiKey: null;
8
9
  apiVersion: undefined;
9
10
  registeredComponents: {};
@@ -3,8 +3,9 @@ export default {
3
3
  Builder: {
4
4
  content: null,
5
5
  context: {},
6
- state: {},
7
- setState() { },
6
+ localState: undefined,
7
+ rootSetState() { },
8
+ rootState: {},
8
9
  apiKey: null,
9
10
  apiVersion: undefined,
10
11
  registeredComponents: {},
@@ -11,8 +11,23 @@ export type BuilderRenderContext = Record<string, unknown>;
11
11
  export interface BuilderContextInterface {
12
12
  content: Nullable<BuilderContent>;
13
13
  context: BuilderRenderContext;
14
- state: BuilderRenderState;
15
- setState?: (state: BuilderRenderState) => void;
14
+ /**
15
+ * The state of the application.
16
+ *
17
+ * NOTE: see `localState` below to understand how it is different from `rootState`.
18
+ */
19
+ rootState: BuilderRenderState;
20
+ /**
21
+ * Some frameworks have a `setState` function which needs to be invoked to notify
22
+ * the framework of state change. (other frameworks don't in which case it is `undefined')
23
+ */
24
+ rootSetState: ((rootState: BuilderRenderState) => void) | undefined;
25
+ /**
26
+ * The local state of the current component. This is different from `rootState` in that
27
+ * it can be a child state created by a repeater containing local state.
28
+ * The `rootState` is where all of the state mutations are actually stored.
29
+ */
30
+ localState: BuilderRenderState | undefined;
16
31
  apiKey: string | null;
17
32
  apiVersion: ApiVersion | undefined;
18
33
  registeredComponents: RegisteredComponents;
@@ -1,6 +1,7 @@
1
- import type { BuilderContextInterface } from '../context/types.js';
2
- export declare function evaluate({ code, context, state, event, isExpression, }: {
1
+ import type { BuilderContextInterface, BuilderRenderState } from '../context/types.js';
2
+ export declare function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression, }: {
3
3
  code: string;
4
4
  event?: Event;
5
5
  isExpression?: boolean;
6
- } & Pick<BuilderContextInterface, 'state' | 'context'>): any;
6
+ } & Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'>): any;
7
+ export declare function flattenState(rootState: Record<string | symbol, any>, localState: Record<string | symbol, any> | undefined, rootSetState: ((rootState: BuilderRenderState) => void) | undefined): BuilderRenderState;
@@ -1,6 +1,6 @@
1
1
  import { isBrowser } from './is-browser.js';
2
2
  import { isEditing } from './is-editing.js';
3
- export function evaluate({ code, context, state, event, isExpression = true, }) {
3
+ export function evaluate({ code, context, localState, rootState, rootSetState, event, isExpression = true, }) {
4
4
  if (code === '') {
5
5
  console.warn('Skipping evaluation of empty code block.');
6
6
  return;
@@ -20,9 +20,30 @@ export function evaluate({ code, context, state, event, isExpression = true, })
20
20
  code.trim().startsWith('return '));
21
21
  const useCode = useReturn ? `return (${code});` : code;
22
22
  try {
23
- return new Function('builder', 'Builder' /* <- legacy */, 'state', 'context', 'event', useCode)(builder, builder, state, context, event);
23
+ return new Function('builder', 'Builder' /* <- legacy */, 'state', 'context', 'event', useCode)(builder, builder, flattenState(rootState, localState, rootSetState), context, event);
24
24
  }
25
25
  catch (e) {
26
26
  console.warn('Builder custom code error: \n While Evaluating: \n ', useCode, '\n', e);
27
27
  }
28
28
  }
29
+ export function flattenState(rootState, localState, rootSetState) {
30
+ if (rootState === localState) {
31
+ throw new Error('rootState === localState');
32
+ }
33
+ return new Proxy(rootState, {
34
+ get: (_, prop) => {
35
+ if (localState && prop in localState) {
36
+ return localState[prop];
37
+ }
38
+ return rootState[prop];
39
+ },
40
+ set: (_, prop, value) => {
41
+ if (localState && prop in localState) {
42
+ throw new Error('Writing to local state is not allowed as it is read-only.');
43
+ }
44
+ rootState[prop] = value;
45
+ rootSetState?.(rootState);
46
+ return true;
47
+ },
48
+ });
49
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,17 @@
1
+ import { flattenState } from './evaluate';
2
+ describe('flatten state', () => {
3
+ it('should behave normally when no PROTO_STATE', () => {
4
+ const localState = {};
5
+ const rootState = { foo: 'bar' };
6
+ const flattened = flattenState(rootState, localState, undefined);
7
+ expect(flattened.foo).toEqual('bar');
8
+ flattened.foo = 'baz';
9
+ expect(rootState.foo).toEqual('baz');
10
+ });
11
+ it('should shadow write ', () => {
12
+ const rootState = { foo: 'foo' };
13
+ const localState = { foo: 'baz' };
14
+ const flattened = flattenState(rootState, localState, undefined);
15
+ expect(() => (flattened.foo = 'bar')).toThrow('Writing to local state is not allowed as it is read-only.');
16
+ });
17
+ });
@@ -2,7 +2,7 @@ import type { BuilderContextInterface } from '../context/types.js';
2
2
  import type { BuilderBlock } from '../types/builder-block.js';
3
3
  type Options = {
4
4
  block: BuilderBlock;
5
- } & Pick<BuilderContextInterface, 'state' | 'context'>;
5
+ } & Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'>;
6
6
  type EventHandler = (event: Event) => any;
7
7
  export declare const createEventHandler: (value: string, options: Options) => EventHandler;
8
8
  export {};
@@ -2,7 +2,9 @@ import { evaluate } from './evaluate.js';
2
2
  export const createEventHandler = (value, options) => (event) => evaluate({
3
3
  code: value,
4
4
  context: options.context,
5
- state: options.state,
5
+ localState: options.localState,
6
+ rootState: options.rootState,
7
+ rootSetState: options.rootSetState,
6
8
  event,
7
9
  isExpression: false,
8
10
  });
@@ -5,5 +5,5 @@ type Actions = {
5
5
  };
6
6
  export declare function getBlockActions(options: {
7
7
  block: BuilderBlock;
8
- } & Pick<BuilderContextInterface, 'state' | 'context'>): Actions;
8
+ } & Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'>): Actions;
9
9
  export {};
@@ -1,10 +1,10 @@
1
1
  import type { BuilderContextInterface } from '../context/types.js';
2
2
  import type { BuilderBlock } from '../types/builder-block.js';
3
- export declare function getProcessedBlock({ block, context, shouldEvaluateBindings, state, }: {
3
+ export declare function getProcessedBlock({ block, context, shouldEvaluateBindings, localState, rootState, rootSetState, }: {
4
4
  block: BuilderBlock;
5
5
  /**
6
6
  * In some cases, we want to avoid evaluating bindings and only want framework-specific block transformation. It is
7
7
  * also sometimes too early to consider bindings, e.g. when we might be looking at a repeated block.
8
8
  */
9
9
  shouldEvaluateBindings: boolean;
10
- } & Pick<BuilderContextInterface, 'state' | 'context'>): BuilderBlock;
10
+ } & Pick<BuilderContextInterface, 'localState' | 'context' | 'rootState' | 'rootSetState'>): BuilderBlock;
@@ -2,7 +2,7 @@ import { evaluate } from './evaluate.js';
2
2
  import { fastClone } from './fast-clone.js';
3
3
  import { set } from './set.js';
4
4
  import { transformBlock } from './transform-block.js';
5
- const evaluateBindings = ({ block, context, state, }) => {
5
+ const evaluateBindings = ({ block, context, localState, rootState, rootSetState, }) => {
6
6
  if (!block.bindings) {
7
7
  return block;
8
8
  }
@@ -14,15 +14,27 @@ const evaluateBindings = ({ block, context, state, }) => {
14
14
  };
15
15
  for (const binding in block.bindings) {
16
16
  const expression = block.bindings[binding];
17
- const value = evaluate({ code: expression, state, context });
17
+ const value = evaluate({
18
+ code: expression,
19
+ localState,
20
+ rootState,
21
+ rootSetState,
22
+ context,
23
+ });
18
24
  set(copied, binding, value);
19
25
  }
20
26
  return copied;
21
27
  };
22
- export function getProcessedBlock({ block, context, shouldEvaluateBindings, state, }) {
28
+ export function getProcessedBlock({ block, context, shouldEvaluateBindings, localState, rootState, rootSetState, }) {
23
29
  const transformedBlock = transformBlock(block);
24
30
  if (shouldEvaluateBindings) {
25
- return evaluateBindings({ block: transformedBlock, state, context });
31
+ return evaluateBindings({
32
+ block: transformedBlock,
33
+ localState,
34
+ rootState,
35
+ rootSetState,
36
+ context,
37
+ });
26
38
  }
27
39
  else {
28
40
  return transformedBlock;
@@ -19,7 +19,9 @@ test('Can process bindings', () => {
19
19
  const processed = getProcessedBlock({
20
20
  block,
21
21
  context: {},
22
- state: { test: 'hello' },
22
+ rootState: { test: 'hello' },
23
+ rootSetState: undefined,
24
+ localState: undefined,
23
25
  shouldEvaluateBindings: true,
24
26
  });
25
27
  expect(processed).not.toEqual(block);
@@ -1,3 +1,4 @@
1
+ import { SDK_VERSION } from '../constants/sdk-version.js';
1
2
  import { TARGET } from '../constants/target.js';
2
3
  import { isBrowser } from '../functions/is-browser.js';
3
4
  import { register } from '../functions/register.js';
@@ -32,6 +33,7 @@ export const setupBrowserForEditing = (options = {}) => {
32
33
  type: 'builder.sdkInfo',
33
34
  data: {
34
35
  target: TARGET,
36
+ version: SDK_VERSION,
35
37
  // TODO: compile these in
36
38
  // type: process.env.SDK_TYPE,
37
39
  // version: process.env.SDK_VERSION,
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": "0.2.3",
4
+ "version": "0.3.1",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "package",
@@ -28,9 +28,6 @@
28
28
  },
29
29
  "types": "./package/index.d.ts",
30
30
  "scripts": {
31
- "release:patch": "yarn run build && npm version patch && npm publish",
32
- "release:minor": "yarn run build && npm version minor && npm publish",
33
- "release:dev": "yarn run build && npm version prerelease && npm publish --tag dev",
34
31
  "dev": "vite dev",
35
32
  "build": "svelte-kit sync && svelte-package",
36
33
  "build:watch": "svelte-kit sync && svelte-package --watch",
@@ -11,12 +11,8 @@
11
11
  export interface ColumnProps {
12
12
  columns?: Column[];
13
13
  builderBlock: BuilderBlock;
14
-
15
- // TODO: Implement this when support for dynamic CSS lands
16
14
  space?: number;
17
- // TODO: Implement this when support for dynamic CSS lands
18
15
  stackColumnsAt?: StackColumnsAt;
19
- // TODO: Implement this when support for dynamic CSS lands
20
16
  reverseColumnsWhenStacked?: boolean;
21
17
  }
22
18
  </script>
@@ -84,7 +80,7 @@
84
80
  } as any as Dictionary<string>;
85
81
  }
86
82
  const width = getColumnCssWidth(index);
87
- const gutterPixels = `${gutterSize}px`;
83
+ const gutterPixels = `${gutter}px`;
88
84
  const mobileWidth = "100%";
89
85
  const mobileMarginLeft = 0;
90
86
  return {
@@ -136,7 +136,7 @@
136
136
  customComponents={Object.values(builderContext.registeredComponents)}
137
137
  data={{
138
138
  ...symbol?.data,
139
- ...builderContext.state,
139
+ ...builderContext.localState,
140
140
  ...contentToUse?.data?.state,
141
141
  }}
142
142
  model={symbol?.model}
@@ -24,7 +24,9 @@
24
24
  $: useBlock = () => {
25
25
  return getProcessedBlock({
26
26
  block: block,
27
- state: context.state,
27
+ localState: context.localState,
28
+ rootState: context.rootState,
29
+ rootSetState: context.rootSetState,
28
30
  context: context.context,
29
31
  shouldEvaluateBindings: true,
30
32
  });
@@ -1,7 +1,4 @@
1
- import type {
2
- BuilderContextInterface,
3
- BuilderRenderState,
4
- } from '../../context/types';
1
+ import type { BuilderContextInterface } from '../../context/types';
5
2
  import { evaluate } from '../../functions/evaluate';
6
3
  import { getProcessedBlock } from '../../functions/get-processed-block';
7
4
  import type { BuilderBlock } from '../../types/builder-block';
@@ -44,7 +41,9 @@ export const getComponent = ({
44
41
  }) => {
45
42
  const componentName = getProcessedBlock({
46
43
  block,
47
- state: context.state,
44
+ localState: context.localState,
45
+ rootState: context.rootState,
46
+ rootSetState: context.rootSetState,
48
47
  context: context.context,
49
48
  shouldEvaluateBindings: false,
50
49
  }).component?.name;
@@ -85,7 +84,9 @@ export const getRepeatItemData = ({
85
84
 
86
85
  const itemsArray = evaluate({
87
86
  code: repeat.collection,
88
- state: context.state,
87
+ localState: context.localState,
88
+ rootState: context.rootState,
89
+ rootSetState: context.rootSetState,
89
90
  context: context.context,
90
91
  });
91
92
 
@@ -100,8 +101,8 @@ export const getRepeatItemData = ({
100
101
  const repeatArray = itemsArray.map<RepeatData>((item, index) => ({
101
102
  context: {
102
103
  ...context,
103
- state: {
104
- ...context.state,
104
+ localState: {
105
+ ...context.localState,
105
106
  $index: index,
106
107
  $item: item,
107
108
  [itemNameToUse]: item,
@@ -113,26 +114,3 @@ export const getRepeatItemData = ({
113
114
 
114
115
  return repeatArray;
115
116
  };
116
-
117
- export const getProxyState = (
118
- context: BuilderContextInterface
119
- ): BuilderContextInterface['state'] => {
120
- if (typeof Proxy === 'undefined') {
121
- console.error(
122
- 'no Proxy available in this environment, cannot proxy state.'
123
- );
124
- return context.state;
125
- }
126
-
127
- const useState = new Proxy(context.state, {
128
- set: (obj, prop: keyof BuilderRenderState, value) => {
129
- // set the value on the state object, so that the event handler instantly gets the update.
130
- obj[prop] = value;
131
-
132
- // set the value in the context, so that the rest of the app gets the update.
133
- context.setState?.(obj);
134
- return true;
135
- },
136
- });
137
- return useState;
138
- };
@@ -15,7 +15,6 @@
15
15
  import BlockStyles from "./block-styles.svelte";
16
16
  import {
17
17
  getComponent,
18
- getProxyState,
19
18
  getRepeatItemData,
20
19
  isEmptyHtmlElement,
21
20
  } from "./render-block.helpers.js";
@@ -47,12 +46,20 @@
47
46
  export let block: RenderBlockProps["block"];
48
47
  export let context: RenderBlockProps["context"];
49
48
 
49
+ $: repeatItem = () => {
50
+ return getRepeatItemData({
51
+ block: block,
52
+ context: context,
53
+ });
54
+ };
50
55
  $: useBlock = () => {
51
- return repeatItemData
56
+ return repeatItem()
52
57
  ? block
53
58
  : getProcessedBlock({
54
59
  block: block,
55
- state: context.state,
60
+ localState: context.localState,
61
+ rootState: context.rootState,
62
+ rootSetState: context.rootSetState,
56
63
  context: context.context,
57
64
  shouldEvaluateBindings: true,
58
65
  });
@@ -69,7 +76,9 @@
69
76
  $: actions = () => {
70
77
  return getBlockActions({
71
78
  block: useBlock(),
72
- state: TARGET === "qwik" ? context.state : proxyState,
79
+ rootState: context.rootState,
80
+ rootSetState: context.rootSetState,
81
+ localState: context.localState,
73
82
  context: context.context,
74
83
  });
75
84
  };
@@ -96,7 +105,7 @@
96
105
  * blocks, and the children will be repeated within those blocks.
97
106
  */
98
107
  const shouldRenderChildrenOutsideRef =
99
- !component?.component && !repeatItemData;
108
+ !component?.component && !repeatItem();
100
109
  return shouldRenderChildrenOutsideRef ? useBlock().children ?? [] : [];
101
110
  };
102
111
  $: childrenContext = () => {
@@ -115,10 +124,11 @@
115
124
  return {
116
125
  apiKey: context.apiKey,
117
126
  apiVersion: context.apiVersion,
118
- state: context.state,
127
+ localState: context.localState,
128
+ rootState: context.rootState,
129
+ rootSetState: context.rootSetState,
119
130
  content: context.content,
120
131
  context: context.context,
121
- setState: context.setState,
122
132
  registeredComponents: context.registeredComponents,
123
133
  inheritedStyles: getInheritedTextStyles(),
124
134
  };
@@ -150,12 +160,7 @@
150
160
  block: block,
151
161
  context: context,
152
162
  });
153
- let repeatItemData = getRepeatItemData({
154
- block: block,
155
- context: context,
156
- });
157
163
  let tag = block.tagName || "div";
158
- let proxyState = getProxyState(context);
159
164
  </script>
160
165
 
161
166
  {#if canShowBlock()}
@@ -164,13 +169,13 @@
164
169
  <svelte:element this={tag} {...attributes()} use:setAttrs={actions()} />
165
170
  {/if}
166
171
 
167
- {#if !isEmptyHtmlElement(tag) && repeatItemData}
168
- {#each repeatItemData as data, index (index)}
172
+ {#if !isEmptyHtmlElement(tag) && repeatItem()}
173
+ {#each repeatItem() as data, index (index)}
169
174
  <RenderRepeatedBlock repeatContext={data.context} block={data.block} />
170
175
  {/each}
171
176
  {/if}
172
177
 
173
- {#if !isEmptyHtmlElement(tag) && !repeatItemData}
178
+ {#if !isEmptyHtmlElement(tag) && !repeatItem()}
174
179
  <svelte:element this={tag} {...attributes()} use:setAttrs={actions()}>
175
180
  <RenderComponent {...renderComponentProps()} />
176
181
 
@@ -28,8 +28,9 @@
28
28
 
29
29
  setContext(BuilderContext.key, {
30
30
  content: repeatContext.content,
31
- state: repeatContext.state,
32
- setState: repeatContext.setState,
31
+ localState: repeatContext.localState,
32
+ rootState: repeatContext.rootState,
33
+ rootSetState: repeatContext.rootSetState,
33
34
  context: repeatContext.context,
34
35
  apiKey: repeatContext.apiKey,
35
36
  registeredComponents: repeatContext.registeredComponents,
@@ -79,8 +79,8 @@
79
79
  },
80
80
  };
81
81
  }
82
- function setContextState(newState: BuilderRenderState) {
83
- contentState = newState;
82
+ function contentSetState(newRootState: BuilderRenderState) {
83
+ contentState = newRootState;
84
84
  }
85
85
  function processMessage(event: MessageEvent) {
86
86
  const { data } = event;
@@ -127,7 +127,9 @@
127
127
  evaluate({
128
128
  code: jsCode,
129
129
  context: context || {},
130
- state: contentState,
130
+ localState: undefined,
131
+ rootState: contentState,
132
+ rootSetState: contentSetState,
131
133
  });
132
134
  }
133
135
  }
@@ -154,7 +156,9 @@
154
156
  evaluate({
155
157
  code: group,
156
158
  context: context || {},
157
- state: contentState,
159
+ localState: undefined,
160
+ rootState: contentState,
161
+ rootSetState: contentSetState,
158
162
  })
159
163
  );
160
164
  }
@@ -166,7 +170,7 @@
166
170
  ...contentState,
167
171
  [key]: json,
168
172
  };
169
- setContextState(newState);
173
+ contentSetState(newState);
170
174
  })
171
175
  .catch((err) => {
172
176
  console.error("error fetching dynamic data", url, err);
@@ -348,11 +352,14 @@
348
352
  get content() {
349
353
  return useContent;
350
354
  },
351
- get state() {
355
+ get localState() {
356
+ return undefined;
357
+ },
358
+ get rootState() {
352
359
  return contentState;
353
360
  },
354
- get setState() {
355
- return setContextState;
361
+ get rootSetState() {
362
+ return TARGET === "qwik" ? undefined : contentSetState;
356
363
  },
357
364
  get context() {
358
365
  return context || {};
@@ -10,9 +10,9 @@
10
10
  export let styles: Props["styles"];
11
11
 
12
12
  $: tag = () => {
13
- // NOTE: we have to obfusctate the name of the tag due to a limitation in the svelte-preprocessor plugin.
13
+ // NOTE: we have to obfuscate the name of the tag due to a limitation in the svelte-preprocessor plugin.
14
14
  // https://github.com/sveltejs/vite-plugin-svelte/issues/315#issuecomment-1109000027
15
- return "sty" + "le";
15
+ return ("sty" + "le") as any;
16
16
  };
17
17
  $: injectedStyleScript = () => {
18
18
  return `<${tag()}>${styles}</${tag()}>`;
@@ -0,0 +1 @@
1
+ export const SDK_VERSION = "0.3.1"
@@ -4,8 +4,9 @@ export default {
4
4
  Builder: {
5
5
  content: null,
6
6
  context: {},
7
- state: {},
8
- setState() {},
7
+ localState: undefined,
8
+ rootSetState() {},
9
+ rootState: {},
9
10
  apiKey: null,
10
11
  apiVersion: undefined,
11
12
  registeredComponents: {},
@@ -16,8 +16,23 @@ export type BuilderRenderContext = Record<string, unknown>;
16
16
  export interface BuilderContextInterface {
17
17
  content: Nullable<BuilderContent>;
18
18
  context: BuilderRenderContext;
19
- state: BuilderRenderState;
20
- setState?: (state: BuilderRenderState) => void;
19
+ /**
20
+ * The state of the application.
21
+ *
22
+ * NOTE: see `localState` below to understand how it is different from `rootState`.
23
+ */
24
+ rootState: BuilderRenderState;
25
+ /**
26
+ * Some frameworks have a `setState` function which needs to be invoked to notify
27
+ * the framework of state change. (other frameworks don't in which case it is `undefined')
28
+ */
29
+ rootSetState: ((rootState: BuilderRenderState) => void) | undefined;
30
+ /**
31
+ * The local state of the current component. This is different from `rootState` in that
32
+ * it can be a child state created by a repeater containing local state.
33
+ * The `rootState` is where all of the state mutations are actually stored.
34
+ */
35
+ localState: BuilderRenderState | undefined;
21
36
  apiKey: string | null;
22
37
  apiVersion: ApiVersion | undefined;
23
38
  registeredComponents: RegisteredComponents;
@@ -0,0 +1,21 @@
1
+ import { flattenState } from './evaluate';
2
+
3
+ describe('flatten state', () => {
4
+ it('should behave normally when no PROTO_STATE', () => {
5
+ const localState = {};
6
+ const rootState = { foo: 'bar' };
7
+ const flattened = flattenState(rootState, localState, undefined);
8
+ expect(flattened.foo).toEqual('bar');
9
+ flattened.foo = 'baz';
10
+ expect(rootState.foo).toEqual('baz');
11
+ });
12
+
13
+ it('should shadow write ', () => {
14
+ const rootState = { foo: 'foo' };
15
+ const localState = { foo: 'baz' };
16
+ const flattened = flattenState(rootState, localState, undefined);
17
+ expect(() => (flattened.foo = 'bar')).toThrow(
18
+ 'Writing to local state is not allowed as it is read-only.'
19
+ );
20
+ });
21
+ });
@@ -1,18 +1,26 @@
1
- import type { BuilderContextInterface } from '../context/types.js';
1
+ import type {
2
+ BuilderContextInterface,
3
+ BuilderRenderState,
4
+ } from '../context/types.js';
2
5
  import { isBrowser } from './is-browser.js';
3
6
  import { isEditing } from './is-editing.js';
4
7
 
5
8
  export function evaluate({
6
9
  code,
7
10
  context,
8
- state,
11
+ localState,
12
+ rootState,
13
+ rootSetState,
9
14
  event,
10
15
  isExpression = true,
11
16
  }: {
12
17
  code: string;
13
18
  event?: Event;
14
19
  isExpression?: boolean;
15
- } & Pick<BuilderContextInterface, 'state' | 'context'>): any {
20
+ } & Pick<
21
+ BuilderContextInterface,
22
+ 'localState' | 'context' | 'rootState' | 'rootSetState'
23
+ >): any {
16
24
  if (code === '') {
17
25
  console.warn('Skipping evaluation of empty code block.');
18
26
  return;
@@ -45,7 +53,13 @@ export function evaluate({
45
53
  'context',
46
54
  'event',
47
55
  useCode
48
- )(builder, builder, state, context, event);
56
+ )(
57
+ builder,
58
+ builder,
59
+ flattenState(rootState, localState, rootSetState),
60
+ context,
61
+ event
62
+ );
49
63
  } catch (e) {
50
64
  console.warn(
51
65
  'Builder custom code error: \n While Evaluating: \n ',
@@ -55,3 +69,31 @@ export function evaluate({
55
69
  );
56
70
  }
57
71
  }
72
+
73
+ export function flattenState(
74
+ rootState: Record<string | symbol, any>,
75
+ localState: Record<string | symbol, any> | undefined,
76
+ rootSetState: ((rootState: BuilderRenderState) => void) | undefined
77
+ ): BuilderRenderState {
78
+ if (rootState === localState) {
79
+ throw new Error('rootState === localState');
80
+ }
81
+ return new Proxy(rootState, {
82
+ get: (_, prop) => {
83
+ if (localState && prop in localState) {
84
+ return localState[prop];
85
+ }
86
+ return rootState[prop as string];
87
+ },
88
+ set: (_, prop, value) => {
89
+ if (localState && prop in localState) {
90
+ throw new Error(
91
+ 'Writing to local state is not allowed as it is read-only.'
92
+ );
93
+ }
94
+ rootState[prop as string] = value;
95
+ rootSetState?.(rootState);
96
+ return true;
97
+ },
98
+ });
99
+ }
@@ -4,7 +4,10 @@ import { evaluate } from './evaluate.js';
4
4
 
5
5
  type Options = {
6
6
  block: BuilderBlock;
7
- } & Pick<BuilderContextInterface, 'state' | 'context'>;
7
+ } & Pick<
8
+ BuilderContextInterface,
9
+ 'localState' | 'context' | 'rootState' | 'rootSetState'
10
+ >;
8
11
 
9
12
  type EventHandler = (event: Event) => any;
10
13
 
@@ -14,7 +17,9 @@ export const createEventHandler =
14
17
  evaluate({
15
18
  code: value,
16
19
  context: options.context,
17
- state: options.state,
20
+ localState: options.localState,
21
+ rootState: options.rootState,
22
+ rootSetState: options.rootSetState,
18
23
  event,
19
24
  isExpression: false,
20
25
  });
@@ -8,7 +8,10 @@ type Actions = { [index: string]: (event: Event) => any };
8
8
  export function getBlockActions(
9
9
  options: {
10
10
  block: BuilderBlock;
11
- } & Pick<BuilderContextInterface, 'state' | 'context'>
11
+ } & Pick<
12
+ BuilderContextInterface,
13
+ 'localState' | 'context' | 'rootState' | 'rootSetState'
14
+ >
12
15
  ): Actions {
13
16
  const obj: Actions = {};
14
17
  const optionActions = options.block.actions ?? {};
@@ -21,7 +21,9 @@ test('Can process bindings', () => {
21
21
  const processed = getProcessedBlock({
22
22
  block,
23
23
  context: {},
24
- state: { test: 'hello' },
24
+ rootState: { test: 'hello' },
25
+ rootSetState: undefined,
26
+ localState: undefined,
25
27
  shouldEvaluateBindings: true,
26
28
  });
27
29
  expect(processed).not.toEqual(block);
@@ -8,10 +8,15 @@ import { transformBlock } from './transform-block.js';
8
8
  const evaluateBindings = ({
9
9
  block,
10
10
  context,
11
- state,
11
+ localState,
12
+ rootState,
13
+ rootSetState,
12
14
  }: {
13
15
  block: BuilderBlock;
14
- } & Pick<BuilderContextInterface, 'state' | 'context'>): BuilderBlock => {
16
+ } & Pick<
17
+ BuilderContextInterface,
18
+ 'localState' | 'context' | 'rootState' | 'rootSetState'
19
+ >): BuilderBlock => {
15
20
  if (!block.bindings) {
16
21
  return block;
17
22
  }
@@ -24,7 +29,13 @@ const evaluateBindings = ({
24
29
 
25
30
  for (const binding in block.bindings) {
26
31
  const expression = block.bindings[binding];
27
- const value = evaluate({ code: expression, state, context });
32
+ const value = evaluate({
33
+ code: expression,
34
+ localState,
35
+ rootState,
36
+ rootSetState,
37
+ context,
38
+ });
28
39
  set(copied, binding, value);
29
40
  }
30
41
 
@@ -35,7 +46,9 @@ export function getProcessedBlock({
35
46
  block,
36
47
  context,
37
48
  shouldEvaluateBindings,
38
- state,
49
+ localState,
50
+ rootState,
51
+ rootSetState,
39
52
  }: {
40
53
  block: BuilderBlock;
41
54
  /**
@@ -43,11 +56,20 @@ export function getProcessedBlock({
43
56
  * also sometimes too early to consider bindings, e.g. when we might be looking at a repeated block.
44
57
  */
45
58
  shouldEvaluateBindings: boolean;
46
- } & Pick<BuilderContextInterface, 'state' | 'context'>): BuilderBlock {
59
+ } & Pick<
60
+ BuilderContextInterface,
61
+ 'localState' | 'context' | 'rootState' | 'rootSetState'
62
+ >): BuilderBlock {
47
63
  const transformedBlock = transformBlock(block);
48
64
 
49
65
  if (shouldEvaluateBindings) {
50
- return evaluateBindings({ block: transformedBlock, state, context });
66
+ return evaluateBindings({
67
+ block: transformedBlock,
68
+ localState,
69
+ rootState,
70
+ rootSetState,
71
+ context,
72
+ });
51
73
  } else {
52
74
  return transformedBlock;
53
75
  }
@@ -1,3 +1,4 @@
1
+ import { SDK_VERSION } from '../constants/sdk-version.js';
1
2
  import { TARGET } from '../constants/target.js';
2
3
  import { isBrowser } from '../functions/is-browser.js';
3
4
  import { register } from '../functions/register.js';
@@ -40,6 +41,7 @@ export const setupBrowserForEditing = (
40
41
  type: 'builder.sdkInfo',
41
42
  data: {
42
43
  target: TARGET,
44
+ version: SDK_VERSION,
43
45
  // TODO: compile these in
44
46
  // type: process.env.SDK_TYPE,
45
47
  // version: process.env.SDK_VERSION,