@builder.io/sdk-svelte 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/package/--package.json +3 -2
  2. package/package/components/render-content/render-content.svelte +13 -7
  3. package/package/functions/track.d.ts +35 -5
  4. package/package/functions/track.js +5 -4
  5. package/package/index.d.ts +1 -0
  6. package/package/index.js +1 -0
  7. package/package.json +3 -2
  8. package/src/blocks/BaseText.svelte +27 -0
  9. package/src/blocks/button/button.svelte +36 -0
  10. package/src/blocks/button/component-info.ts +42 -0
  11. package/src/blocks/columns/columns.svelte +148 -0
  12. package/src/blocks/columns/component-info.ts +254 -0
  13. package/src/blocks/custom-code/component-info.ts +31 -0
  14. package/src/blocks/custom-code/custom-code.svelte +66 -0
  15. package/src/blocks/embed/component-info.ts +53 -0
  16. package/src/blocks/embed/embed.svelte +51 -0
  17. package/src/blocks/embed/helpers.ts +8 -0
  18. package/src/blocks/form/component-info.ts +282 -0
  19. package/src/blocks/form/form.svelte +332 -0
  20. package/src/blocks/fragment/component-info.ts +10 -0
  21. package/src/blocks/fragment/fragment.svelte +12 -0
  22. package/src/blocks/image/component-info.ts +170 -0
  23. package/src/blocks/image/image.helpers.ts +75 -0
  24. package/src/blocks/image/image.svelte +145 -0
  25. package/src/blocks/img/component-info.ts +21 -0
  26. package/src/blocks/img/img.svelte +51 -0
  27. package/src/blocks/input/component-info.ts +78 -0
  28. package/src/blocks/input/input.svelte +34 -0
  29. package/src/blocks/raw-text/component-info.ts +15 -0
  30. package/src/blocks/raw-text/raw-text.svelte +16 -0
  31. package/src/blocks/section/component-info.ts +50 -0
  32. package/src/blocks/section/section.svelte +34 -0
  33. package/src/blocks/select/component-info.ts +61 -0
  34. package/src/blocks/select/select.svelte +34 -0
  35. package/src/blocks/submit-button/component-info.ts +32 -0
  36. package/src/blocks/submit-button/submit-button.svelte +15 -0
  37. package/src/blocks/symbol/component-info.ts +43 -0
  38. package/src/blocks/symbol/symbol.svelte +100 -0
  39. package/src/blocks/text/component-info.ts +24 -0
  40. package/src/blocks/text/text.svelte +11 -0
  41. package/src/blocks/textarea/component-info.ts +48 -0
  42. package/src/blocks/textarea/textarea.svelte +19 -0
  43. package/src/blocks/util.ts +6 -0
  44. package/src/blocks/video/component-info.ts +109 -0
  45. package/src/blocks/video/video.svelte +102 -0
  46. package/src/components/render-block/block-styles.svelte +62 -0
  47. package/src/components/render-block/render-block.helpers.ts +27 -0
  48. package/src/components/render-block/render-block.svelte +228 -0
  49. package/src/components/render-block/render-component-with-context.svelte +38 -0
  50. package/src/components/render-block/render-component.svelte +35 -0
  51. package/src/components/render-block/render-repeated-block.svelte +53 -0
  52. package/src/components/render-block/types.ts +7 -0
  53. package/src/components/render-blocks.svelte +104 -0
  54. package/src/components/render-content/components/render-styles.svelte +80 -0
  55. package/src/components/render-content/index.ts +1 -0
  56. package/src/components/render-content/render-content.svelte +360 -0
  57. package/src/components/render-inlined-styles.svelte +26 -0
  58. package/src/constants/builder-registered-components.ts +42 -0
  59. package/src/constants/device-sizes.ts +28 -0
  60. package/src/constants/target.ts +2 -0
  61. package/src/context/builder.context.ts +13 -0
  62. package/src/context/types.ts +23 -0
  63. package/src/functions/camel-to-kebab-case.ts +2 -0
  64. package/src/functions/evaluate.ts +52 -0
  65. package/src/functions/event-handler-name.ts +6 -0
  66. package/src/functions/extract-text-styles.ts +39 -0
  67. package/src/functions/fast-clone.ts +5 -0
  68. package/src/functions/get-block-actions-handler.ts +19 -0
  69. package/src/functions/get-block-actions.ts +25 -0
  70. package/src/functions/get-block-component-options.ts +12 -0
  71. package/src/functions/get-block-properties.ts +11 -0
  72. package/src/functions/get-block-tag.ts +7 -0
  73. package/src/functions/get-builder-search-params/fn.test.ts +19 -0
  74. package/src/functions/get-builder-search-params/index.ts +48 -0
  75. package/src/functions/get-content/ab-testing.ts +132 -0
  76. package/src/functions/get-content/fn.test.ts +35 -0
  77. package/src/functions/get-content/index.ts +77 -0
  78. package/src/functions/get-content/types.ts +36 -0
  79. package/src/functions/get-fetch.ts +14 -0
  80. package/src/functions/get-global-this.ts +15 -0
  81. package/src/functions/get-processed-block.test.ts +33 -0
  82. package/src/functions/get-processed-block.ts +54 -0
  83. package/src/functions/get-react-native-block-styles.ts +30 -0
  84. package/src/functions/if-target.ts +20 -0
  85. package/src/functions/is-browser.ts +3 -0
  86. package/src/functions/is-editing.ts +7 -0
  87. package/src/functions/is-iframe.ts +5 -0
  88. package/src/functions/is-previewing.ts +14 -0
  89. package/src/functions/mark-mutable.ts +7 -0
  90. package/src/functions/on-change.test.ts +23 -0
  91. package/src/functions/on-change.ts +29 -0
  92. package/src/functions/register-component.ts +67 -0
  93. package/src/functions/register.ts +46 -0
  94. package/src/functions/sanitize-react-native-block-styles.ts +65 -0
  95. package/src/functions/set-editor-settings.ts +16 -0
  96. package/src/functions/set.test.ts +19 -0
  97. package/src/functions/set.ts +26 -0
  98. package/src/functions/track.ts +124 -0
  99. package/src/functions/transform-block.ts +6 -0
  100. package/src/helpers/ab-tests.ts +22 -0
  101. package/src/helpers/cookie.ts +102 -0
  102. package/src/helpers/css.ts +33 -0
  103. package/src/helpers/flatten.ts +24 -0
  104. package/src/helpers/localStorage.ts +38 -0
  105. package/src/helpers/nullable.ts +4 -0
  106. package/src/helpers/sessionId.ts +34 -0
  107. package/src/helpers/time.ts +4 -0
  108. package/src/helpers/url.test.ts +16 -0
  109. package/src/helpers/url.ts +12 -0
  110. package/src/helpers/uuid.ts +17 -0
  111. package/src/helpers/visitorId.ts +38 -0
  112. package/src/index-helpers/blocks-exports.ts +10 -0
  113. package/src/index-helpers/top-of-file.ts +4 -0
  114. package/src/index.ts +13 -0
  115. package/src/scripts/init-editing.ts +102 -0
  116. package/src/types/builder-block.ts +58 -0
  117. package/src/types/builder-content.ts +39 -0
  118. package/src/types/can-track.ts +3 -0
  119. package/src/types/components.ts +188 -0
  120. package/src/types/deep-partial.ts +7 -0
  121. package/src/types/element.ts +60 -0
  122. package/src/types/targets.ts +6 -0
  123. package/src/types/typescript.ts +7 -0
@@ -0,0 +1,360 @@
1
+ <script context="module" lang="ts">
2
+ import type {
3
+ BuilderRenderContext,
4
+ BuilderRenderState,
5
+ RegisteredComponent,
6
+ RegisteredComponents,
7
+ } from "../../context/types.js";
8
+
9
+ import type { BuilderContent } from "../../types/builder-content.js";
10
+
11
+ import type { Dictionary, Nullable } from "../../types/typescript.js";
12
+
13
+ export type RenderContentProps = {
14
+ content?: Nullable<BuilderContent>;
15
+ model?: string;
16
+ data?: {
17
+ [key: string]: any;
18
+ };
19
+ context?: BuilderRenderContext;
20
+ apiKey: string;
21
+ customComponents?: RegisteredComponent[];
22
+ canTrack?: boolean;
23
+ };
24
+
25
+ interface BuilderComponentStateChange {
26
+ state: BuilderRenderState;
27
+ ref: {
28
+ name?: string;
29
+ props?: {
30
+ builderBlock?: {
31
+ id?: string;
32
+ };
33
+ };
34
+ };
35
+ }
36
+ </script>
37
+
38
+ <script lang="ts">
39
+ import {
40
+ afterUpdate,
41
+ getContext,
42
+ onDestroy,
43
+ onMount,
44
+ setContext,
45
+ } from "svelte";
46
+
47
+ import { getDefaultRegisteredComponents } from "../../constants/builder-registered-components.js";
48
+ import { TARGET } from "../../constants/target.js";
49
+ import BuilderContext from "../../context/builder.context.js";
50
+ import { evaluate } from "../../functions/evaluate.js";
51
+ import { getContent } from "../../functions/get-content/index.js";
52
+ import { getFetch } from "../../functions/get-fetch.js";
53
+ import { isBrowser } from "../../functions/is-browser.js";
54
+ import { isEditing } from "../../functions/is-editing.js";
55
+ import { isPreviewing } from "../../functions/is-previewing.js";
56
+ import {
57
+ components,
58
+ createRegisterComponentMessage,
59
+ } from "../../functions/register-component.js";
60
+ import { _track } from "../../functions/track.js";
61
+ import RenderBlocks from "../render-blocks.svelte";
62
+ import RenderContentStyles from "./components/render-styles.svelte";
63
+ import {
64
+ registerInsertMenu,
65
+ setupBrowserForEditing,
66
+ } from "../../scripts/init-editing.js";
67
+ import { markMutable } from "../../functions/mark-mutable.js";
68
+
69
+ export let content: RenderContentProps["content"];
70
+ export let data: RenderContentProps["data"];
71
+ export let canTrack: RenderContentProps["canTrack"];
72
+ export let context: RenderContentProps["context"];
73
+ export let customComponents: RenderContentProps["customComponents"];
74
+ export let model: RenderContentProps["model"];
75
+ export let apiKey: RenderContentProps["apiKey"];
76
+
77
+ setContext(BuilderContext.key, {
78
+ get content() {
79
+ return useContent();
80
+ },
81
+ get state() {
82
+ return contentState();
83
+ },
84
+ get context() {
85
+ return contextContext();
86
+ },
87
+ get apiKey() {
88
+ return apiKey;
89
+ },
90
+ get registeredComponents() {
91
+ return allRegisteredComponents();
92
+ },
93
+ });
94
+
95
+ function processMessage(event) {
96
+ const { data } = event;
97
+ if (data) {
98
+ switch (data.type) {
99
+ case "builder.contentUpdate": {
100
+ const messageContent = data.data;
101
+ const key =
102
+ messageContent.key ||
103
+ messageContent.alias ||
104
+ messageContent.entry ||
105
+ messageContent.modelName;
106
+ const contentData = messageContent.data;
107
+ if (key === model) {
108
+ overrideContent = contentData;
109
+ forceReRenderCount = forceReRenderCount + 1; // This is a hack to force Qwik to re-render.
110
+ }
111
+
112
+ break;
113
+ }
114
+ case "builder.patchUpdates": {
115
+ // TODO
116
+ break;
117
+ }
118
+ }
119
+ }
120
+ }
121
+ function evaluateJsCode() {
122
+ // run any dynamic JS code attached to content
123
+ const jsCode = useContent?.()?.data?.jsCode;
124
+ if (jsCode) {
125
+ evaluate({
126
+ code: jsCode,
127
+ context: contextContext(),
128
+ state: contentState(),
129
+ });
130
+ }
131
+ }
132
+ function onClick(_event) {
133
+ if (useContent()) {
134
+ const variationId = useContent?.()?.testVariationId;
135
+ const contentId = useContent?.()?.id;
136
+ _track({
137
+ type: "click",
138
+ canTrack: canTrackToUse(),
139
+ contentId,
140
+ apiKey: apiKey,
141
+ variationId: variationId !== contentId ? variationId : undefined,
142
+ });
143
+ }
144
+ }
145
+ function evalExpression(expression) {
146
+ return expression.replace(/{{([^}]+)}}/g, (_match, group) =>
147
+ evaluate({
148
+ code: group,
149
+ context: contextContext(),
150
+ state: contentState(),
151
+ })
152
+ );
153
+ }
154
+ function handleRequest({ url, key }) {
155
+ getFetch()
156
+ .then((fetch) => fetch(url))
157
+ .then((response) => response.json())
158
+ .then((json) => {
159
+ const newOverrideState = {
160
+ ...overrideState,
161
+ [key]: json,
162
+ };
163
+ overrideState = newOverrideState;
164
+ })
165
+ .catch((err) => {
166
+ console.log("error fetching dynamic data", url, err);
167
+ });
168
+ }
169
+ function runHttpRequests() {
170
+ const requests = useContent?.()?.data?.httpRequests ?? {};
171
+ Object.entries(requests).forEach(([key, url]) => {
172
+ if (url && (!httpReqsData()[key] || isEditing())) {
173
+ const evaluatedUrl = evalExpression(url);
174
+ handleRequest({
175
+ url: evaluatedUrl,
176
+ key,
177
+ });
178
+ }
179
+ });
180
+ }
181
+ function emitStateUpdate() {
182
+ if (isEditing()) {
183
+ window.dispatchEvent(
184
+ new CustomEvent("builder:component:stateChange", {
185
+ detail: {
186
+ state: contentState(),
187
+ ref: {
188
+ name: model,
189
+ },
190
+ },
191
+ })
192
+ );
193
+ }
194
+ }
195
+ $: useContent = () => {
196
+ if (!content && !overrideContent) {
197
+ return undefined;
198
+ }
199
+ const mergedContent = {
200
+ ...content,
201
+ ...overrideContent,
202
+ data: {
203
+ ...content?.data,
204
+ ...data,
205
+ ...overrideContent?.data,
206
+ },
207
+ };
208
+ return mergedContent;
209
+ };
210
+ $: canTrackToUse = () => {
211
+ return canTrack || true;
212
+ };
213
+ $: contentState = () => {
214
+ return {
215
+ ...content?.data?.state,
216
+ ...data,
217
+ ...overrideState,
218
+ };
219
+ };
220
+ $: contextContext = () => {
221
+ return context || {};
222
+ };
223
+ $: allRegisteredComponents = () => {
224
+ const allComponentsArray = [
225
+ ...getDefaultRegisteredComponents(),
226
+ // While this `components` object is deprecated, we must maintain support for it.
227
+ // Since users are able to override our default components, we need to make sure that we do not break such
228
+ // existing usage.
229
+ // This is why we spread `components` after the default Builder.io components, but before the `customComponents`,
230
+ // which is the new standard way of providing custom components, and must therefore take precedence.
231
+ ...components,
232
+ ...(customComponents || []),
233
+ ];
234
+ const allComponents = allComponentsArray.reduce(
235
+ (acc, curr) => ({
236
+ ...acc,
237
+ [curr.name]: curr,
238
+ }),
239
+ {}
240
+ );
241
+ return allComponents;
242
+ };
243
+ $: httpReqsData = () => {
244
+ return {};
245
+ };
246
+ $: shouldRenderContentStyles = () => {
247
+ return Boolean(
248
+ (useContent?.()?.data?.cssCode ||
249
+ useContent?.()?.data?.customFonts?.length) &&
250
+ TARGET !== "reactNative"
251
+ );
252
+ };
253
+
254
+ let elementRef;
255
+
256
+ let forceReRenderCount = 0;
257
+ let overrideContent = null;
258
+ let update = 0;
259
+ let overrideState = {};
260
+
261
+ onMount(() => {
262
+ if (isBrowser()) {
263
+ if (isEditing()) {
264
+ forceReRenderCount = forceReRenderCount + 1;
265
+ // QWIK-REPLACE: _useMutableProps
266
+ registerInsertMenu();
267
+ setupBrowserForEditing();
268
+ Object.values(allRegisteredComponents()).forEach(
269
+ (registeredComponent) => {
270
+ const message = createRegisterComponentMessage(registeredComponent);
271
+ window.parent?.postMessage(message, "*");
272
+ }
273
+ );
274
+ window.addEventListener("message", processMessage);
275
+ window.addEventListener(
276
+ "builder:component:stateChangeListenerActivated",
277
+ emitStateUpdate
278
+ );
279
+ }
280
+ if (useContent()) {
281
+ const variationId = useContent?.()?.testVariationId;
282
+ const contentId = useContent?.()?.id;
283
+ _track({
284
+ type: "impression",
285
+ canTrack: canTrackToUse(),
286
+ contentId,
287
+ apiKey: apiKey,
288
+ variationId: variationId !== contentId ? variationId : undefined,
289
+ });
290
+ }
291
+
292
+ // override normal content in preview mode
293
+ if (isPreviewing()) {
294
+ const searchParams = new URL(location.href).searchParams;
295
+ if (model && searchParams.get("builder.preview") === model) {
296
+ const previewApiKey =
297
+ searchParams.get("apiKey") || searchParams.get("builder.space");
298
+ if (previewApiKey) {
299
+ getContent({
300
+ model: model,
301
+ apiKey: previewApiKey,
302
+ }).then((content) => {
303
+ if (content) {
304
+ overrideContent = content;
305
+ }
306
+ });
307
+ }
308
+ }
309
+ }
310
+ evaluateJsCode();
311
+ runHttpRequests();
312
+ emitStateUpdate();
313
+ }
314
+ });
315
+
316
+ function onUpdateFn_0() {
317
+ evaluateJsCode();
318
+ }
319
+ $: onUpdateFn_0(...[useContent?.()?.data?.jsCode]);
320
+ function onUpdateFn_1() {
321
+ runHttpRequests();
322
+ }
323
+ $: onUpdateFn_1(...[useContent?.()?.data?.httpRequests]);
324
+ function onUpdateFn_2() {
325
+ emitStateUpdate();
326
+ }
327
+ $: onUpdateFn_2(...[contentState()]);
328
+
329
+ onDestroy(() => {
330
+ if (isBrowser()) {
331
+ window.removeEventListener("message", processMessage);
332
+ window.removeEventListener(
333
+ "builder:component:stateChangeListenerActivated",
334
+ emitStateUpdate
335
+ );
336
+ }
337
+ });
338
+ </script>
339
+
340
+ {#if useContent()}
341
+ <div
342
+ bind:this={elementRef}
343
+ on:click={(event) => {
344
+ onClick(event);
345
+ }}
346
+ builder-content-id={useContent?.()?.id}
347
+ builder-model={model}
348
+ >
349
+ {#if shouldRenderContentStyles()}
350
+ <RenderContentStyles
351
+ cssCode={useContent?.()?.data?.cssCode}
352
+ customFonts={useContent?.()?.data?.customFonts}
353
+ />
354
+ {/if}
355
+ <RenderBlocks
356
+ blocks={markMutable(useContent?.()?.data?.blocks)}
357
+ key={forceReRenderCount}
358
+ />
359
+ </div>
360
+ {/if}
@@ -0,0 +1,26 @@
1
+ <script context="module" lang="ts">
2
+ interface Props {
3
+ styles: string;
4
+ }
5
+ </script>
6
+
7
+ <script lang="ts">
8
+ import { TARGET } from "../constants/target.js";
9
+
10
+ export let styles: Props["styles"];
11
+
12
+ $: injectedStyleScript = () => {
13
+ return `<${tagName()}>${styles}</${tagName()}>`;
14
+ };
15
+ $: tagName = () => {
16
+ // NOTE: we have to obfusctate the name of the tag due to a limitation in the svelte-preprocessor plugin.
17
+ // https://github.com/sveltejs/vite-plugin-svelte/issues/315#issuecomment-1109000027
18
+ return "sty" + "le";
19
+ };
20
+ </script>
21
+
22
+ {#if TARGET === "svelte"}
23
+ {@html injectedStyleScript()}
24
+ {:else}
25
+ <svelte:element this={tagName()}>{styles}</svelte:element>
26
+ {/if}
@@ -0,0 +1,42 @@
1
+ import { default as Button } from '../blocks/button/button.svelte';
2
+ import { componentInfo as buttonComponentInfo } from '../blocks/button/component-info.js';
3
+ import { default as Columns } from '../blocks/columns/columns.svelte';
4
+ import { componentInfo as columnsComponentInfo } from '../blocks/columns/component-info.js';
5
+ import { componentInfo as fragmentComponentInfo } from '../blocks/fragment/component-info.js';
6
+ import { default as Fragment } from '../blocks/fragment/fragment.svelte';
7
+ import { componentInfo as imageComponentInfo } from '../blocks/image/component-info.js';
8
+ import { default as Image } from '../blocks/image/image.svelte';
9
+ import { componentInfo as sectionComponentInfo } from '../blocks/section/component-info.js';
10
+ import { default as Section } from '../blocks/section/section.svelte';
11
+ import { componentInfo as symbolComponentInfo } from '../blocks/symbol/component-info.js';
12
+ import { default as Symbol } from '../blocks/symbol/symbol.svelte';
13
+ import { componentInfo as textComponentInfo } from '../blocks/text/component-info.js';
14
+ import { default as Text } from '../blocks/text/text.svelte';
15
+ import { componentInfo as videoComponentInfo } from '../blocks/video/component-info.js';
16
+ import { default as Video } from '../blocks/video/video.svelte';
17
+ import type { RegisteredComponent } from '../context/types.js';
18
+ import { componentInfo as embedComponentInfo } from '../blocks/embed/component-info.js';
19
+ import { default as embed } from '../blocks/embed/embed.svelte';
20
+ import { default as Img } from '../blocks/img/img.svelte';
21
+ import { componentInfo as imgComponentInfo } from '../blocks/img/component-info.js';
22
+ import { default as customCode } from '../blocks/custom-code/custom-code.svelte';
23
+ import { componentInfo as customCodeInfo } from '../blocks/custom-code/component-info.js';
24
+
25
+ /**
26
+ * Returns a list of all registered components.
27
+ * NOTE: This needs to be a function to work around ESM circular dependencies.
28
+ */
29
+ export const getDefaultRegisteredComponents: () => RegisteredComponent[] =
30
+ () => [
31
+ { component: Columns, ...columnsComponentInfo },
32
+ { component: Image, ...imageComponentInfo },
33
+ { component: Img, ...imgComponentInfo },
34
+ { component: Text, ...textComponentInfo },
35
+ { component: Video, ...videoComponentInfo },
36
+ { component: Symbol, ...symbolComponentInfo },
37
+ { component: Button, ...buttonComponentInfo },
38
+ { component: Section, ...sectionComponentInfo },
39
+ { component: Fragment, ...fragmentComponentInfo },
40
+ { component: embed, ...embedComponentInfo },
41
+ { component: customCode, ...customCodeInfo },
42
+ ];
@@ -0,0 +1,28 @@
1
+ export type SizeName = 'large' | 'medium' | 'small';
2
+
3
+ interface Size {
4
+ min: number;
5
+ default: number;
6
+ max: number;
7
+ }
8
+
9
+ const SIZES: Record<SizeName, Size> = {
10
+ small: {
11
+ min: 320,
12
+ default: 321,
13
+ max: 640,
14
+ },
15
+ medium: {
16
+ min: 641,
17
+ default: 642,
18
+ max: 991,
19
+ },
20
+ large: {
21
+ min: 990,
22
+ default: 991,
23
+ max: 1200,
24
+ },
25
+ };
26
+
27
+ export const getMaxWidthQueryForSize = (size: SizeName) =>
28
+ `@media (max-width: ${SIZES[size].max}px)`;
@@ -0,0 +1,2 @@
1
+ /** This file should be overriden for each framework. Ideally this would be implemented in Mitosis. */
2
+ export const TARGET = 'svelte';
@@ -0,0 +1,13 @@
1
+ const key = Symbol();
2
+
3
+ export default {
4
+ Builder: {
5
+ content: null,
6
+ context: {},
7
+ state: {},
8
+ apiKey: null,
9
+ registeredComponents: {},
10
+ inheritedStyles: {},
11
+ },
12
+ key,
13
+ };
@@ -0,0 +1,23 @@
1
+ import type { BuilderContent } from '../types/builder-content.js';
2
+ import type { ComponentInfo } from '../types/components.js';
3
+ import type { Dictionary, Nullable } from '../types/typescript.js';
4
+
5
+ export type RegisteredComponent = ComponentInfo & {
6
+ component: any;
7
+ };
8
+
9
+ export type RegisteredComponents = Dictionary<RegisteredComponent>;
10
+
11
+ export type BuilderRenderState = Record<string, unknown>;
12
+
13
+ export type BuilderRenderContext = Record<string, unknown>;
14
+
15
+ export interface BuilderContextInterface {
16
+ content: Nullable<BuilderContent>;
17
+ context: BuilderRenderContext;
18
+ state: BuilderRenderState;
19
+ apiKey: string | null;
20
+ registeredComponents: RegisteredComponents;
21
+ // Used to recursively store all CSS coming from a parent that would apply to a Text block
22
+ inheritedStyles: Record<string, unknown>;
23
+ }
@@ -0,0 +1,2 @@
1
+ export const camelToKebabCase = (string: string) =>
2
+ string.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2').toLowerCase();
@@ -0,0 +1,52 @@
1
+ import type { BuilderContextInterface } from '../context/types.js';
2
+ import { isBrowser } from './is-browser.js';
3
+ import { isEditing } from './is-editing.js';
4
+
5
+ export function evaluate({
6
+ code,
7
+ context,
8
+ state,
9
+ event,
10
+ }: {
11
+ code: string;
12
+ event?: Event;
13
+ } & Pick<BuilderContextInterface, 'state' | 'context'>): any {
14
+ if (code === '') {
15
+ console.warn('Skipping evaluation of empty code block.');
16
+ return;
17
+ }
18
+
19
+ const builder = {
20
+ isEditing: isEditing(),
21
+ isBrowser: isBrowser(),
22
+ isServer: !isBrowser(),
23
+ };
24
+
25
+ // Be able to handle simple expressions like "state.foo" or "1 + 1"
26
+ // as well as full blocks like "var foo = "bar"; return foo"
27
+ const useReturn = !(
28
+ code.includes(';') ||
29
+ code.includes(' return ') ||
30
+ code.trim().startsWith('return ')
31
+ );
32
+
33
+ const useCode = useReturn ? `return (${code});` : code;
34
+
35
+ try {
36
+ return new Function(
37
+ 'builder',
38
+ 'Builder' /* <- legacy */,
39
+ 'state',
40
+ 'context',
41
+ 'event',
42
+ useCode
43
+ )(builder, builder, state, context, event);
44
+ } catch (e) {
45
+ console.warn(
46
+ 'Builder custom code error: \n While Evaluating: \n ',
47
+ useCode,
48
+ '\n',
49
+ (e as any).message || e
50
+ );
51
+ }
52
+ }
@@ -0,0 +1,6 @@
1
+ function capitalizeFirstLetter(string: string) {
2
+ return string.charAt(0).toUpperCase() + string.slice(1);
3
+ }
4
+
5
+ export const getEventHandlerName = (key: string) =>
6
+ `on${capitalizeFirstLetter(key)}`;
@@ -0,0 +1,39 @@
1
+ const TEXT_STYLE_KEYS = [
2
+ 'color',
3
+ 'whiteSpace',
4
+ 'direction',
5
+ 'hyphens',
6
+ 'overflowWrap',
7
+ ];
8
+
9
+ /**
10
+ * Check if the key represent a CSS style property that applies to text
11
+ * See MDN docs for refrence of what properties apply to text.
12
+ * https://developer.mozilla.org/en-US/docs/Learn/CSS/Styling_text/Fundamentals#summary
13
+ */
14
+ const isTextStyle = (key: string) => {
15
+ return (
16
+ TEXT_STYLE_KEYS.includes(key) ||
17
+ key.startsWith('font') ||
18
+ key.startsWith('text') ||
19
+ key.startsWith('letter') ||
20
+ key.startsWith('line') ||
21
+ key.startsWith('word') ||
22
+ key.startsWith('writing')
23
+ );
24
+ };
25
+
26
+ /**
27
+ * Extract styles that apply to text from a style object.
28
+ */
29
+ export const extractTextStyles = (styles: Partial<CSSStyleDeclaration>) => {
30
+ const textStyles: Partial<CSSStyleDeclaration> = {};
31
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
32
+ Object.entries(styles).forEach(([key, value]: [any, any]) => {
33
+ if (isTextStyle(key)) {
34
+ textStyles[key] = value;
35
+ }
36
+ });
37
+
38
+ return textStyles;
39
+ };
@@ -0,0 +1,5 @@
1
+ /**
2
+ * We need to serialize values to a string in case there are Proxy values, as is the case with SolidJS etc.
3
+ */
4
+ export const fastClone = <T extends object>(obj: T): T =>
5
+ JSON.parse(JSON.stringify(obj));
@@ -0,0 +1,19 @@
1
+ import type { BuilderContextInterface } from '../context/types.js';
2
+ import type { BuilderBlock } from '../types/builder-block.js';
3
+ import { evaluate } from './evaluate.js';
4
+
5
+ export function crateEventHandler(
6
+ value: string,
7
+ options: { block: BuilderBlock } & Pick<
8
+ BuilderContextInterface,
9
+ 'state' | 'context'
10
+ >
11
+ ): (event: Event) => any {
12
+ return (event: Event) =>
13
+ evaluate({
14
+ code: value,
15
+ context: options.context,
16
+ state: options.state,
17
+ event,
18
+ });
19
+ }
@@ -0,0 +1,25 @@
1
+ import type { BuilderContextInterface } from '../context/types.js';
2
+ import type { BuilderBlock } from '../types/builder-block.js';
3
+ import { getEventHandlerName } from './event-handler-name.js';
4
+ import { crateEventHandler } from './get-block-actions-handler.js';
5
+
6
+ type Actions = { [index: string]: (event: Event) => any };
7
+
8
+ export function getBlockActions(
9
+ options: {
10
+ block: BuilderBlock;
11
+ } & Pick<BuilderContextInterface, 'state' | 'context'>
12
+ ): Actions {
13
+ const obj: Actions = {};
14
+ const optionActions = options.block.actions ?? {};
15
+
16
+ for (const key in optionActions) {
17
+ // eslint-disable-next-line no-prototype-builtins
18
+ if (!optionActions.hasOwnProperty(key)) {
19
+ continue;
20
+ }
21
+ const value = optionActions[key];
22
+ obj[getEventHandlerName(key)] = crateEventHandler(value, options);
23
+ }
24
+ return obj;
25
+ }
@@ -0,0 +1,12 @@
1
+ import type { BuilderBlock } from '../types/builder-block.js';
2
+
3
+ export function getBlockComponentOptions(block: BuilderBlock) {
4
+ return {
5
+ ...block.component?.options,
6
+ ...(block as any).options,
7
+ /**
8
+ * Our built-in components frequently make use of the block, so we provide all of it under `builderBlock`
9
+ */
10
+ builderBlock: block,
11
+ };
12
+ }
@@ -0,0 +1,11 @@
1
+ import type { BuilderBlock } from '../types/builder-block.js';
2
+
3
+ export function getBlockProperties(block: BuilderBlock) {
4
+ return {
5
+ ...block.properties,
6
+ 'builder-id': block.id,
7
+ class: [block.id, 'builder-block', block.class, block.properties?.class]
8
+ .filter(Boolean)
9
+ .join(' '),
10
+ };
11
+ }
@@ -0,0 +1,7 @@
1
+ import type { BuilderBlock } from '../types/builder-block.js';
2
+
3
+ export type TagName = string;
4
+
5
+ export function getBlockTag(block: BuilderBlock): TagName {
6
+ return block.tagName || 'div';
7
+ }