@builder.io/sdk-react 0.4.3 → 0.4.5
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.
- package/dist/sdk/blocks/columns/columns.js +6 -6
- package/dist/sdk/blocks/symbol/symbol.js +7 -5
- package/dist/sdk/blocks/text/text.d.ts +1 -1
- package/dist/sdk/blocks/text/text.js +1 -1
- package/dist/sdk/components/block/block.d.ts +11 -0
- package/dist/sdk/components/block/block.helpers.d.ts +12 -0
- package/dist/sdk/components/block/block.helpers.js +86 -0
- package/dist/sdk/components/block/block.js +124 -0
- package/dist/sdk/components/block/components/block-styles.d.ts +9 -0
- package/dist/sdk/components/block/components/block-styles.js +65 -0
- package/dist/sdk/components/block/components/component.d.ts +20 -0
- package/dist/sdk/components/block/components/component.js +11 -0
- package/dist/sdk/components/block/components/repeated-block.d.ts +11 -0
- package/dist/sdk/components/block/components/repeated-block.js +11 -0
- package/dist/sdk/components/block/types.d.ts +6 -0
- package/dist/sdk/components/block/types.js +1 -0
- package/dist/sdk/components/blocks/blocks-wrapper.d.ts +13 -0
- package/dist/sdk/components/blocks/blocks-wrapper.js +38 -0
- package/dist/sdk/components/blocks/blocks.d.ts +10 -0
- package/dist/sdk/components/blocks/blocks.js +11 -0
- package/dist/sdk/components/content/components/content-styles.d.ts +9 -0
- package/dist/sdk/components/content/components/content-styles.helpers.d.ts +15 -0
- package/dist/sdk/components/content/components/content-styles.helpers.js +59 -0
- package/dist/sdk/components/content/components/content-styles.js +32 -0
- package/dist/sdk/components/content/components/enable-editor.d.ts +13 -0
- package/dist/sdk/components/content/components/enable-editor.js +278 -0
- package/dist/sdk/components/content/content.d.ts +4 -0
- package/dist/sdk/components/content/content.helpers.d.ts +7 -0
- package/dist/sdk/components/content/content.helpers.js +30 -0
- package/dist/sdk/components/content/content.js +97 -0
- package/dist/sdk/components/content/content.types.d.ts +38 -0
- package/dist/sdk/components/content/content.types.js +1 -0
- package/dist/sdk/components/content/index.d.ts +1 -0
- package/dist/sdk/components/content/index.js +1 -0
- package/dist/sdk/components/content/wrap-component-ref.d.ts +6 -0
- package/dist/sdk/components/content/wrap-component-ref.js +6 -0
- package/dist/sdk/components/content-variants/content-variants.d.ts +5 -0
- package/dist/sdk/components/content-variants/content-variants.js +37 -0
- package/dist/sdk/components/content-variants/helpers.d.ts +17 -0
- package/dist/sdk/components/content-variants/helpers.js +184 -0
- package/dist/sdk/components/inlined-script.d.ts +7 -0
- package/dist/sdk/components/inlined-script.js +6 -0
- package/dist/sdk/components/inlined-styles.d.ts +7 -0
- package/dist/sdk/components/inlined-styles.js +6 -0
- package/dist/sdk/components/render-block/block-styles.js +2 -2
- package/dist/sdk/components/render-content/components/render-styles.js +2 -2
- package/dist/sdk/components/render-content/render-content.js +8 -6
- package/dist/sdk/components/render-content/render-content.types.d.ts +9 -23
- package/dist/sdk/components/render-content-variants/helpers.d.ts +27 -3
- package/dist/sdk/components/render-content-variants/helpers.js +38 -24
- package/dist/sdk/components/render-content-variants/render-content-variants.d.ts +7 -2
- package/dist/sdk/components/render-content-variants/render-content-variants.js +29 -21
- package/dist/sdk/components/render-content-variants/render-content-variants.types.d.ts +20 -0
- package/dist/sdk/components/render-content-variants/render-content-variants.types.js +1 -0
- package/dist/sdk/constants/sdk-version.d.ts +1 -1
- package/dist/sdk/constants/sdk-version.js +1 -1
- package/dist/sdk/functions/track/index.js +1 -1
- package/dist/sdk/helpers/ab-tests.js +6 -0
- package/dist/sdk/types/builder-props.d.ts +10 -0
- package/dist/sdk/types/builder-props.js +1 -0
- package/dist/sdk/types/enforced-partials.d.ts +21 -0
- package/dist/sdk/types/enforced-partials.js +1 -0
- package/dist/sdk/types/typescript.d.ts +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import InlinedStyles from "../../inlined-styles";
|
|
5
|
+
import { getCss } from "./content-styles.helpers";
|
|
6
|
+
import { getFontCss } from "./content-styles.helpers";
|
|
7
|
+
function ContentStyles(props) {
|
|
8
|
+
const [injectedStyles, setInjectedStyles] = useState(() => `
|
|
9
|
+
${getCss({
|
|
10
|
+
cssCode: props.cssCode,
|
|
11
|
+
contentId: props.contentId,
|
|
12
|
+
})}
|
|
13
|
+
${getFontCss({
|
|
14
|
+
customFonts: props.customFonts,
|
|
15
|
+
})}
|
|
16
|
+
|
|
17
|
+
.builder-text > p:first-of-type, .builder-text > .builder-paragraph:first-of-type {
|
|
18
|
+
margin: 0;
|
|
19
|
+
}
|
|
20
|
+
.builder-text > p, .builder-text > .builder-paragraph {
|
|
21
|
+
color: inherit;
|
|
22
|
+
line-height: inherit;
|
|
23
|
+
letter-spacing: inherit;
|
|
24
|
+
font-weight: inherit;
|
|
25
|
+
font-size: inherit;
|
|
26
|
+
text-align: inherit;
|
|
27
|
+
font-family: inherit;
|
|
28
|
+
}
|
|
29
|
+
`.trim());
|
|
30
|
+
return React.createElement(InlinedStyles, { styles: injectedStyles });
|
|
31
|
+
}
|
|
32
|
+
export default ContentStyles;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
type BuilderEditorProps = Omit<ContentProps, "customComponents"> & {
|
|
3
|
+
customComponents: Dictionary<ComponentInfo>;
|
|
4
|
+
builderContextSignal: BuilderContextInterface;
|
|
5
|
+
setBuilderContextSignal: any;
|
|
6
|
+
children?: any;
|
|
7
|
+
};
|
|
8
|
+
import type { BuilderContextInterface } from "../../../context/types.js";
|
|
9
|
+
import type { ContentProps } from "../content.types.js";
|
|
10
|
+
import type { Dictionary } from "../../../types/typescript.js";
|
|
11
|
+
import type { ComponentInfo } from "../../../types/components.js";
|
|
12
|
+
declare function EnableEditor(props: BuilderEditorProps): JSX.Element;
|
|
13
|
+
export default EnableEditor;
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useState, useRef, useEffect } from "react";
|
|
4
|
+
import { evaluate } from "../../../functions/evaluate.js";
|
|
5
|
+
import { getContent } from "../../../functions/get-content/index.js";
|
|
6
|
+
import { fetch } from "../../../functions/get-fetch.js";
|
|
7
|
+
import { isBrowser } from "../../../functions/is-browser.js";
|
|
8
|
+
import { isEditing } from "../../../functions/is-editing.js";
|
|
9
|
+
import { isPreviewing } from "../../../functions/is-previewing.js";
|
|
10
|
+
import { createRegisterComponentMessage } from "../../../functions/register-component.js";
|
|
11
|
+
import { _track } from "../../../functions/track/index.js";
|
|
12
|
+
import builderContext from "../../../context/builder.context.js";
|
|
13
|
+
import { registerInsertMenu, setupBrowserForEditing, } from "../../../scripts/init-editing.js";
|
|
14
|
+
import { checkIsDefined } from "../../../helpers/nullable.js";
|
|
15
|
+
import { getInteractionPropertiesForEvent } from "../../../functions/track/interaction.js";
|
|
16
|
+
import { TARGET } from "../../../constants/target.js";
|
|
17
|
+
import { logger } from "../../../helpers/logger.js";
|
|
18
|
+
function EnableEditor(props) {
|
|
19
|
+
const elementRef = useRef(null);
|
|
20
|
+
const [forceReRenderCount, setForceReRenderCount] = useState(() => 0);
|
|
21
|
+
function mergeNewContent(newContent) {
|
|
22
|
+
props.setBuilderContextSignal((PREVIOUS_VALUE) => ({
|
|
23
|
+
...PREVIOUS_VALUE,
|
|
24
|
+
content: {
|
|
25
|
+
...props.builderContextSignal.content,
|
|
26
|
+
...newContent,
|
|
27
|
+
data: {
|
|
28
|
+
...props.builderContextSignal.content?.data,
|
|
29
|
+
...newContent?.data,
|
|
30
|
+
},
|
|
31
|
+
meta: {
|
|
32
|
+
...props.builderContextSignal.content?.meta,
|
|
33
|
+
...newContent?.meta,
|
|
34
|
+
breakpoints: newContent?.meta?.breakpoints ||
|
|
35
|
+
props.builderContextSignal.content?.meta?.breakpoints,
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
const [canTrackToUse, setCanTrackToUse] = useState(() => checkIsDefined(props.canTrack) ? props.canTrack : true);
|
|
41
|
+
function processMessage(event) {
|
|
42
|
+
const { data } = event;
|
|
43
|
+
if (data) {
|
|
44
|
+
switch (data.type) {
|
|
45
|
+
case "builder.configureSdk": {
|
|
46
|
+
const messageContent = data.data;
|
|
47
|
+
const { breakpoints, contentId } = messageContent;
|
|
48
|
+
if (!contentId ||
|
|
49
|
+
contentId !== props.builderContextSignal.content?.id) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (breakpoints) {
|
|
53
|
+
mergeNewContent({
|
|
54
|
+
meta: {
|
|
55
|
+
breakpoints,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
setForceReRenderCount(forceReRenderCount + 1); // This is a hack to force Qwik to re-render.
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "builder.contentUpdate": {
|
|
63
|
+
const messageContent = data.data;
|
|
64
|
+
const key = messageContent.key ||
|
|
65
|
+
messageContent.alias ||
|
|
66
|
+
messageContent.entry ||
|
|
67
|
+
messageContent.modelName;
|
|
68
|
+
const contentData = messageContent.data;
|
|
69
|
+
if (key === props.model) {
|
|
70
|
+
mergeNewContent(contentData);
|
|
71
|
+
setForceReRenderCount(forceReRenderCount + 1); // This is a hack to force Qwik to re-render.
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
case "builder.patchUpdates": {
|
|
76
|
+
// TODO
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function evaluateJsCode() {
|
|
83
|
+
// run any dynamic JS code attached to content
|
|
84
|
+
const jsCode = props.builderContextSignal.content?.data?.jsCode;
|
|
85
|
+
if (jsCode) {
|
|
86
|
+
evaluate({
|
|
87
|
+
code: jsCode,
|
|
88
|
+
context: props.context || {},
|
|
89
|
+
localState: undefined,
|
|
90
|
+
rootState: props.builderContextSignal.rootState,
|
|
91
|
+
rootSetState: props.builderContextSignal.rootSetState,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const [httpReqsData, setHttpReqsData] = useState(() => ({}));
|
|
96
|
+
const [clicked, setClicked] = useState(() => false);
|
|
97
|
+
function onClick(event) {
|
|
98
|
+
if (props.builderContextSignal.content) {
|
|
99
|
+
const variationId = props.builderContextSignal.content?.testVariationId;
|
|
100
|
+
const contentId = props.builderContextSignal.content?.id;
|
|
101
|
+
_track({
|
|
102
|
+
type: "click",
|
|
103
|
+
canTrack: canTrackToUse,
|
|
104
|
+
contentId,
|
|
105
|
+
apiKey: props.apiKey,
|
|
106
|
+
variationId: variationId !== contentId ? variationId : undefined,
|
|
107
|
+
...getInteractionPropertiesForEvent(event),
|
|
108
|
+
unique: !clicked,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (!clicked) {
|
|
112
|
+
setClicked(true);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
function evalExpression(expression) {
|
|
116
|
+
return expression.replace(/{{([^}]+)}}/g, (_match, group) => evaluate({
|
|
117
|
+
code: group,
|
|
118
|
+
context: props.context || {},
|
|
119
|
+
localState: undefined,
|
|
120
|
+
rootState: props.builderContextSignal.rootState,
|
|
121
|
+
rootSetState: props.builderContextSignal.rootSetState,
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
function handleRequest({ url, key }) {
|
|
125
|
+
fetch(url)
|
|
126
|
+
.then((response) => response.json())
|
|
127
|
+
.then((json) => {
|
|
128
|
+
const newState = {
|
|
129
|
+
...props.builderContextSignal.rootState,
|
|
130
|
+
[key]: json,
|
|
131
|
+
};
|
|
132
|
+
props.builderContextSignal.rootSetState?.(newState);
|
|
133
|
+
httpReqsData[key] = true;
|
|
134
|
+
})
|
|
135
|
+
.catch((err) => {
|
|
136
|
+
console.error("error fetching dynamic data", url, err);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
function runHttpRequests() {
|
|
140
|
+
const requests = props.builderContextSignal.content?.data?.httpRequests ?? {};
|
|
141
|
+
Object.entries(requests).forEach(([key, url]) => {
|
|
142
|
+
if (url && (!httpReqsData[key] || isEditing())) {
|
|
143
|
+
const evaluatedUrl = evalExpression(url);
|
|
144
|
+
handleRequest({
|
|
145
|
+
url: evaluatedUrl,
|
|
146
|
+
key,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
function emitStateUpdate() {
|
|
152
|
+
if (isEditing()) {
|
|
153
|
+
window.dispatchEvent(new CustomEvent("builder:component:stateChange", {
|
|
154
|
+
detail: {
|
|
155
|
+
state: props.builderContextSignal.rootState,
|
|
156
|
+
ref: {
|
|
157
|
+
name: props.model,
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
}));
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
if (!props.apiKey) {
|
|
165
|
+
logger.error("No API key provided to `RenderContent` component. This can cause issues. Please provide an API key using the `apiKey` prop.");
|
|
166
|
+
}
|
|
167
|
+
if (isBrowser()) {
|
|
168
|
+
if (isEditing()) {
|
|
169
|
+
setForceReRenderCount(forceReRenderCount + 1);
|
|
170
|
+
registerInsertMenu();
|
|
171
|
+
setupBrowserForEditing({
|
|
172
|
+
...(props.locale
|
|
173
|
+
? {
|
|
174
|
+
locale: props.locale,
|
|
175
|
+
}
|
|
176
|
+
: {}),
|
|
177
|
+
...(props.includeRefs
|
|
178
|
+
? {
|
|
179
|
+
includeRefs: props.includeRefs,
|
|
180
|
+
}
|
|
181
|
+
: {}),
|
|
182
|
+
...(props.enrich
|
|
183
|
+
? {
|
|
184
|
+
enrich: props.enrich,
|
|
185
|
+
}
|
|
186
|
+
: {}),
|
|
187
|
+
});
|
|
188
|
+
Object.values(props.builderContextSignal.registeredComponents).forEach((registeredComponent) => {
|
|
189
|
+
const message = createRegisterComponentMessage(registeredComponent);
|
|
190
|
+
window.parent?.postMessage(message, "*");
|
|
191
|
+
});
|
|
192
|
+
window.addEventListener("message", processMessage);
|
|
193
|
+
window.addEventListener("builder:component:stateChangeListenerActivated", emitStateUpdate);
|
|
194
|
+
}
|
|
195
|
+
if (props.builderContextSignal.content) {
|
|
196
|
+
const variationId = props.builderContextSignal.content?.testVariationId;
|
|
197
|
+
const contentId = props.builderContextSignal.content?.id;
|
|
198
|
+
_track({
|
|
199
|
+
type: "impression",
|
|
200
|
+
canTrack: canTrackToUse,
|
|
201
|
+
contentId,
|
|
202
|
+
apiKey: props.apiKey,
|
|
203
|
+
variationId: variationId !== contentId ? variationId : undefined,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
// override normal content in preview mode
|
|
207
|
+
if (isPreviewing()) {
|
|
208
|
+
const searchParams = new URL(location.href).searchParams;
|
|
209
|
+
const searchParamPreviewModel = searchParams.get("builder.preview");
|
|
210
|
+
const searchParamPreviewId = searchParams.get(`builder.preview.${searchParamPreviewModel}`);
|
|
211
|
+
const previewApiKey = searchParams.get("apiKey") || searchParams.get("builder.space");
|
|
212
|
+
/**
|
|
213
|
+
* Make sure that:
|
|
214
|
+
* - the preview model name is the same as the one we're rendering, since there can be multiple models rendered * at the same time, e.g. header/page/footer. * - the API key is the same, since we don't want to preview content from other organizations.
|
|
215
|
+
* - if there is content, that the preview ID is the same as that of the one we receive.
|
|
216
|
+
*
|
|
217
|
+
* TO-DO: should we only update the state when there is a change?
|
|
218
|
+
**/
|
|
219
|
+
if (searchParamPreviewModel === props.model &&
|
|
220
|
+
previewApiKey === props.apiKey &&
|
|
221
|
+
(!props.content || searchParamPreviewId === props.content.id)) {
|
|
222
|
+
getContent({
|
|
223
|
+
model: props.model,
|
|
224
|
+
apiKey: props.apiKey,
|
|
225
|
+
apiVersion: props.apiVersion,
|
|
226
|
+
}).then((content) => {
|
|
227
|
+
if (content) {
|
|
228
|
+
mergeNewContent(content);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
evaluateJsCode();
|
|
234
|
+
runHttpRequests();
|
|
235
|
+
emitStateUpdate();
|
|
236
|
+
}
|
|
237
|
+
}, []);
|
|
238
|
+
useEffect(() => {
|
|
239
|
+
if (props.content) {
|
|
240
|
+
mergeNewContent(props.content);
|
|
241
|
+
}
|
|
242
|
+
}, [props.content]);
|
|
243
|
+
useEffect(() => {
|
|
244
|
+
evaluateJsCode();
|
|
245
|
+
}, [
|
|
246
|
+
props.builderContextSignal.content?.data?.jsCode,
|
|
247
|
+
props.builderContextSignal.rootState,
|
|
248
|
+
]);
|
|
249
|
+
useEffect(() => {
|
|
250
|
+
runHttpRequests();
|
|
251
|
+
}, [props.builderContextSignal.content?.data?.httpRequests]);
|
|
252
|
+
useEffect(() => {
|
|
253
|
+
emitStateUpdate();
|
|
254
|
+
}, [props.builderContextSignal.rootState]);
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
return () => {
|
|
257
|
+
if (isBrowser()) {
|
|
258
|
+
window.removeEventListener("message", processMessage);
|
|
259
|
+
window.removeEventListener("builder:component:stateChangeListenerActivated", emitStateUpdate);
|
|
260
|
+
}
|
|
261
|
+
};
|
|
262
|
+
}, []);
|
|
263
|
+
return (React.createElement(builderContext.Provider, { value: props.builderContextSignal }, props.builderContextSignal.content ? (React.createElement(React.Fragment, null,
|
|
264
|
+
React.createElement("div", { ref: elementRef, onClick: (event) => onClick(event), "builder-content-id": props.builderContextSignal.content?.id, "builder-model": props.model, key: forceReRenderCount, ...(TARGET === "reactNative"
|
|
265
|
+
? {
|
|
266
|
+
dataSet: {
|
|
267
|
+
// currently, we can't set the actual ID here. // we don't need it right now, we just need to identify content divs for testing.
|
|
268
|
+
"builder-content-id": "",
|
|
269
|
+
},
|
|
270
|
+
}
|
|
271
|
+
: {}), ...(props.hideContent
|
|
272
|
+
? {
|
|
273
|
+
hidden: true,
|
|
274
|
+
"aria-hidden": true,
|
|
275
|
+
}
|
|
276
|
+
: {}), className: props.classNameProp }, props.children))) : null));
|
|
277
|
+
}
|
|
278
|
+
export default EnableEditor;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { BuilderContent } from '../../types/builder-content';
|
|
2
|
+
import type { Nullable } from '../../types/typescript';
|
|
3
|
+
import type { ContentProps } from './content.types';
|
|
4
|
+
export declare const getContextStateInitialValue: ({ content, data, locale, }: Pick<ContentProps, 'content' | 'data' | 'locale'>) => {
|
|
5
|
+
[x: string]: unknown;
|
|
6
|
+
};
|
|
7
|
+
export declare const getContentInitialValue: ({ content, data, }: Pick<ContentProps, 'content' | 'data'>) => Nullable<BuilderContent>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const getContextStateInitialValue = ({ content, data, locale, }) => {
|
|
2
|
+
const defaultValues = {};
|
|
3
|
+
// set default values for content state inputs
|
|
4
|
+
content?.data?.inputs?.forEach((input) => {
|
|
5
|
+
if (input.name &&
|
|
6
|
+
input.defaultValue !== undefined &&
|
|
7
|
+
content?.data?.state &&
|
|
8
|
+
content.data.state[input.name] === undefined) {
|
|
9
|
+
defaultValues[input.name] = input.defaultValue;
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
const stateToUse = {
|
|
13
|
+
...content?.data?.state,
|
|
14
|
+
...data,
|
|
15
|
+
...(locale ? { locale } : {}),
|
|
16
|
+
};
|
|
17
|
+
return { ...defaultValues, ...stateToUse };
|
|
18
|
+
};
|
|
19
|
+
export const getContentInitialValue = ({ content, data, }) => {
|
|
20
|
+
return !content
|
|
21
|
+
? undefined
|
|
22
|
+
: {
|
|
23
|
+
...content,
|
|
24
|
+
data: {
|
|
25
|
+
...content?.data,
|
|
26
|
+
...data,
|
|
27
|
+
},
|
|
28
|
+
meta: content?.meta,
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { getDefaultRegisteredComponents } from "../../constants/builder-registered-components.js";
|
|
5
|
+
import { components } from "../../functions/register-component.js";
|
|
6
|
+
import Blocks from "../blocks/blocks";
|
|
7
|
+
import ContentStyles from "./components/content-styles";
|
|
8
|
+
import { getContentInitialValue, getContextStateInitialValue, } from "./content.helpers.js";
|
|
9
|
+
import { TARGET } from "../../constants/target.js";
|
|
10
|
+
import { getRenderContentScriptString } from "../content-variants/helpers.js";
|
|
11
|
+
import { wrapComponentRef } from "./wrap-component-ref.js";
|
|
12
|
+
import EnableEditor from "./components/enable-editor";
|
|
13
|
+
function Content(props) {
|
|
14
|
+
const [scriptStr, setScriptStr] = useState(() => getRenderContentScriptString({
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/no-non-null-asserted-optional-chain
|
|
16
|
+
contentId: props.content?.id,
|
|
17
|
+
parentContentId: props.parentContentId,
|
|
18
|
+
}));
|
|
19
|
+
function contentSetState(newRootState) {
|
|
20
|
+
setBuilderContextSignal((PREVIOUS_VALUE) => ({
|
|
21
|
+
...PREVIOUS_VALUE,
|
|
22
|
+
rootState: newRootState,
|
|
23
|
+
}));
|
|
24
|
+
}
|
|
25
|
+
const [customComps, setCustomComps] = useState(() => [
|
|
26
|
+
...getDefaultRegisteredComponents(),
|
|
27
|
+
// While this `components` object is deprecated, we must maintain support for it.
|
|
28
|
+
// Since users are able to override our default components, we need to make sure that we do not break such
|
|
29
|
+
// existing usage.
|
|
30
|
+
// This is why we spread `components` after the default Builder.io components, but before the `props.customComponents`,
|
|
31
|
+
// which is the new standard way of providing custom components, and must therefore take precedence.
|
|
32
|
+
...components,
|
|
33
|
+
...(props.customComponents || []),
|
|
34
|
+
].reduce((acc, info) => ({
|
|
35
|
+
...acc,
|
|
36
|
+
[info.name]: info,
|
|
37
|
+
}), {}));
|
|
38
|
+
function customComponentsInfo() {
|
|
39
|
+
// TO-DO: fix once we remove `useStore<any>` hack in Qwik generator.
|
|
40
|
+
return Object.values(customComps).reduce((acc, { component: _, ...info }) => ({
|
|
41
|
+
...acc,
|
|
42
|
+
[info.name]: info,
|
|
43
|
+
}), {});
|
|
44
|
+
}
|
|
45
|
+
function tempContextSignalSetter() {
|
|
46
|
+
return setBuilderContextSignal;
|
|
47
|
+
}
|
|
48
|
+
const [builderContextSignal, setBuilderContextSignal] = useState(() => ({
|
|
49
|
+
content: getContentInitialValue({
|
|
50
|
+
content: props.content,
|
|
51
|
+
data: props.data,
|
|
52
|
+
}),
|
|
53
|
+
localState: undefined,
|
|
54
|
+
rootState: getContextStateInitialValue({
|
|
55
|
+
content: props.content,
|
|
56
|
+
data: props.data,
|
|
57
|
+
locale: props.locale,
|
|
58
|
+
}),
|
|
59
|
+
rootSetState: undefined,
|
|
60
|
+
context: props.context || {},
|
|
61
|
+
apiKey: props.apiKey,
|
|
62
|
+
apiVersion: props.apiVersion,
|
|
63
|
+
registeredComponents: [
|
|
64
|
+
...getDefaultRegisteredComponents(),
|
|
65
|
+
// While this `components` object is deprecated, we must maintain support for it.
|
|
66
|
+
// Since users are able to override our default components, we need to make sure that we do not break such
|
|
67
|
+
// existing usage.
|
|
68
|
+
// This is why we spread `components` after the default Builder.io components, but before the `props.customComponents`,
|
|
69
|
+
// which is the new standard way of providing custom components, and must therefore take precedence.
|
|
70
|
+
...components,
|
|
71
|
+
...(props.customComponents || []),
|
|
72
|
+
].reduce((acc, { component, ...curr }) => ({
|
|
73
|
+
...acc,
|
|
74
|
+
[curr.name]: {
|
|
75
|
+
component: TARGET === "vue3" ? wrapComponentRef(component) : component,
|
|
76
|
+
...curr,
|
|
77
|
+
},
|
|
78
|
+
}), {}),
|
|
79
|
+
inheritedStyles: {},
|
|
80
|
+
}));
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
if (!builderContextSignal.content) {
|
|
83
|
+
builderContextSignal.content = getContentInitialValue({
|
|
84
|
+
content: props.content,
|
|
85
|
+
data: props.data,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}, [props.content, props.data, props.locale]);
|
|
89
|
+
return (React.createElement(React.Fragment, null, builderContextSignal.content ? (React.createElement(React.Fragment, null,
|
|
90
|
+
React.createElement(EnableEditor, { content: props.content, model: props.model, data: props.data, context: props.context, apiKey: props.apiKey, apiVersion: props.apiVersion, customComponents: customComponentsInfo(), canTrack: props.canTrack, locale: props.locale, includeRefs: props.includeRefs, enrich: props.enrich, classNameProp: props.classNameProp, hideContent: props.hideContent, parentContentId: props.parentContentId, isSsrAbTest: props.isSsrAbTest, builderContextSignal: builderContextSignal, setBuilderContextSignal: tempContextSignalSetter() },
|
|
91
|
+
props.isSsrAbTest ? (React.createElement(React.Fragment, null,
|
|
92
|
+
React.createElement("script", { dangerouslySetInnerHTML: { __html: scriptStr } }))) : null,
|
|
93
|
+
TARGET !== "reactNative" ? (React.createElement(React.Fragment, null,
|
|
94
|
+
React.createElement(ContentStyles, { contentId: builderContextSignal.content?.id, cssCode: builderContextSignal.content?.data?.cssCode, customFonts: builderContextSignal.content?.data?.customFonts }))) : null,
|
|
95
|
+
React.createElement(Blocks, { blocks: builderContextSignal.content?.data?.blocks, context: builderContextSignal, components: customComps })))) : null));
|
|
96
|
+
}
|
|
97
|
+
export default Content;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { BuilderRenderContext, RegisteredComponent, BuilderRenderState } from '../../context/types';
|
|
2
|
+
import type { BuilderContent } from '../../types/builder-content';
|
|
3
|
+
import type { Nullable } from '../../types/typescript';
|
|
4
|
+
import type { ApiVersion } from '../../types/api-version';
|
|
5
|
+
export interface ContentProps {
|
|
6
|
+
content?: Nullable<BuilderContent>;
|
|
7
|
+
model?: string;
|
|
8
|
+
data?: {
|
|
9
|
+
[key: string]: any;
|
|
10
|
+
};
|
|
11
|
+
context?: BuilderRenderContext;
|
|
12
|
+
apiKey: string;
|
|
13
|
+
apiVersion?: ApiVersion;
|
|
14
|
+
customComponents?: RegisteredComponent[];
|
|
15
|
+
canTrack?: boolean;
|
|
16
|
+
locale?: string;
|
|
17
|
+
/** @deprecated use `enrich` instead **/
|
|
18
|
+
includeRefs?: boolean;
|
|
19
|
+
enrich?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* TO-DO: improve qwik generator to not remap this name for non-HTML tags, then name it `className`
|
|
22
|
+
*/
|
|
23
|
+
classNameProp?: string;
|
|
24
|
+
hideContent?: boolean;
|
|
25
|
+
parentContentId?: string;
|
|
26
|
+
isSsrAbTest?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface BuilderComponentStateChange {
|
|
29
|
+
state: BuilderRenderState;
|
|
30
|
+
ref: {
|
|
31
|
+
name?: string;
|
|
32
|
+
props?: {
|
|
33
|
+
builderBlock?: {
|
|
34
|
+
id?: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './content';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './content';
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { checkShouldRunVariants, getVariants, getVariantsScriptString, } from "./helpers";
|
|
5
|
+
import Content from "../content/content";
|
|
6
|
+
import { getDefaultCanTrack } from "../../helpers/canTrack";
|
|
7
|
+
import InlinedStyles from "../inlined-styles";
|
|
8
|
+
import { handleABTestingSync } from "../../helpers/ab-tests";
|
|
9
|
+
function RenderContentVariants(props) {
|
|
10
|
+
const [variantScriptStr, setVariantScriptStr] = useState(() => getVariantsScriptString(getVariants(props.content).map((value) => ({
|
|
11
|
+
id: value.id,
|
|
12
|
+
testRatio: value.testRatio,
|
|
13
|
+
})), props.content?.id || ""));
|
|
14
|
+
const [shouldRenderVariants, setShouldRenderVariants] = useState(() => checkShouldRunVariants({
|
|
15
|
+
canTrack: getDefaultCanTrack(props.canTrack),
|
|
16
|
+
content: props.content,
|
|
17
|
+
}));
|
|
18
|
+
const [hideVariantsStyleString, setHideVariantsStyleString] = useState(() => getVariants(props.content)
|
|
19
|
+
.map((value) => `.variant-${value.id} { display: none; } `)
|
|
20
|
+
.join(""));
|
|
21
|
+
const [contentToRender, setContentToRender] = useState(() => checkShouldRunVariants({
|
|
22
|
+
canTrack: getDefaultCanTrack(props.canTrack),
|
|
23
|
+
content: props.content,
|
|
24
|
+
})
|
|
25
|
+
? props.content
|
|
26
|
+
: handleABTestingSync({
|
|
27
|
+
item: props.content,
|
|
28
|
+
canTrack: getDefaultCanTrack(props.canTrack),
|
|
29
|
+
}));
|
|
30
|
+
return (React.createElement(React.Fragment, null,
|
|
31
|
+
shouldRenderVariants ? (React.createElement(React.Fragment, null,
|
|
32
|
+
React.createElement(InlinedStyles, { id: `variants-styles-${props.content?.id}`, styles: hideVariantsStyleString }),
|
|
33
|
+
React.createElement("script", { id: `variants-script-${props.content?.id}`, dangerouslySetInnerHTML: { __html: variantScriptStr } }),
|
|
34
|
+
getVariants(props.content)?.map((variant) => (React.createElement(Content, { key: variant.id, content: variant, apiKey: props.apiKey, apiVersion: props.apiVersion, canTrack: props.canTrack, customComponents: props.customComponents, hideContent: true, parentContentId: props.content?.id, isSsrAbTest: shouldRenderVariants }))))) : null,
|
|
35
|
+
React.createElement(Content, { model: props.model, content: contentToRender, apiKey: props.apiKey, apiVersion: props.apiVersion, canTrack: props.canTrack, customComponents: props.customComponents, classNameProp: `variant-${props.content?.id}`, parentContentId: props.content?.id, isSsrAbTest: shouldRenderVariants })));
|
|
36
|
+
}
|
|
37
|
+
export default RenderContentVariants;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Nullable } from '../../helpers/nullable';
|
|
2
|
+
import type { BuilderContent } from '../../types/builder-content';
|
|
3
|
+
export declare const getVariants: (content: Nullable<BuilderContent>) => import("../../types/builder-content").BuilderContentVariation[];
|
|
4
|
+
export declare const checkShouldRunVariants: ({ canTrack, content, }: {
|
|
5
|
+
canTrack: Nullable<boolean>;
|
|
6
|
+
content: Nullable<BuilderContent>;
|
|
7
|
+
}) => boolean;
|
|
8
|
+
type VariantData = {
|
|
9
|
+
id: string;
|
|
10
|
+
testRatio?: number;
|
|
11
|
+
};
|
|
12
|
+
export declare const getVariantsScriptString: (variants: VariantData[], contentId: string) => string;
|
|
13
|
+
export declare const getRenderContentScriptString: ({ parentContentId, contentId, }: {
|
|
14
|
+
contentId: string;
|
|
15
|
+
parentContentId: string;
|
|
16
|
+
}) => string;
|
|
17
|
+
export {};
|