@contentful/experiences-sdk-react 0.0.1-alpha.2
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/LICENSE +21 -0
- package/README.md +115 -0
- package/dist/VisualEditorInjectScript-DPer6DO3.js +38 -0
- package/dist/VisualEditorInjectScript-DPer6DO3.js.map +1 -0
- package/dist/VisualEditorLoader-CY2fhqS5.js +28 -0
- package/dist/VisualEditorLoader-CY2fhqS5.js.map +1 -0
- package/dist/index.d.ts +122 -0
- package/dist/index.js +859 -0
- package/dist/index.js.map +1 -0
- package/dist/src/ExperienceRoot.d.ts +14 -0
- package/dist/src/blocks/editor/VisualEditorInjectScript.d.ts +4 -0
- package/dist/src/blocks/editor/VisualEditorLoader.d.ts +7 -0
- package/dist/src/blocks/editor/VisualEditorRoot.d.ts +9 -0
- package/dist/src/blocks/preview/CompositionBlock.d.ts +10 -0
- package/dist/src/blocks/preview/CompositionBlock.spec.d.ts +1 -0
- package/dist/src/blocks/preview/DeprecatedPreviewDeliveryRoot.d.ts +12 -0
- package/dist/src/blocks/preview/PreviewDeliveryRoot.d.ts +8 -0
- package/dist/src/blocks/preview/PreviewDeliveryRoot.test.d.ts +1 -0
- package/dist/src/components/Assembly.d.ts +3 -0
- package/dist/src/components/ErrorBoundary.d.ts +25 -0
- package/dist/src/components/Flex.d.ts +64 -0
- package/dist/src/constants.d.ts +3 -0
- package/dist/src/core/componentRegistry.d.ts +23 -0
- package/dist/src/core/componentRegistry.test.d.ts +1 -0
- package/dist/src/core/index.d.ts +1 -0
- package/dist/src/core/preview/assemblyUtils.d.ts +10 -0
- package/dist/src/core/preview/assemblyUtils.spec.d.ts +1 -0
- package/dist/src/hooks/index.d.ts +5 -0
- package/dist/src/hooks/useBreakpoints.d.ts +4 -0
- package/dist/src/hooks/useBreakpoints.spec.d.ts +1 -0
- package/dist/src/hooks/useDetectEditorMode.d.ts +7 -0
- package/dist/src/hooks/useExperienceBuilder.d.ts +41 -0
- package/dist/src/hooks/useExperienceBuilder.test.d.ts +1 -0
- package/dist/src/hooks/useFetchByBase.d.ts +8 -0
- package/dist/src/hooks/useFetchById.d.ts +16 -0
- package/dist/src/hooks/useFetchById.spec.d.ts +1 -0
- package/dist/src/hooks/useFetchBySlug.d.ts +16 -0
- package/dist/src/hooks/useFetchBySlug.spec.d.ts +1 -0
- package/dist/src/hooks/useFetchExperience.d.ts +27 -0
- package/dist/src/hooks/useFetchExperience.test.d.ts +1 -0
- package/dist/src/hooks/useInitializeVisualEditor.d.ts +7 -0
- package/dist/src/hooks/usePrevious.d.ts +6 -0
- package/dist/src/hooks/useStyleTag.d.ts +16 -0
- package/dist/src/index.d.ts +19 -0
- package/dist/src/sdkVersion.d.ts +1 -0
- package/dist/src/utils/withComponentWrapper.d.ts +36 -0
- package/dist/src/utils/withComponentWrapper.spec.d.ts +1 -0
- package/dist/test/__fixtures__/assembly.d.ts +96 -0
- package/dist/test/__fixtures__/composition.d.ts +7 -0
- package/dist/test/__fixtures__/entities.d.ts +9 -0
- package/dist/test/components/Test.d.ts +1 -0
- package/dist/test/components/Test.spec.d.ts +1 -0
- package/dist/vite.config.d.ts +2 -0
- package/package.json +92 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,859 @@
|
|
|
1
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
2
|
+
import { containerDefinition, sectionDefinition, columnsDefinition, singleColumnDefinition, builtInStyles, optionalBuiltInStyles, sendMessage, designTokensRegistry, buildStyleTag, checkIsAssemblyNode, isDeepPath, transformContentValue, buildCfStyles, isEmptyStructureWithRelativeHeight, mediaQueryMatcher, getFallbackBreakpointIndex, getActiveBreakpointIndex, getValueForBreakpoint, fetchBySlug, fetchById, doesMismatchMessageSchema, tryParseMessage, validateExperienceBuilderConfig, isDeprecatedExperience, VisualEditorMode } from '@contentful/experiences-core';
|
|
3
|
+
export { EntityStore, VisualEditorMode, calculateNodeDefaultHeight, checkIsAssembly, checkIsAssemblyEntry, checkIsAssemblyNode, createExperience, defineDesignTokens, fetchById, fetchBySlug, supportedModes } from '@contentful/experiences-core';
|
|
4
|
+
import React, { useState, useEffect, useMemo, useCallback, useRef, Suspense } from 'react';
|
|
5
|
+
import { omit } from 'lodash-es';
|
|
6
|
+
import { INTERNAL_EVENTS, CONTENTFUL_COMPONENTS, OUTGOING_EVENTS, ASSEMBLY_DEFAULT_CATEGORY, EMPTY_CONTAINER_HEIGHT, CF_STYLE_ATTRIBUTES, INCOMING_EVENTS, VISUAL_EDITOR_EVENTS } from '@contentful/experiences-core/constants';
|
|
7
|
+
export { ASSEMBLY_BLOCK_NODE_TYPE, ASSEMBLY_NODE_TYPE, ASSEMBLY_NODE_TYPES, CF_STYLE_ATTRIBUTES, CONTENTFUL_COMPONENTS, CONTENTFUL_COMPONENT_CATEGORY, CONTENTFUL_CONTAINER_ID, CONTENTFUL_SECTION_ID, DESIGN_COMPONENT_BLOCK_NODE_TYPE, DESIGN_COMPONENT_NODE_TYPE, DESIGN_COMPONENT_NODE_TYPES, INCOMING_EVENTS, LATEST_SCHEMA_VERSION, OUTGOING_EVENTS, SCROLL_STATES } from '@contentful/experiences-core/constants';
|
|
8
|
+
import * as Components from '@contentful/experiences-components-react';
|
|
9
|
+
import { ContentfulContainer, Columns, SingleColumn } from '@contentful/experiences-components-react';
|
|
10
|
+
import styleInject from 'style-inject';
|
|
11
|
+
|
|
12
|
+
const SDK_VERSION = '0.0.1-alpha.1';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Sets up a component to be consumed by Experience Builder. This function can be used to wrap a component with a container component, or to pass props to the component directly.
|
|
16
|
+
* @param Component Component to be used by Experience Builder.
|
|
17
|
+
* @param options Options for the `withComponentWrapper` function.
|
|
18
|
+
* @default { wrapComponent: true, wrapContainerTag: 'div' }
|
|
19
|
+
* @returns A component that can be passed to `defineComponents`.
|
|
20
|
+
*/
|
|
21
|
+
function withComponentWrapper(Component, options = {
|
|
22
|
+
wrapComponent: true,
|
|
23
|
+
wrapContainerTag: 'div',
|
|
24
|
+
}) {
|
|
25
|
+
const Wrapped = ({ classes = '', className = '', 'data-cf-node-id': dataCfNodeId, 'data-cf-node-block-id': dataCfNodeBlockId, 'data-cf-node-block-type': dataCfNodeBlockType, onClick, onMouseDown, onMouseUp, ...props }) => {
|
|
26
|
+
const Tag = options.wrapContainerTag || 'div';
|
|
27
|
+
const cfProps = {
|
|
28
|
+
'data-cf-node-id': dataCfNodeId,
|
|
29
|
+
'data-cf-node-block-id': dataCfNodeBlockId,
|
|
30
|
+
'data-cf-node-block-type': dataCfNodeBlockType,
|
|
31
|
+
onClick,
|
|
32
|
+
onMouseDown,
|
|
33
|
+
onMouseUp,
|
|
34
|
+
};
|
|
35
|
+
const component = options.wrapComponent ? (jsx(Tag, { className: className, ...cfProps, children: typeof Component === 'string' ? (React.createElement(Component, { className: classes, ...props })) : (jsx(Component, { className: classes, ...props })) })) : (React.createElement(Component, {
|
|
36
|
+
className: classes + className ? classes + ' ' + className : undefined,
|
|
37
|
+
...cfProps,
|
|
38
|
+
...props,
|
|
39
|
+
}));
|
|
40
|
+
return component;
|
|
41
|
+
};
|
|
42
|
+
return Wrapped;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// this is the array of version which currently LATEST_SCHEMA_VERSION is compatible with
|
|
46
|
+
const compatibleVersions = ['2023-08-23', '2023-09-28'];
|
|
47
|
+
|
|
48
|
+
const cloneObject = (targetObject) => {
|
|
49
|
+
if (typeof structuredClone !== 'undefined') {
|
|
50
|
+
return structuredClone(targetObject);
|
|
51
|
+
}
|
|
52
|
+
return JSON.parse(JSON.stringify(targetObject));
|
|
53
|
+
};
|
|
54
|
+
const applyComponentDefinitionFallbacks = (componentDefinition) => {
|
|
55
|
+
const clone = cloneObject(componentDefinition);
|
|
56
|
+
for (const variable of Object.values(clone.variables)) {
|
|
57
|
+
variable.group = variable.group ?? 'content';
|
|
58
|
+
}
|
|
59
|
+
return clone;
|
|
60
|
+
};
|
|
61
|
+
const applyBuiltInStyleDefinitions = (componentDefinition) => {
|
|
62
|
+
if ([CONTENTFUL_COMPONENTS.container.id].includes(componentDefinition.id)) {
|
|
63
|
+
return componentDefinition;
|
|
64
|
+
}
|
|
65
|
+
const clone = cloneObject(componentDefinition);
|
|
66
|
+
// set margin built-in style by default
|
|
67
|
+
if (!clone.builtInStyles) {
|
|
68
|
+
clone.builtInStyles = ['cfMargin'];
|
|
69
|
+
}
|
|
70
|
+
for (const style of Object.values(clone.builtInStyles || [])) {
|
|
71
|
+
if (builtInStyles[style]) {
|
|
72
|
+
clone.variables[style] = builtInStyles[style];
|
|
73
|
+
}
|
|
74
|
+
if (optionalBuiltInStyles[style]) {
|
|
75
|
+
clone.variables[style] = optionalBuiltInStyles[style];
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return clone;
|
|
79
|
+
};
|
|
80
|
+
const enrichComponentDefinition = ({ component, definition, options, }) => {
|
|
81
|
+
const definitionWithFallbacks = applyComponentDefinitionFallbacks(definition);
|
|
82
|
+
const definitionWithBuiltInStyles = applyBuiltInStyleDefinitions(definitionWithFallbacks);
|
|
83
|
+
return {
|
|
84
|
+
component: withComponentWrapper(component, options),
|
|
85
|
+
definition: definitionWithBuiltInStyles,
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
const DEFAULT_COMPONENT_REGISTRATIONS = {
|
|
89
|
+
container: {
|
|
90
|
+
component: Components.ContentfulContainer,
|
|
91
|
+
definition: containerDefinition,
|
|
92
|
+
},
|
|
93
|
+
section: {
|
|
94
|
+
component: Components.ContentfulContainer,
|
|
95
|
+
definition: sectionDefinition,
|
|
96
|
+
},
|
|
97
|
+
columns: {
|
|
98
|
+
component: Components.Columns,
|
|
99
|
+
definition: columnsDefinition,
|
|
100
|
+
},
|
|
101
|
+
singleColumn: {
|
|
102
|
+
component: Components.SingleColumn,
|
|
103
|
+
definition: singleColumnDefinition,
|
|
104
|
+
},
|
|
105
|
+
button: enrichComponentDefinition({
|
|
106
|
+
component: Components.Button,
|
|
107
|
+
definition: Components.ButtonComponentDefinition,
|
|
108
|
+
options: {
|
|
109
|
+
wrapComponent: false,
|
|
110
|
+
},
|
|
111
|
+
}),
|
|
112
|
+
heading: enrichComponentDefinition({
|
|
113
|
+
component: Components.Heading,
|
|
114
|
+
definition: Components.HeadingComponentDefinition,
|
|
115
|
+
options: {
|
|
116
|
+
wrapComponent: false,
|
|
117
|
+
},
|
|
118
|
+
}),
|
|
119
|
+
image: enrichComponentDefinition({
|
|
120
|
+
component: Components.Image,
|
|
121
|
+
definition: Components.ImageComponentDefinition,
|
|
122
|
+
}),
|
|
123
|
+
richText: enrichComponentDefinition({
|
|
124
|
+
component: Components.RichText,
|
|
125
|
+
definition: Components.RichTextComponentDefinition,
|
|
126
|
+
options: {
|
|
127
|
+
wrapComponent: false,
|
|
128
|
+
},
|
|
129
|
+
}),
|
|
130
|
+
text: enrichComponentDefinition({
|
|
131
|
+
component: Components.Text,
|
|
132
|
+
definition: Components.TextComponentDefinition,
|
|
133
|
+
options: {
|
|
134
|
+
wrapComponent: false,
|
|
135
|
+
},
|
|
136
|
+
}),
|
|
137
|
+
};
|
|
138
|
+
// pre-filling with the default component registrations
|
|
139
|
+
const componentRegistry = new Map([
|
|
140
|
+
[DEFAULT_COMPONENT_REGISTRATIONS.section.definition.id, DEFAULT_COMPONENT_REGISTRATIONS.section],
|
|
141
|
+
[
|
|
142
|
+
DEFAULT_COMPONENT_REGISTRATIONS.container.definition.id,
|
|
143
|
+
DEFAULT_COMPONENT_REGISTRATIONS.container,
|
|
144
|
+
],
|
|
145
|
+
[
|
|
146
|
+
DEFAULT_COMPONENT_REGISTRATIONS.singleColumn.definition.id,
|
|
147
|
+
DEFAULT_COMPONENT_REGISTRATIONS.singleColumn,
|
|
148
|
+
],
|
|
149
|
+
[DEFAULT_COMPONENT_REGISTRATIONS.columns.definition.id, DEFAULT_COMPONENT_REGISTRATIONS.columns],
|
|
150
|
+
[DEFAULT_COMPONENT_REGISTRATIONS.button.definition.id, DEFAULT_COMPONENT_REGISTRATIONS.button],
|
|
151
|
+
[DEFAULT_COMPONENT_REGISTRATIONS.heading.definition.id, DEFAULT_COMPONENT_REGISTRATIONS.heading],
|
|
152
|
+
[DEFAULT_COMPONENT_REGISTRATIONS.image.definition.id, DEFAULT_COMPONENT_REGISTRATIONS.image],
|
|
153
|
+
[
|
|
154
|
+
DEFAULT_COMPONENT_REGISTRATIONS.richText.definition.id,
|
|
155
|
+
DEFAULT_COMPONENT_REGISTRATIONS.richText,
|
|
156
|
+
],
|
|
157
|
+
[DEFAULT_COMPONENT_REGISTRATIONS.text.definition.id, DEFAULT_COMPONENT_REGISTRATIONS.text],
|
|
158
|
+
]);
|
|
159
|
+
const optionalBuiltInComponents = [
|
|
160
|
+
DEFAULT_COMPONENT_REGISTRATIONS.button.definition.id,
|
|
161
|
+
DEFAULT_COMPONENT_REGISTRATIONS.heading.definition.id,
|
|
162
|
+
DEFAULT_COMPONENT_REGISTRATIONS.image.definition.id,
|
|
163
|
+
DEFAULT_COMPONENT_REGISTRATIONS.richText.definition.id,
|
|
164
|
+
DEFAULT_COMPONENT_REGISTRATIONS.text.definition.id,
|
|
165
|
+
];
|
|
166
|
+
const sendRegisteredComponentsMessage = () => {
|
|
167
|
+
// Send the definitions (without components) via the connection message to the experience builder
|
|
168
|
+
const registeredDefinitions = Array.from(componentRegistry.values());
|
|
169
|
+
sendMessage(OUTGOING_EVENTS.RegisteredComponents, {
|
|
170
|
+
definitions: registeredDefinitions,
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
const sendConnectedEventWithRegisteredComponents = () => {
|
|
174
|
+
// Send the definitions (without components) via the connection message to the experience builder
|
|
175
|
+
const registeredDefinitions = Array.from(componentRegistry.values()).map(({ definition }) => definition);
|
|
176
|
+
sendMessage(OUTGOING_EVENTS.Connected, {
|
|
177
|
+
sdkVersion: SDK_VERSION,
|
|
178
|
+
definitions: registeredDefinitions,
|
|
179
|
+
});
|
|
180
|
+
sendMessage(OUTGOING_EVENTS.DesignTokens, {
|
|
181
|
+
designTokens: designTokensRegistry,
|
|
182
|
+
});
|
|
183
|
+
};
|
|
184
|
+
/**
|
|
185
|
+
* Registers multiple components and their component definitions at once
|
|
186
|
+
* @param componentRegistrations - ComponentRegistration[]
|
|
187
|
+
* @returns void
|
|
188
|
+
*/
|
|
189
|
+
const defineComponents = (componentRegistrations, options) => {
|
|
190
|
+
if (options?.enabledBuiltInComponents) {
|
|
191
|
+
for (const id of optionalBuiltInComponents) {
|
|
192
|
+
if (!options.enabledBuiltInComponents.includes(id)) {
|
|
193
|
+
componentRegistry.delete(id);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
for (const registration of componentRegistrations) {
|
|
198
|
+
// Fill definitions with fallbacks values
|
|
199
|
+
const enrichedComponentRegistration = enrichComponentDefinition(registration);
|
|
200
|
+
componentRegistry.set(enrichedComponentRegistration.definition.id, enrichedComponentRegistration);
|
|
201
|
+
}
|
|
202
|
+
if (typeof window !== 'undefined') {
|
|
203
|
+
window.dispatchEvent(new CustomEvent(INTERNAL_EVENTS.ComponentsRegistered));
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const getComponentRegistration = (id) => componentRegistry.get(id);
|
|
207
|
+
const addComponentRegistration = (componentRegistration) => {
|
|
208
|
+
componentRegistry.set(componentRegistration.definition.id, componentRegistration);
|
|
209
|
+
};
|
|
210
|
+
const createAssemblyRegistration = ({ definitionId, definitionName, component, }) => {
|
|
211
|
+
const componentRegistration = componentRegistry.get(definitionId);
|
|
212
|
+
if (componentRegistration) {
|
|
213
|
+
return componentRegistration;
|
|
214
|
+
}
|
|
215
|
+
const definition = {
|
|
216
|
+
id: definitionId,
|
|
217
|
+
name: definitionName || 'Component',
|
|
218
|
+
variables: {},
|
|
219
|
+
children: true,
|
|
220
|
+
category: ASSEMBLY_DEFAULT_CATEGORY,
|
|
221
|
+
};
|
|
222
|
+
addComponentRegistration({ component, definition });
|
|
223
|
+
return componentRegistry.get(definitionId);
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
*
|
|
228
|
+
* @param styles: the list of styles to apply
|
|
229
|
+
* @param nodeId: [Optional] the id of node that these styles will be applied to
|
|
230
|
+
* @returns className: the className that was used
|
|
231
|
+
* Builds and adds a style tag in the document. Returns the className to be attached to the element.
|
|
232
|
+
* In editor mode the nodeId is used as the identifier in order to avoid creating endless tags as the styles are tweeked
|
|
233
|
+
* In preview/delivery mode the styles don't change oftem so we're using the md5 hash of the content of the tag
|
|
234
|
+
*/
|
|
235
|
+
const useStyleTag = ({ styles, nodeId }) => {
|
|
236
|
+
const [className, setClassName] = useState('');
|
|
237
|
+
useEffect(() => {
|
|
238
|
+
if (Object.keys(styles).length === 0) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
const [className, styleRule] = buildStyleTag({ styles, nodeId });
|
|
242
|
+
setClassName(className);
|
|
243
|
+
const existingTag = document.querySelector(`[data-cf-styles="${className}"]`);
|
|
244
|
+
if (existingTag) {
|
|
245
|
+
// editor mode - update existing
|
|
246
|
+
if (nodeId) {
|
|
247
|
+
existingTag.innerHTML = styleRule;
|
|
248
|
+
}
|
|
249
|
+
// preview/delivery mode - here we don't need to update the existing tag because
|
|
250
|
+
// the className is based on the md5 hash of the content so it hasn't changed
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const styleTag = document.createElement('style');
|
|
254
|
+
styleTag.dataset['cfStyles'] = className;
|
|
255
|
+
document.head.appendChild(styleTag).innerHTML = styleRule;
|
|
256
|
+
}, [styles, nodeId]);
|
|
257
|
+
return { className };
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const deserializeAssemblyNode = ({ node, componentInstanceVariables, }) => {
|
|
261
|
+
const variables = {};
|
|
262
|
+
for (const [variableName, variable] of Object.entries(node.variables)) {
|
|
263
|
+
variables[variableName] = variable;
|
|
264
|
+
if (variable.type === 'ComponentValue') {
|
|
265
|
+
const componentValueKey = variable.key;
|
|
266
|
+
const instanceProperty = componentInstanceVariables[componentValueKey];
|
|
267
|
+
// For assembly, we look up the variable in the assembly instance and
|
|
268
|
+
// replace the componentValue with that one.
|
|
269
|
+
if (instanceProperty?.type === 'UnboundValue') {
|
|
270
|
+
variables[variableName] = {
|
|
271
|
+
type: 'UnboundValue',
|
|
272
|
+
key: instanceProperty.key,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
else if (instanceProperty?.type === 'BoundValue') {
|
|
276
|
+
variables[variableName] = {
|
|
277
|
+
type: 'BoundValue',
|
|
278
|
+
path: instanceProperty.path,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const children = node.children.map((child) => deserializeAssemblyNode({
|
|
284
|
+
node: child,
|
|
285
|
+
componentInstanceVariables,
|
|
286
|
+
}));
|
|
287
|
+
return {
|
|
288
|
+
definitionId: node.definitionId,
|
|
289
|
+
variables,
|
|
290
|
+
children,
|
|
291
|
+
};
|
|
292
|
+
};
|
|
293
|
+
const resolveAssembly = ({ node, entityStore, }) => {
|
|
294
|
+
const isAssembly = checkIsAssemblyNode({
|
|
295
|
+
componentId: node.definitionId,
|
|
296
|
+
usedComponents: entityStore.usedComponents,
|
|
297
|
+
});
|
|
298
|
+
if (!isAssembly) {
|
|
299
|
+
return node;
|
|
300
|
+
}
|
|
301
|
+
const componentId = node.definitionId;
|
|
302
|
+
const assembly = entityStore.experienceEntryFields?.usedComponents?.find((component) => component.sys.id === componentId);
|
|
303
|
+
if (!assembly || !('fields' in assembly)) {
|
|
304
|
+
return node;
|
|
305
|
+
}
|
|
306
|
+
const componentFields = assembly.fields;
|
|
307
|
+
const deserializedNode = deserializeAssemblyNode({
|
|
308
|
+
node: {
|
|
309
|
+
definitionId: node.definitionId,
|
|
310
|
+
variables: {},
|
|
311
|
+
children: componentFields.componentTree.children,
|
|
312
|
+
},
|
|
313
|
+
componentInstanceVariables: node.variables,
|
|
314
|
+
});
|
|
315
|
+
entityStore.addAssemblyUnboundValues(componentFields.unboundValues);
|
|
316
|
+
return deserializedNode;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
const assemblyStyle = { display: 'contents' };
|
|
320
|
+
// Feel free to do any magic as regards variable definitions for assemblies
|
|
321
|
+
// Or if this isn't necessary by the time we figure that part out, we can bid this part farewell
|
|
322
|
+
const Assembly = ({ ...props }) => {
|
|
323
|
+
// Using a display contents so assembly content/children
|
|
324
|
+
// can appear as if they are direct children of the div wrapper's parent
|
|
325
|
+
return jsx("div", { "data-test-id": "assembly", ...props, style: assemblyStyle });
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const CompositionBlock = ({ node: rawNode, locale, entityStore, resolveDesignValue, }) => {
|
|
329
|
+
const isAssembly = useMemo(() => checkIsAssemblyNode({
|
|
330
|
+
componentId: rawNode.definitionId,
|
|
331
|
+
usedComponents: entityStore.usedComponents,
|
|
332
|
+
}), [entityStore.usedComponents, rawNode.definitionId]);
|
|
333
|
+
const node = useMemo(() => {
|
|
334
|
+
return isAssembly
|
|
335
|
+
? resolveAssembly({
|
|
336
|
+
node: rawNode,
|
|
337
|
+
entityStore,
|
|
338
|
+
})
|
|
339
|
+
: rawNode;
|
|
340
|
+
}, [entityStore, isAssembly, rawNode]);
|
|
341
|
+
const componentRegistration = useMemo(() => {
|
|
342
|
+
const registration = getComponentRegistration(node.definitionId);
|
|
343
|
+
if (isAssembly && !registration) {
|
|
344
|
+
return createAssemblyRegistration({
|
|
345
|
+
definitionId: node.definitionId,
|
|
346
|
+
component: Assembly,
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
return registration;
|
|
350
|
+
}, [isAssembly, node.definitionId]);
|
|
351
|
+
const nodeProps = useMemo(() => {
|
|
352
|
+
// Don't enrich the assembly wrapper node with props
|
|
353
|
+
if (!componentRegistration || isAssembly) {
|
|
354
|
+
return {};
|
|
355
|
+
}
|
|
356
|
+
const propMap = {};
|
|
357
|
+
return Object.entries(node.variables).reduce((acc, [variableName, variable]) => {
|
|
358
|
+
switch (variable.type) {
|
|
359
|
+
case 'DesignValue':
|
|
360
|
+
acc[variableName] = resolveDesignValue(variable.valuesByBreakpoint, variableName);
|
|
361
|
+
break;
|
|
362
|
+
case 'BoundValue': {
|
|
363
|
+
const variableDefinition = componentRegistration.definition.variables[variableName];
|
|
364
|
+
if (isDeepPath(variable.path)) {
|
|
365
|
+
const [, uuid] = variable.path.split('/');
|
|
366
|
+
const link = entityStore.dataSource[uuid];
|
|
367
|
+
const boundValue = entityStore.getValueDeep(link, variable.path);
|
|
368
|
+
const value = boundValue || variableDefinition.defaultValue;
|
|
369
|
+
acc[variableName] = transformContentValue(value, variableDefinition);
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
const [, uuid, ...path] = variable.path.split('/');
|
|
373
|
+
const binding = entityStore.dataSource[uuid];
|
|
374
|
+
let value = entityStore.getValue(binding, path.slice(0, -1));
|
|
375
|
+
if (!value) {
|
|
376
|
+
const foundAssetValue = entityStore.getValue(binding, [
|
|
377
|
+
...path.slice(0, -2),
|
|
378
|
+
'fields',
|
|
379
|
+
'file',
|
|
380
|
+
]);
|
|
381
|
+
if (foundAssetValue) {
|
|
382
|
+
value = foundAssetValue;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
acc[variableName] = transformContentValue(value, variableDefinition);
|
|
386
|
+
break;
|
|
387
|
+
}
|
|
388
|
+
case 'UnboundValue': {
|
|
389
|
+
const uuid = variable.key;
|
|
390
|
+
acc[variableName] = entityStore.unboundValues[uuid]?.value;
|
|
391
|
+
break;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
return acc;
|
|
395
|
+
}, propMap);
|
|
396
|
+
}, [componentRegistration, isAssembly, node.variables, resolveDesignValue, entityStore]);
|
|
397
|
+
const cfStyles = buildCfStyles(nodeProps);
|
|
398
|
+
if (isEmptyStructureWithRelativeHeight(node.children.length, node.definitionId, cfStyles.height)) {
|
|
399
|
+
cfStyles.minHeight = EMPTY_CONTAINER_HEIGHT;
|
|
400
|
+
}
|
|
401
|
+
const { className } = useStyleTag({ styles: cfStyles });
|
|
402
|
+
if (!componentRegistration) {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
const { component } = componentRegistration;
|
|
406
|
+
const children = componentRegistration.definition.children === true
|
|
407
|
+
? node.children.map((childNode, index) => {
|
|
408
|
+
return (jsx(CompositionBlock, { node: childNode, locale: locale, entityStore: entityStore, resolveDesignValue: resolveDesignValue }, index));
|
|
409
|
+
})
|
|
410
|
+
: null;
|
|
411
|
+
if ([CONTENTFUL_COMPONENTS.container.id, CONTENTFUL_COMPONENTS.section.id].includes(node.definitionId)) {
|
|
412
|
+
return (jsx(ContentfulContainer, { editorMode: false, cfHyperlink: nodeProps.cfHyperlink, cfOpenInNewTab: nodeProps.cfOpenInNewTab, className: className, children: children }));
|
|
413
|
+
}
|
|
414
|
+
if (node.definitionId === CONTENTFUL_COMPONENTS.columns.id) {
|
|
415
|
+
return (jsx(Columns, { editorMode: false, className: className, children: children }));
|
|
416
|
+
}
|
|
417
|
+
if (node.definitionId === CONTENTFUL_COMPONENTS.singleColumn.id) {
|
|
418
|
+
return (jsx(SingleColumn, { editorMode: false, className: className, children: children }));
|
|
419
|
+
}
|
|
420
|
+
return React.createElement(component, {
|
|
421
|
+
...omit(nodeProps, CF_STYLE_ATTRIBUTES, ['cfHyperlink', 'cfOpenInNewTab']),
|
|
422
|
+
className,
|
|
423
|
+
}, children);
|
|
424
|
+
};
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* @deprecated This hook is deprecated. Use fetchBySlug or fetchById instead
|
|
428
|
+
*/
|
|
429
|
+
const useExperienceBuilder = ({ experienceTypeId, client, mode = 'delivery', }) => {
|
|
430
|
+
const experience = useMemo(() => ({
|
|
431
|
+
client,
|
|
432
|
+
experienceTypeId,
|
|
433
|
+
mode,
|
|
434
|
+
}), [mode, client, experienceTypeId]);
|
|
435
|
+
return {
|
|
436
|
+
/**
|
|
437
|
+
* @deprecated please fetch the experience using `useFetchExperience` hook or fetch the data manually using `fetchers` or `client` and create experience with `createExperience` function
|
|
438
|
+
*
|
|
439
|
+
* @example
|
|
440
|
+
*
|
|
441
|
+
* import { useFetchExperience } from '@contentful/experiences-sdk-react'
|
|
442
|
+
*
|
|
443
|
+
* const { fetchBySlug, fetchById, experience, isFetching } = useFetchExperience({ client, mode })
|
|
444
|
+
*/
|
|
445
|
+
experience,
|
|
446
|
+
/**
|
|
447
|
+
* @deprecated please import the function from the library
|
|
448
|
+
*
|
|
449
|
+
* @example
|
|
450
|
+
*
|
|
451
|
+
* import { defineComponents } from '@contentful/experiences-sdk-react'
|
|
452
|
+
*/
|
|
453
|
+
defineComponents,
|
|
454
|
+
};
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
// TODO: In order to support integrations without React, we should extract this heavy logic into simple
|
|
458
|
+
// functions that we can reuse in other frameworks.
|
|
459
|
+
/*
|
|
460
|
+
* Registers media query change listeners for each breakpoint (except for "*").
|
|
461
|
+
* It will always assume the last matching media query in the list. It therefore,
|
|
462
|
+
* assumes that the breakpoints are sorted beginning with the default value (query: "*")
|
|
463
|
+
* and then decending by screen width. For mobile-first designs, the order would be ascending
|
|
464
|
+
*/
|
|
465
|
+
const useBreakpoints = (breakpoints) => {
|
|
466
|
+
const [mediaQueryMatchers, initialMediaQueryMatches] = mediaQueryMatcher(breakpoints);
|
|
467
|
+
const [mediaQueryMatches, setMediaQueryMatches] = useState(initialMediaQueryMatches);
|
|
468
|
+
const fallbackBreakpointIndex = getFallbackBreakpointIndex(breakpoints);
|
|
469
|
+
// Register event listeners to update the media query states
|
|
470
|
+
useEffect(() => {
|
|
471
|
+
const eventListeners = mediaQueryMatchers.map(({ id, signal }) => {
|
|
472
|
+
const onChange = () => setMediaQueryMatches((prev) => ({
|
|
473
|
+
...prev,
|
|
474
|
+
[id]: signal.matches,
|
|
475
|
+
}));
|
|
476
|
+
signal.addEventListener('change', onChange);
|
|
477
|
+
return onChange;
|
|
478
|
+
});
|
|
479
|
+
return () => {
|
|
480
|
+
eventListeners.forEach((eventListener, index) => {
|
|
481
|
+
mediaQueryMatchers[index].signal.removeEventListener('change', eventListener);
|
|
482
|
+
});
|
|
483
|
+
};
|
|
484
|
+
}, [mediaQueryMatchers]);
|
|
485
|
+
const activeBreakpointIndex = getActiveBreakpointIndex(breakpoints, mediaQueryMatches, fallbackBreakpointIndex);
|
|
486
|
+
const resolveDesignValue = useCallback((valuesByBreakpoint, variableName) => {
|
|
487
|
+
return getValueForBreakpoint(valuesByBreakpoint, breakpoints, activeBreakpointIndex, variableName);
|
|
488
|
+
}, [activeBreakpointIndex, breakpoints]);
|
|
489
|
+
return { resolveDesignValue };
|
|
490
|
+
};
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* @deprecated please use `useFetchBySlug` or `useFetchById` hooks instead
|
|
494
|
+
*/
|
|
495
|
+
const useFetchExperience = ({ client }) => {
|
|
496
|
+
const [experience, setExperience] = useState(undefined);
|
|
497
|
+
const [isFetching, setIsFetching] = useState(false);
|
|
498
|
+
const [error, setError] = useState();
|
|
499
|
+
/**
|
|
500
|
+
* Fetch experience entry using slug as the identifier
|
|
501
|
+
* @param {string} experienceTypeId - id of the content type associated with the experience
|
|
502
|
+
* @param {string} slug - slug of the experience (defined in entry settings)
|
|
503
|
+
* @param {string} localeCode - locale code to fetch the experience. Falls back to the currently active locale in the state
|
|
504
|
+
*/
|
|
505
|
+
const fetchBySlug$1 = useCallback(async ({ experienceTypeId, slug, localeCode, }) => {
|
|
506
|
+
setIsFetching(true);
|
|
507
|
+
setError(undefined);
|
|
508
|
+
try {
|
|
509
|
+
const experience = await fetchBySlug({
|
|
510
|
+
client,
|
|
511
|
+
experienceTypeId,
|
|
512
|
+
localeCode,
|
|
513
|
+
slug,
|
|
514
|
+
});
|
|
515
|
+
setExperience(experience);
|
|
516
|
+
return experience;
|
|
517
|
+
}
|
|
518
|
+
catch (error) {
|
|
519
|
+
setError(error);
|
|
520
|
+
}
|
|
521
|
+
finally {
|
|
522
|
+
setIsFetching(false);
|
|
523
|
+
}
|
|
524
|
+
}, [client]);
|
|
525
|
+
/**
|
|
526
|
+
* Fetch experience entry using id as the identifier
|
|
527
|
+
* @param {string} experienceTypeId - id of the content type associated with the experience
|
|
528
|
+
* @param {string} id - id of the experience (defined in entry settings)
|
|
529
|
+
* @param {string} localeCode - locale code to fetch the experience. Falls back to the currently active locale in the state
|
|
530
|
+
*/
|
|
531
|
+
const fetchById$1 = useCallback(async ({ experienceTypeId, id, localeCode, }) => {
|
|
532
|
+
setIsFetching(true);
|
|
533
|
+
setError(undefined);
|
|
534
|
+
try {
|
|
535
|
+
const experience = await fetchById({
|
|
536
|
+
client,
|
|
537
|
+
experienceTypeId,
|
|
538
|
+
localeCode,
|
|
539
|
+
id,
|
|
540
|
+
});
|
|
541
|
+
setExperience(experience);
|
|
542
|
+
return experience;
|
|
543
|
+
}
|
|
544
|
+
catch (error) {
|
|
545
|
+
setError(error);
|
|
546
|
+
}
|
|
547
|
+
finally {
|
|
548
|
+
setIsFetching(false);
|
|
549
|
+
}
|
|
550
|
+
}, [client]);
|
|
551
|
+
return {
|
|
552
|
+
fetchBySlug: fetchBySlug$1,
|
|
553
|
+
fetchById: fetchById$1,
|
|
554
|
+
error,
|
|
555
|
+
experience,
|
|
556
|
+
isFetching,
|
|
557
|
+
};
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
const useFetchByBase = (fetchMethod, isEditorMode) => {
|
|
561
|
+
const [experience, setExperience] = useState();
|
|
562
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
563
|
+
const [error, setError] = useState();
|
|
564
|
+
useEffect(() => {
|
|
565
|
+
(async () => {
|
|
566
|
+
// if we are in editor mode, we don't want to fetch the experience here
|
|
567
|
+
// it is passed via postMessage instead
|
|
568
|
+
if (isEditorMode) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
setIsLoading(true);
|
|
572
|
+
setError(undefined);
|
|
573
|
+
try {
|
|
574
|
+
const exp = await fetchMethod();
|
|
575
|
+
setExperience(exp);
|
|
576
|
+
}
|
|
577
|
+
catch (error) {
|
|
578
|
+
setError(error);
|
|
579
|
+
}
|
|
580
|
+
finally {
|
|
581
|
+
setIsLoading(false);
|
|
582
|
+
}
|
|
583
|
+
})();
|
|
584
|
+
}, [fetchMethod, isEditorMode]);
|
|
585
|
+
return {
|
|
586
|
+
error,
|
|
587
|
+
experience,
|
|
588
|
+
isLoading,
|
|
589
|
+
isEditorMode,
|
|
590
|
+
};
|
|
591
|
+
};
|
|
592
|
+
|
|
593
|
+
const useDetectEditorMode = ({ isClientSide = false } = {}) => {
|
|
594
|
+
const [mounted, setMounted] = useState(false);
|
|
595
|
+
const [isEditorMode, setIsEditorMode] = useState(isClientSide ? inIframe() : false);
|
|
596
|
+
const receivedMessage = useRef(false);
|
|
597
|
+
useEffect(() => {
|
|
598
|
+
const onMessage = (event) => {
|
|
599
|
+
if (doesMismatchMessageSchema(event)) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const eventData = tryParseMessage(event);
|
|
603
|
+
if (eventData.eventType === INCOMING_EVENTS.RequestEditorMode) {
|
|
604
|
+
setIsEditorMode(true);
|
|
605
|
+
receivedMessage.current = true;
|
|
606
|
+
if (typeof window !== 'undefined') {
|
|
607
|
+
//Once we definitely know that we are in editor mode, we set this flag so future postMessage connect calls are not made
|
|
608
|
+
window.__EB__.isEditorMode = true;
|
|
609
|
+
window.removeEventListener('message', onMessage);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
//Only run check after component is mounted on the client to avoid hydration ssr issues
|
|
614
|
+
if (mounted) {
|
|
615
|
+
setIsEditorMode(inIframe());
|
|
616
|
+
//Double check if we are in editor mode by listening to postMessage events
|
|
617
|
+
if (typeof window !== 'undefined' && !window.__EB__?.isEditorMode) {
|
|
618
|
+
window.addEventListener('message', onMessage);
|
|
619
|
+
sendMessage(OUTGOING_EVENTS.Connected);
|
|
620
|
+
setTimeout(() => {
|
|
621
|
+
if (!receivedMessage.current) {
|
|
622
|
+
// if message is not received back in time, set editorMode back to false
|
|
623
|
+
setIsEditorMode(false);
|
|
624
|
+
}
|
|
625
|
+
}, 100);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
else {
|
|
629
|
+
setMounted(true);
|
|
630
|
+
}
|
|
631
|
+
return () => window.removeEventListener('message', onMessage);
|
|
632
|
+
}, [mounted]);
|
|
633
|
+
return isEditorMode;
|
|
634
|
+
};
|
|
635
|
+
function inIframe() {
|
|
636
|
+
try {
|
|
637
|
+
return window.self !== window.top;
|
|
638
|
+
}
|
|
639
|
+
catch (e) {
|
|
640
|
+
return false;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const useFetchById = ({ id, localeCode, client, experienceTypeId }) => {
|
|
645
|
+
const isEditorMode = useDetectEditorMode({ isClientSide: typeof window !== 'undefined' });
|
|
646
|
+
const fetchMethod = useCallback(() => {
|
|
647
|
+
return fetchById({ id, localeCode, client, experienceTypeId });
|
|
648
|
+
}, [id, localeCode, client, experienceTypeId]);
|
|
649
|
+
return useFetchByBase(fetchMethod, isEditorMode);
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
const useFetchBySlug = ({ slug, localeCode, client, experienceTypeId, }) => {
|
|
653
|
+
const isEditorMode = useDetectEditorMode({ isClientSide: typeof window !== 'undefined' });
|
|
654
|
+
const fetchMethod = useCallback(() => {
|
|
655
|
+
return fetchBySlug({ slug, localeCode, client, experienceTypeId });
|
|
656
|
+
}, [slug, localeCode, client, experienceTypeId]);
|
|
657
|
+
return useFetchByBase(fetchMethod, isEditorMode);
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Returns the value of the argument from the previous render
|
|
662
|
+
* @param {T} value
|
|
663
|
+
* @returns {T | undefined} previous value
|
|
664
|
+
*/
|
|
665
|
+
function usePrevious(value) {
|
|
666
|
+
const ref = useRef();
|
|
667
|
+
useEffect(() => {
|
|
668
|
+
ref.current = value;
|
|
669
|
+
}, [value]);
|
|
670
|
+
return ref.current;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/**
|
|
674
|
+
* @deprecated Remove after the BETA release
|
|
675
|
+
* @returns
|
|
676
|
+
*/
|
|
677
|
+
const DeprecatedPreviewDeliveryRoot = ({ locale, slug, deprecatedExperience, }) => {
|
|
678
|
+
const attemptedToFetch = useRef(false);
|
|
679
|
+
const previousLocale = usePrevious(locale);
|
|
680
|
+
const { experienceTypeId, client } = deprecatedExperience;
|
|
681
|
+
const { fetchBySlug, experience, isFetching } = useFetchExperience({
|
|
682
|
+
client,
|
|
683
|
+
});
|
|
684
|
+
const entityStore = experience?.entityStore;
|
|
685
|
+
useEffect(() => {
|
|
686
|
+
// TODO: Test it, it is crucial
|
|
687
|
+
// will make it fetch on each locale change as well as if experience entry hasn't been fetched yet at least once
|
|
688
|
+
const shouldFetch = (client && !entityStore && !attemptedToFetch.current) || previousLocale !== locale;
|
|
689
|
+
// this useEffect is meant to trigger fetching for the first time if it hasn't been done earlier
|
|
690
|
+
// if not yet fetched and not fetchin at the moment
|
|
691
|
+
if (shouldFetch && !isFetching && slug) {
|
|
692
|
+
attemptedToFetch.current = true;
|
|
693
|
+
fetchBySlug({
|
|
694
|
+
experienceTypeId,
|
|
695
|
+
localeCode: locale,
|
|
696
|
+
slug,
|
|
697
|
+
}).catch(() => {
|
|
698
|
+
// noop
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
}, [
|
|
702
|
+
experienceTypeId,
|
|
703
|
+
entityStore,
|
|
704
|
+
isFetching,
|
|
705
|
+
fetchBySlug,
|
|
706
|
+
client,
|
|
707
|
+
slug,
|
|
708
|
+
locale,
|
|
709
|
+
previousLocale,
|
|
710
|
+
]);
|
|
711
|
+
const { resolveDesignValue } = useBreakpoints(entityStore?.breakpoints ?? []);
|
|
712
|
+
if (!entityStore?.experienceEntryFields || !entityStore?.schemaVersion) {
|
|
713
|
+
return null;
|
|
714
|
+
}
|
|
715
|
+
if (!compatibleVersions.includes(entityStore.schemaVersion)) {
|
|
716
|
+
console.warn(`[experiences-sdk-react] Contenful composition schema version: ${entityStore.schemaVersion} does not match the compatible schema versions: ${compatibleVersions}. Aborting.`);
|
|
717
|
+
return null;
|
|
718
|
+
}
|
|
719
|
+
return (jsx(Fragment, { children: entityStore.experienceEntryFields.componentTree.children.map((childNode, index) => (jsx(CompositionBlock, { node: childNode, locale: locale, entityStore: entityStore, resolveDesignValue: resolveDesignValue }, index))) }));
|
|
720
|
+
};
|
|
721
|
+
|
|
722
|
+
const PreviewDeliveryRoot = ({ locale, experience }) => {
|
|
723
|
+
const { entityStore } = experience;
|
|
724
|
+
const { resolveDesignValue } = useBreakpoints(entityStore?.breakpoints ?? []);
|
|
725
|
+
if (!entityStore?.experienceEntryFields || !entityStore?.schemaVersion) {
|
|
726
|
+
return null;
|
|
727
|
+
}
|
|
728
|
+
if (!compatibleVersions.includes(entityStore.schemaVersion)) {
|
|
729
|
+
console.warn(`[experiences-sdk-react] Contentful composition schema version: ${entityStore.schemaVersion} does not match the compatible schema versions: ${compatibleVersions}. Aborting.`);
|
|
730
|
+
return null;
|
|
731
|
+
}
|
|
732
|
+
return (jsx(Fragment, { children: entityStore.experienceEntryFields.componentTree.children.map((childNode, index) => (jsx(CompositionBlock, { node: childNode, locale: locale, entityStore: entityStore, resolveDesignValue: resolveDesignValue }, index))) }));
|
|
733
|
+
};
|
|
734
|
+
|
|
735
|
+
var css_248z = ".cf-error-message {\n margin: 24px;\n font-size: var(--exp-builder-font-size-m);\n font-family: var(--exp-builder-font-stack-primary);\n color: var(--exp-builder-red800);\n padding: 16px;\n background-color: var(--exp-builder-red200);\n}\n\n.cf-error-message .title {\n margin-top: 0;\n font-size: var(--exp-builder-font-size-l);\n}\n\n.cf-error-message .more-details {\n cursor: pointer;\n color: var(--exp-builder-blue700);\n}\n";
|
|
736
|
+
styleInject(css_248z);
|
|
737
|
+
|
|
738
|
+
class ImportedComponentError extends Error {
|
|
739
|
+
}
|
|
740
|
+
class ErrorBoundary extends React.Component {
|
|
741
|
+
constructor(props) {
|
|
742
|
+
super(props);
|
|
743
|
+
this.state = { hasError: false, error: null, errorInfo: null, showErrorDetails: false };
|
|
744
|
+
}
|
|
745
|
+
static getDerivedStateFromError() {
|
|
746
|
+
return { hasError: true };
|
|
747
|
+
}
|
|
748
|
+
componentDidCatch(error, errorInfo) {
|
|
749
|
+
this.setState({ error, errorInfo });
|
|
750
|
+
if (!(error instanceof ImportedComponentError)) {
|
|
751
|
+
sendMessage(OUTGOING_EVENTS.CanvasError, error);
|
|
752
|
+
}
|
|
753
|
+
else {
|
|
754
|
+
throw error;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
render() {
|
|
758
|
+
if (this.state.hasError) {
|
|
759
|
+
return (jsxs("div", { className: "cf-error-message", children: [jsx("h2", { className: "title", children: `Something went wrong while rendering the experience` }), jsxs("div", { children: ["The Experience Builder SDK has encountered an error. It may be that the SDK has not been set up properly or an imported component has thrown this error. Try to refresh the page and find more guidance in our", ' ', jsx("a", { href: "https://www.contentful.com/developers/docs/tutorials/general/experience-builder/", rel: "noreferrer", target: "_blank", children: "documentation" }), "."] }), jsx("br", {}), jsxs("span", { className: "more-details", onClick: () => this.setState((prevState) => ({
|
|
760
|
+
showErrorDetails: !prevState.showErrorDetails,
|
|
761
|
+
})), children: [this.state.showErrorDetails ? 'Hide' : 'See', " details"] }), this.state.showErrorDetails && (jsx("code", { children: this.state.error?.stack?.split('\n').map((i, key) => {
|
|
762
|
+
return jsx("div", { children: i }, key);
|
|
763
|
+
}) }))] }));
|
|
764
|
+
}
|
|
765
|
+
return this.props.children;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
class ImportedComponentErrorBoundary extends React.Component {
|
|
769
|
+
componentDidCatch(error, _errorInfo) {
|
|
770
|
+
const err = new ImportedComponentError(error.message);
|
|
771
|
+
err.stack = error.stack;
|
|
772
|
+
throw err;
|
|
773
|
+
}
|
|
774
|
+
render() {
|
|
775
|
+
return this.props.children;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const useInitializeVisualEditor = (params) => {
|
|
780
|
+
const { initialLocale, initialEntities } = params;
|
|
781
|
+
const [locale, setLocale] = useState(initialLocale);
|
|
782
|
+
const hasConnectEventBeenSent = useRef(false);
|
|
783
|
+
// sends component definitions to the web app
|
|
784
|
+
// InternalEvents.COMPONENTS_REGISTERED is triggered by defineComponents function
|
|
785
|
+
useEffect(() => {
|
|
786
|
+
if (!hasConnectEventBeenSent.current) {
|
|
787
|
+
// sending CONNECT but with the registered components now
|
|
788
|
+
sendConnectedEventWithRegisteredComponents();
|
|
789
|
+
hasConnectEventBeenSent.current = true;
|
|
790
|
+
}
|
|
791
|
+
const onComponentsRegistered = () => {
|
|
792
|
+
sendRegisteredComponentsMessage();
|
|
793
|
+
};
|
|
794
|
+
if (typeof window !== 'undefined') {
|
|
795
|
+
window.addEventListener(INTERNAL_EVENTS.ComponentsRegistered, onComponentsRegistered);
|
|
796
|
+
}
|
|
797
|
+
return () => {
|
|
798
|
+
if (typeof window !== 'undefined') {
|
|
799
|
+
window.removeEventListener(INTERNAL_EVENTS.ComponentsRegistered, onComponentsRegistered);
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
}, []);
|
|
803
|
+
useEffect(() => {
|
|
804
|
+
setLocale(initialLocale);
|
|
805
|
+
}, [initialLocale]);
|
|
806
|
+
useEffect(() => {
|
|
807
|
+
const onVisualEditorReady = () => {
|
|
808
|
+
window.dispatchEvent(new CustomEvent(INTERNAL_EVENTS.VisualEditorInitialize, {
|
|
809
|
+
detail: {
|
|
810
|
+
componentRegistry,
|
|
811
|
+
designTokens: designTokensRegistry,
|
|
812
|
+
locale,
|
|
813
|
+
entities: initialEntities ?? [],
|
|
814
|
+
},
|
|
815
|
+
}));
|
|
816
|
+
};
|
|
817
|
+
window.addEventListener(VISUAL_EDITOR_EVENTS.Ready, onVisualEditorReady);
|
|
818
|
+
return () => {
|
|
819
|
+
window.removeEventListener(VISUAL_EDITOR_EVENTS.Ready, onVisualEditorReady);
|
|
820
|
+
};
|
|
821
|
+
}, [locale, initialEntities]);
|
|
822
|
+
};
|
|
823
|
+
|
|
824
|
+
const VisualEditorLoader = React.lazy(() => import('./VisualEditorLoader-CY2fhqS5.js'));
|
|
825
|
+
const VisualEditorRoot = ({ visualEditorMode, initialEntities, initialLocale, }) => {
|
|
826
|
+
useInitializeVisualEditor({
|
|
827
|
+
initialLocale,
|
|
828
|
+
initialEntities,
|
|
829
|
+
});
|
|
830
|
+
return (jsx(ErrorBoundary, { children: jsx(Suspense, { fallback: jsx("div", { children: "Loading..." }), children: jsx(VisualEditorLoader, { visualEditorMode: visualEditorMode }) }) }));
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
const ExperienceRoot = ({ locale, experience, slug, visualEditorMode = VisualEditorMode.LazyLoad, }) => {
|
|
834
|
+
const isEditorMode = useDetectEditorMode();
|
|
835
|
+
validateExperienceBuilderConfig({
|
|
836
|
+
locale,
|
|
837
|
+
isEditorMode,
|
|
838
|
+
});
|
|
839
|
+
if (isEditorMode) {
|
|
840
|
+
const entityStore = experience && !isDeprecatedExperience(experience) ? experience.entityStore : undefined;
|
|
841
|
+
return (jsx(VisualEditorRoot, { visualEditorMode: visualEditorMode, initialEntities: entityStore?.entities || [], initialLocale: locale }));
|
|
842
|
+
}
|
|
843
|
+
if (!experience)
|
|
844
|
+
return null;
|
|
845
|
+
if (isDeprecatedExperience(experience)) {
|
|
846
|
+
return (jsx(DeprecatedPreviewDeliveryRoot, { deprecatedExperience: experience, locale: locale, slug: slug }));
|
|
847
|
+
}
|
|
848
|
+
return jsx(PreviewDeliveryRoot, { locale: locale, experience: experience });
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
// Simple state store to store a few things that are needed across the SDK
|
|
852
|
+
if (typeof window !== 'undefined') {
|
|
853
|
+
window.__EB__ = {
|
|
854
|
+
sdkVersion: SDK_VERSION,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
export { ExperienceRoot, defineComponents, useExperienceBuilder, useFetchById, useFetchBySlug, useFetchExperience };
|
|
859
|
+
//# sourceMappingURL=index.js.map
|