@contentful/experiences-visual-editor-react 1.39.0-alpha-20250528T1342-e28bc3d.0 → 1.39.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +2645 -784
- package/dist/index.js.map +1 -1
- package/dist/renderApp.js +24182 -12349
- package/dist/renderApp.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,268 +1,38 @@
|
|
|
1
1
|
import styleInject from 'style-inject';
|
|
2
|
-
import React, {
|
|
3
|
-
import { create } from 'zustand';
|
|
4
|
-
import { produce } from 'immer';
|
|
5
|
-
import { isEqual, omit, isArray, get as get$1 } from 'lodash-es';
|
|
2
|
+
import React, { useEffect, useRef, useState, useCallback, forwardRef, useLayoutEffect, useMemo } from 'react';
|
|
6
3
|
import { z } from 'zod';
|
|
4
|
+
import { omit, isArray, isEqual, get as get$1 } from 'lodash-es';
|
|
7
5
|
import md5 from 'md5';
|
|
8
6
|
import { BLOCKS } from '@contentful/rich-text-types';
|
|
7
|
+
import { create } from 'zustand';
|
|
8
|
+
import { Droppable, Draggable, DragDropContext } from '@hello-pangea/dnd';
|
|
9
|
+
import { produce } from 'immer';
|
|
9
10
|
import '@contentful/rich-text-react-renderer';
|
|
11
|
+
import { v4 } from 'uuid';
|
|
12
|
+
import { createPortal } from 'react-dom';
|
|
13
|
+
import classNames from 'classnames';
|
|
10
14
|
|
|
11
15
|
var css_248z$b = "html,\nbody {\n margin: 0;\n padding: 0;\n}\n\n/*\n * All of these variables are tokens from Forma-36 and should not be adjusted as these\n * are global variables that may affect multiple places.\n * As our customers may use other design libraries, we try to avoid overlapping global\n * variables by always using the prefix `--exp-builder-` inside this SDK.\n */\n\n:root {\n /* Color tokens from Forma 36: https://f36.contentful.com/tokens/color-system */\n --exp-builder-blue100: #e8f5ff;\n --exp-builder-blue200: #ceecff;\n --exp-builder-blue300: #98cbff;\n --exp-builder-blue400: #40a0ff;\n --exp-builder-blue500: #036fe3;\n --exp-builder-blue600: #0059c8;\n --exp-builder-blue700: #0041ab;\n --exp-builder-blue800: #003298;\n --exp-builder-blue900: #002a8e;\n --exp-builder-gray100: #f7f9fa;\n --exp-builder-gray200: #e7ebee;\n --exp-builder-gray300: #cfd9e0;\n --exp-builder-gray400: #aec1cc;\n --exp-builder-gray500: #67728a;\n --exp-builder-gray600: #5a657c;\n --exp-builder-gray700: #414d63;\n --exp-builder-gray800: #1b273a;\n --exp-builder-gray900: #111b2b;\n --exp-builder-purple600: #6c3ecf;\n --exp-builder-red200: #ffe0e0;\n --exp-builder-red800: #7f0010;\n --exp-builder-color-white: #ffffff;\n --exp-builder-glow-primary: 0px 0px 0px 3px #e8f5ff;\n\n /* RGB colors for applying opacity */\n --exp-builder-blue100-rgb: 232, 245, 255;\n --exp-builder-blue300-rgb: 152, 203, 255;\n\n /* Spacing tokens from Forma 36: https://f36.contentful.com/tokens/spacing */\n --exp-builder-spacing-s: 0.75rem;\n --exp-builder-spacing-2xs: 0.25rem;\n\n /* Typography tokens from Forma 36: https://f36.contentful.com/tokens/typography */\n --exp-builder-font-size-l: 1rem;\n --exp-builder-font-size-m: 0.875rem;\n --exp-builder-font-stack-primary: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,\n sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;\n --exp-builder-line-height-condensed: 1.25;\n}\n";
|
|
12
16
|
styleInject(css_248z$b);
|
|
13
17
|
|
|
14
|
-
const ROOT_ID = 'root';
|
|
15
|
-
var TreeAction;
|
|
16
|
-
(function (TreeAction) {
|
|
17
|
-
TreeAction[TreeAction["REMOVE_NODE"] = 0] = "REMOVE_NODE";
|
|
18
|
-
TreeAction[TreeAction["ADD_NODE"] = 1] = "ADD_NODE";
|
|
19
|
-
TreeAction[TreeAction["MOVE_NODE"] = 2] = "MOVE_NODE";
|
|
20
|
-
TreeAction[TreeAction["UPDATE_NODE"] = 3] = "UPDATE_NODE";
|
|
21
|
-
TreeAction[TreeAction["REORDER_NODE"] = 4] = "REORDER_NODE";
|
|
22
|
-
TreeAction[TreeAction["REPLACE_NODE"] = 5] = "REPLACE_NODE";
|
|
23
|
-
})(TreeAction || (TreeAction = {}));
|
|
24
|
-
|
|
25
|
-
function updateNode(nodeId, updatedNode, node) {
|
|
26
|
-
if (node.data.id === nodeId) {
|
|
27
|
-
node.data = updatedNode.data;
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
node.children.forEach((childNode) => updateNode(nodeId, updatedNode, childNode));
|
|
31
|
-
}
|
|
32
|
-
function replaceNode(indexToReplace, updatedNode, node) {
|
|
33
|
-
if (node.data.id === updatedNode.parentId) {
|
|
34
|
-
node.children = [
|
|
35
|
-
...node.children.slice(0, indexToReplace),
|
|
36
|
-
updatedNode,
|
|
37
|
-
...node.children.slice(indexToReplace + 1),
|
|
38
|
-
];
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
node.children.forEach((childNode) => replaceNode(indexToReplace, updatedNode, childNode));
|
|
42
|
-
}
|
|
43
|
-
function removeChildNode(indexToRemove, nodeId, parentNodeId, node) {
|
|
44
|
-
if (node.data.id === parentNodeId) {
|
|
45
|
-
const childIndex = node.children.findIndex((child) => child.data.id === nodeId);
|
|
46
|
-
node.children.splice(childIndex === -1 ? indexToRemove : childIndex, 1);
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
node.children.forEach((childNode) => removeChildNode(indexToRemove, nodeId, parentNodeId, childNode));
|
|
50
|
-
}
|
|
51
|
-
function addChildNode(indexToAdd, parentNodeId, nodeToAdd, node) {
|
|
52
|
-
if (node.data.id === parentNodeId) {
|
|
53
|
-
node.children = [
|
|
54
|
-
...node.children.slice(0, indexToAdd),
|
|
55
|
-
nodeToAdd,
|
|
56
|
-
...node.children.slice(indexToAdd),
|
|
57
|
-
];
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
node.children.forEach((childNode) => addChildNode(indexToAdd, parentNodeId, nodeToAdd, childNode));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function getItemFromTree(id, node) {
|
|
64
|
-
// Check if the current node's id matches the search id
|
|
65
|
-
if (node.data.id === id) {
|
|
66
|
-
return node;
|
|
67
|
-
}
|
|
68
|
-
// Recursively search through each child
|
|
69
|
-
for (const child of node.children) {
|
|
70
|
-
const foundNode = getItemFromTree(id, child);
|
|
71
|
-
if (foundNode) {
|
|
72
|
-
// Node found in children
|
|
73
|
-
return foundNode;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
// If the node is not found in this branch of the tree, return undefined
|
|
77
|
-
return undefined;
|
|
78
|
-
}
|
|
79
|
-
const getItem = (selector, tree) => {
|
|
80
|
-
return getItemFromTree(selector.id, {
|
|
81
|
-
type: 'block',
|
|
82
|
-
data: {
|
|
83
|
-
id: ROOT_ID,
|
|
84
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
-
},
|
|
86
|
-
children: tree.root.children,
|
|
87
|
-
});
|
|
88
|
-
};
|
|
89
|
-
|
|
90
|
-
function missingNodeAction({ index, nodeAdded, child, tree, parentNodeId, currentNode, }) {
|
|
91
|
-
if (nodeAdded) {
|
|
92
|
-
return { type: TreeAction.ADD_NODE, indexToAdd: index, nodeToAdd: child, parentNodeId };
|
|
93
|
-
}
|
|
94
|
-
const item = getItem({ id: child.data.id }, tree);
|
|
95
|
-
if (item) {
|
|
96
|
-
const parentNode = getItem({ id: item.parentId }, tree);
|
|
97
|
-
if (!parentNode) {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
const sourceIndex = parentNode.children.findIndex((c) => c.data.id === child.data.id);
|
|
101
|
-
return { type: TreeAction.MOVE_NODE, sourceIndex, destinationIndex: index, parentNodeId };
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
type: TreeAction.REPLACE_NODE,
|
|
105
|
-
originalId: currentNode.children[index].data.id,
|
|
106
|
-
indexToReplace: index,
|
|
107
|
-
node: child,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function matchingNodeAction({ index, originalIndex, nodeRemoved, nodeAdded, parentNodeId, }) {
|
|
111
|
-
if (index !== originalIndex && !nodeRemoved && !nodeAdded) {
|
|
112
|
-
return {
|
|
113
|
-
type: TreeAction.REORDER_NODE,
|
|
114
|
-
sourceIndex: originalIndex,
|
|
115
|
-
destinationIndex: index,
|
|
116
|
-
parentNodeId,
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
function compareNodes({ currentNode, updatedNode, originalTree, differences = [], }) {
|
|
122
|
-
// In the end, this map contains the list of nodes that are not present
|
|
123
|
-
// in the updated tree and must be removed
|
|
124
|
-
const map = new Map();
|
|
125
|
-
if (!currentNode || !updatedNode) {
|
|
126
|
-
return differences;
|
|
127
|
-
}
|
|
128
|
-
// On each tree level, consider only the children of the current node to differentiate between added, removed, or replaced case
|
|
129
|
-
const currentNodeCount = currentNode.children.length;
|
|
130
|
-
const updatedNodeCount = updatedNode.children.length;
|
|
131
|
-
const nodeRemoved = currentNodeCount > updatedNodeCount;
|
|
132
|
-
const nodeAdded = currentNodeCount < updatedNodeCount;
|
|
133
|
-
const parentNodeId = updatedNode.data.id;
|
|
134
|
-
/**
|
|
135
|
-
* The data of the current node has changed, we need to update
|
|
136
|
-
* this node to reflect the data change. (design, content, unbound values)
|
|
137
|
-
*/
|
|
138
|
-
if (!isEqual(currentNode.data, updatedNode.data)) {
|
|
139
|
-
differences.push({
|
|
140
|
-
type: TreeAction.UPDATE_NODE,
|
|
141
|
-
nodeId: currentNode.data.id,
|
|
142
|
-
node: updatedNode,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
// Map children of the first tree by their ID
|
|
146
|
-
currentNode.children.forEach((child, index) => map.set(child.data.id, index));
|
|
147
|
-
// Compare with the second tree
|
|
148
|
-
updatedNode.children.forEach((child, index) => {
|
|
149
|
-
const childId = child.data.id;
|
|
150
|
-
// The original tree does not have this node in the updated tree.
|
|
151
|
-
if (!map.has(childId)) {
|
|
152
|
-
const diff = missingNodeAction({
|
|
153
|
-
index,
|
|
154
|
-
child,
|
|
155
|
-
nodeAdded,
|
|
156
|
-
parentNodeId,
|
|
157
|
-
tree: originalTree,
|
|
158
|
-
currentNode,
|
|
159
|
-
});
|
|
160
|
-
if (diff?.type === TreeAction.REPLACE_NODE) {
|
|
161
|
-
// Remove it from the deletion map to avoid adding another REMOVE_NODE action
|
|
162
|
-
map.delete(diff.originalId);
|
|
163
|
-
}
|
|
164
|
-
return differences.push(diff);
|
|
165
|
-
}
|
|
166
|
-
const originalIndex = map.get(childId);
|
|
167
|
-
const diff = matchingNodeAction({
|
|
168
|
-
index,
|
|
169
|
-
originalIndex,
|
|
170
|
-
nodeAdded,
|
|
171
|
-
nodeRemoved,
|
|
172
|
-
parentNodeId,
|
|
173
|
-
});
|
|
174
|
-
differences.push(diff);
|
|
175
|
-
map.delete(childId);
|
|
176
|
-
compareNodes({
|
|
177
|
-
currentNode: currentNode.children[originalIndex],
|
|
178
|
-
updatedNode: child,
|
|
179
|
-
originalTree,
|
|
180
|
-
differences,
|
|
181
|
-
});
|
|
182
|
-
});
|
|
183
|
-
map.forEach((index, key) => {
|
|
184
|
-
// If the node count of the entire tree doesn't signify
|
|
185
|
-
// a node was removed, don't add that as a diff
|
|
186
|
-
if (!nodeRemoved) {
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
// Remaining nodes in the map are removed in the second tree
|
|
190
|
-
differences.push({
|
|
191
|
-
type: TreeAction.REMOVE_NODE,
|
|
192
|
-
indexToRemove: index,
|
|
193
|
-
parentNodeId,
|
|
194
|
-
idToRemove: key,
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
return differences;
|
|
198
|
-
}
|
|
199
|
-
function getTreeDiffs(tree1, tree2, originalTree) {
|
|
200
|
-
const differences = [];
|
|
201
|
-
compareNodes({
|
|
202
|
-
currentNode: tree1,
|
|
203
|
-
updatedNode: tree2,
|
|
204
|
-
originalTree,
|
|
205
|
-
differences,
|
|
206
|
-
});
|
|
207
|
-
return differences.filter((diff) => diff);
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
211
|
-
const OUTGOING_EVENTS = {
|
|
212
|
-
Connected: 'connected',
|
|
213
|
-
DesignTokens: 'registerDesignTokens',
|
|
214
|
-
RegisteredBreakpoints: 'registeredBreakpoints',
|
|
215
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
216
|
-
MouseMove: 'mouseMove',
|
|
217
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
218
|
-
ComponentSelected: 'componentSelected',
|
|
219
|
-
RegisteredComponents: 'registeredComponents',
|
|
220
|
-
RequestComponentTreeUpdate: 'requestComponentTreeUpdate',
|
|
221
|
-
CanvasReload: 'canvasReload',
|
|
222
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
223
|
-
UpdateSelectedComponentCoordinates: 'updateSelectedComponentCoordinates',
|
|
224
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
225
|
-
CanvasScroll: 'canvasScrolling',
|
|
226
|
-
CanvasError: 'canvasError',
|
|
227
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
228
|
-
OutsideCanvasClick: 'outsideCanvasClick',
|
|
229
|
-
SDKFeatures: 'sdkFeatures',
|
|
230
|
-
RequestEntities: 'REQUEST_ENTITIES',
|
|
231
|
-
};
|
|
232
18
|
const INCOMING_EVENTS$1 = {
|
|
233
19
|
RequestEditorMode: 'requestEditorMode',
|
|
234
20
|
RequestReadOnlyMode: 'requestReadOnlyMode',
|
|
235
21
|
ExperienceUpdated: 'componentTreeUpdated',
|
|
236
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
237
22
|
ComponentDraggingChanged: 'componentDraggingChanged',
|
|
238
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
239
23
|
ComponentDragCanceled: 'componentDragCanceled',
|
|
240
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
241
24
|
ComponentDragStarted: 'componentDragStarted',
|
|
242
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
243
25
|
ComponentDragEnded: 'componentDragEnded',
|
|
244
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
245
26
|
ComponentMoveEnded: 'componentMoveEnded',
|
|
246
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
247
27
|
CanvasResized: 'canvasResized',
|
|
248
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
249
28
|
SelectComponent: 'selectComponent',
|
|
250
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
251
29
|
HoverComponent: 'hoverComponent',
|
|
252
30
|
UpdatedEntity: 'updatedEntity',
|
|
253
31
|
AssembliesAdded: 'assembliesAdded',
|
|
254
32
|
AssembliesRegistered: 'assembliesRegistered',
|
|
255
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
256
33
|
MouseMove: 'mouseMove',
|
|
257
34
|
RequestedEntities: 'REQUESTED_ENTITIES',
|
|
258
35
|
};
|
|
259
|
-
const INTERNAL_EVENTS = {
|
|
260
|
-
ComponentsRegistered: 'cfComponentsRegistered',
|
|
261
|
-
VisualEditorInitialize: 'cfVisualEditorInitialize',
|
|
262
|
-
};
|
|
263
|
-
const VISUAL_EDITOR_EVENTS = {
|
|
264
|
-
Ready: 'cfVisualEditorReady',
|
|
265
|
-
};
|
|
266
36
|
/**
|
|
267
37
|
* These modes are ONLY intended to be internally used within the context of
|
|
268
38
|
* editing an experience inside of Contentful Studio. i.e. these modes
|
|
@@ -274,56 +44,7 @@ var StudioCanvasMode$3;
|
|
|
274
44
|
StudioCanvasMode["EDITOR"] = "editorMode";
|
|
275
45
|
StudioCanvasMode["NONE"] = "none";
|
|
276
46
|
})(StudioCanvasMode$3 || (StudioCanvasMode$3 = {}));
|
|
277
|
-
const
|
|
278
|
-
const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
|
|
279
|
-
const EMPTY_CONTAINER_HEIGHT$1 = '80px';
|
|
280
|
-
const HYPERLINK_DEFAULT_PATTERN = `/{locale}/{entry.fields.slug}/`;
|
|
281
|
-
var PostMessageMethods$3;
|
|
282
|
-
(function (PostMessageMethods) {
|
|
283
|
-
PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
|
|
284
|
-
PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
|
|
285
|
-
})(PostMessageMethods$3 || (PostMessageMethods$3 = {}));
|
|
286
|
-
|
|
287
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
288
|
-
const INCOMING_EVENTS = {
|
|
289
|
-
RequestEditorMode: 'requestEditorMode',
|
|
290
|
-
RequestReadOnlyMode: 'requestReadOnlyMode',
|
|
291
|
-
ExperienceUpdated: 'componentTreeUpdated',
|
|
292
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
293
|
-
ComponentDraggingChanged: 'componentDraggingChanged',
|
|
294
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
295
|
-
ComponentDragCanceled: 'componentDragCanceled',
|
|
296
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
297
|
-
ComponentDragStarted: 'componentDragStarted',
|
|
298
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
299
|
-
ComponentDragEnded: 'componentDragEnded',
|
|
300
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
301
|
-
ComponentMoveEnded: 'componentMoveEnded',
|
|
302
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
303
|
-
CanvasResized: 'canvasResized',
|
|
304
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
305
|
-
SelectComponent: 'selectComponent',
|
|
306
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
307
|
-
HoverComponent: 'hoverComponent',
|
|
308
|
-
UpdatedEntity: 'updatedEntity',
|
|
309
|
-
AssembliesAdded: 'assembliesAdded',
|
|
310
|
-
AssembliesRegistered: 'assembliesRegistered',
|
|
311
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
312
|
-
MouseMove: 'mouseMove',
|
|
313
|
-
RequestedEntities: 'REQUESTED_ENTITIES',
|
|
314
|
-
};
|
|
315
|
-
/**
|
|
316
|
-
* These modes are ONLY intended to be internally used within the context of
|
|
317
|
-
* editing an experience inside of Contentful Studio. i.e. these modes
|
|
318
|
-
* intentionally do not include preview/delivery modes.
|
|
319
|
-
*/
|
|
320
|
-
var StudioCanvasMode$2;
|
|
321
|
-
(function (StudioCanvasMode) {
|
|
322
|
-
StudioCanvasMode["READ_ONLY"] = "readOnlyMode";
|
|
323
|
-
StudioCanvasMode["EDITOR"] = "editorMode";
|
|
324
|
-
StudioCanvasMode["NONE"] = "none";
|
|
325
|
-
})(StudioCanvasMode$2 || (StudioCanvasMode$2 = {}));
|
|
326
|
-
const CONTENTFUL_COMPONENTS$1 = {
|
|
47
|
+
const CONTENTFUL_COMPONENTS$2 = {
|
|
327
48
|
section: {
|
|
328
49
|
id: 'contentful-section',
|
|
329
50
|
name: 'Section',
|
|
@@ -369,6 +90,9 @@ const CONTENTFUL_COMPONENTS$1 = {
|
|
|
369
90
|
name: 'Carousel',
|
|
370
91
|
},
|
|
371
92
|
};
|
|
93
|
+
const ASSEMBLY_NODE_TYPE$1 = 'assembly';
|
|
94
|
+
const ASSEMBLY_DEFAULT_CATEGORY$1 = 'Assemblies';
|
|
95
|
+
const ASSEMBLY_BLOCK_NODE_TYPE$1 = 'assemblyBlock';
|
|
372
96
|
const CF_STYLE_ATTRIBUTES = [
|
|
373
97
|
'cfVisibility',
|
|
374
98
|
'cfHorizontalAlignment',
|
|
@@ -411,24 +135,30 @@ const CF_STYLE_ATTRIBUTES = [
|
|
|
411
135
|
'cfBackgroundImageAlignmentVertical',
|
|
412
136
|
'cfBackgroundImageAlignmentHorizontal',
|
|
413
137
|
];
|
|
414
|
-
const EMPTY_CONTAINER_HEIGHT = '80px';
|
|
138
|
+
const EMPTY_CONTAINER_HEIGHT$1 = '80px';
|
|
415
139
|
const DEFAULT_IMAGE_WIDTH = '500px';
|
|
416
|
-
var PostMessageMethods$
|
|
140
|
+
var PostMessageMethods$3;
|
|
417
141
|
(function (PostMessageMethods) {
|
|
418
142
|
PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
|
|
419
143
|
PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
|
|
420
|
-
})(PostMessageMethods$
|
|
144
|
+
})(PostMessageMethods$3 || (PostMessageMethods$3 = {}));
|
|
421
145
|
const SUPPORTED_IMAGE_FORMATS = ['jpg', 'png', 'webp', 'gif', 'avif'];
|
|
422
146
|
|
|
423
147
|
const structureComponentIds = new Set([
|
|
424
|
-
CONTENTFUL_COMPONENTS$
|
|
425
|
-
CONTENTFUL_COMPONENTS$
|
|
426
|
-
CONTENTFUL_COMPONENTS$
|
|
427
|
-
CONTENTFUL_COMPONENTS$
|
|
148
|
+
CONTENTFUL_COMPONENTS$2.section.id,
|
|
149
|
+
CONTENTFUL_COMPONENTS$2.columns.id,
|
|
150
|
+
CONTENTFUL_COMPONENTS$2.container.id,
|
|
151
|
+
CONTENTFUL_COMPONENTS$2.singleColumn.id,
|
|
428
152
|
]);
|
|
429
|
-
const
|
|
153
|
+
const patternTypes = new Set([ASSEMBLY_NODE_TYPE$1, ASSEMBLY_BLOCK_NODE_TYPE$1]);
|
|
154
|
+
const allContentfulComponentIds = new Set(Object.values(CONTENTFUL_COMPONENTS$2).map((component) => component.id));
|
|
155
|
+
const isPatternComponent = (type) => patternTypes.has(type ?? '');
|
|
430
156
|
const isContentfulStructureComponent = (componentId) => structureComponentIds.has((componentId ?? ''));
|
|
431
157
|
const isContentfulComponent = (componentId) => allContentfulComponentIds.has((componentId ?? ''));
|
|
158
|
+
const isComponentAllowedOnRoot = ({ type, category, componentId }) => isPatternComponent(type) ||
|
|
159
|
+
category === ASSEMBLY_DEFAULT_CATEGORY$1 ||
|
|
160
|
+
isContentfulStructureComponent(componentId) ||
|
|
161
|
+
componentId === CONTENTFUL_COMPONENTS$2.divider.id;
|
|
432
162
|
const isStructureWithRelativeHeight = (componentId, height) => {
|
|
433
163
|
return isContentfulStructureComponent(componentId) && !height?.toString().endsWith('px');
|
|
434
164
|
};
|
|
@@ -867,17 +597,16 @@ const PrimitiveValueSchema$1 = z.union([
|
|
|
867
597
|
z.record(z.any(), z.any()),
|
|
868
598
|
z.undefined(),
|
|
869
599
|
]);
|
|
600
|
+
const UsedComponentsSchema$1 = z.array(z.object({
|
|
601
|
+
sys: z.object({
|
|
602
|
+
type: z.literal('Link'),
|
|
603
|
+
id: z.string(),
|
|
604
|
+
linkType: z.literal('Entry'),
|
|
605
|
+
}),
|
|
606
|
+
}));
|
|
870
607
|
const uuidKeySchema$1 = z
|
|
871
608
|
.string()
|
|
872
609
|
.regex(/^[a-zA-Z0-9-_]{1,21}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,21}$/' });
|
|
873
|
-
/**
|
|
874
|
-
* Property keys for imported components have a limit of 32 characters (to be implemented) while
|
|
875
|
-
* property keys for patterns have a limit of 54 characters (<32-char-variabl-name>_<21-char-nanoid-id>).
|
|
876
|
-
* Because we cannot distinguish between the two in the componentTree, we will use the larger limit for both.
|
|
877
|
-
*/
|
|
878
|
-
const propertyKeySchema$1 = z
|
|
879
|
-
.string()
|
|
880
|
-
.regex(/^[a-zA-Z0-9-_]{1,54}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,54}$/' });
|
|
881
610
|
const DataSourceSchema$1 = z.record(uuidKeySchema$1, z.object({
|
|
882
611
|
sys: z.object({
|
|
883
612
|
type: z.literal('Link'),
|
|
@@ -885,7 +614,62 @@ const DataSourceSchema$1 = z.record(uuidKeySchema$1, z.object({
|
|
|
885
614
|
linkType: z.enum(['Entry', 'Asset']),
|
|
886
615
|
}),
|
|
887
616
|
}));
|
|
617
|
+
const UnboundValuesSchema$1 = z.record(uuidKeySchema$1, z.object({
|
|
618
|
+
value: PrimitiveValueSchema$1,
|
|
619
|
+
}));
|
|
620
|
+
/**
|
|
621
|
+
* Property keys for imported components have a limit of 32 characters (to be implemented) while
|
|
622
|
+
* property keys for patterns have a limit of 54 characters (<32-char-variable-name>_<21-char-nanoid-id>).
|
|
623
|
+
* Because we cannot distinguish between the two in the componentTree, we will use the larger limit for both.
|
|
624
|
+
*/
|
|
625
|
+
const propertyKeySchema$1 = z
|
|
626
|
+
.string()
|
|
627
|
+
.regex(/^[a-zA-Z0-9-_]{1,54}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,54}$/' });
|
|
628
|
+
const ComponentTreeNodeIdSchema$1 = z
|
|
629
|
+
.string()
|
|
630
|
+
.regex(/^[a-zA-Z0-9]{1,8}$/, { message: 'Does not match /^[a-zA-Z0-9]{1,8}$/' });
|
|
631
|
+
const breakpointsRefinement$1 = (value, ctx) => {
|
|
632
|
+
if (!value.length || value[0].query !== '*') {
|
|
633
|
+
ctx.addIssue({
|
|
634
|
+
code: z.ZodIssueCode.custom,
|
|
635
|
+
message: `The first breakpoint should include the following attributes: { "query": "*" }`,
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
const hasDuplicateIds = value.some((currentBreakpoint, currentBreakpointIndex) => {
|
|
639
|
+
// check if the current breakpoint id is found in the rest of the array
|
|
640
|
+
const breakpointIndex = value.findIndex((breakpoint) => breakpoint.id === currentBreakpoint.id);
|
|
641
|
+
return breakpointIndex !== currentBreakpointIndex;
|
|
642
|
+
});
|
|
643
|
+
if (hasDuplicateIds) {
|
|
644
|
+
ctx.addIssue({
|
|
645
|
+
code: z.ZodIssueCode.custom,
|
|
646
|
+
message: `Breakpoint IDs must be unique`,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
// Extract the queries boundary by removing the special characters around it
|
|
650
|
+
const queries = value.map((bp) => bp.query === '*' ? bp.query : parseInt(bp.query.replace(/px|<|>/, '')));
|
|
651
|
+
// sort updates queries array in place so we need to create a copy
|
|
652
|
+
const originalQueries = [...queries];
|
|
653
|
+
queries.sort((q1, q2) => {
|
|
654
|
+
if (q1 === '*') {
|
|
655
|
+
return -1;
|
|
656
|
+
}
|
|
657
|
+
if (q2 === '*') {
|
|
658
|
+
return 1;
|
|
659
|
+
}
|
|
660
|
+
return q1 > q2 ? -1 : 1;
|
|
661
|
+
});
|
|
662
|
+
if (originalQueries.join('') !== queries.join('')) {
|
|
663
|
+
ctx.addIssue({
|
|
664
|
+
code: z.ZodIssueCode.custom,
|
|
665
|
+
message: `Breakpoints should be ordered from largest to smallest pixel value`,
|
|
666
|
+
});
|
|
667
|
+
}
|
|
668
|
+
};
|
|
888
669
|
const ValuesByBreakpointSchema$1 = z.record(z.lazy(() => PrimitiveValueSchema$1));
|
|
670
|
+
const BindingSourceTypeEnumSchema$1 = z
|
|
671
|
+
.array(z.enum(['entry', 'asset', 'manual', 'experience']))
|
|
672
|
+
.nonempty();
|
|
889
673
|
const DesignValueSchema$1 = z
|
|
890
674
|
.object({
|
|
891
675
|
type: z.literal('DesignValue'),
|
|
@@ -918,8 +702,6 @@ const ComponentValueSchema$1 = z
|
|
|
918
702
|
key: z.string(),
|
|
919
703
|
})
|
|
920
704
|
.strict();
|
|
921
|
-
// TODO: finalize schema structure before release
|
|
922
|
-
// https://contentful.atlassian.net/browse/LUMOS-523
|
|
923
705
|
const NoValueSchema$1 = z.object({ type: z.literal('NoValue') }).strict();
|
|
924
706
|
const ComponentPropertyValueSchema$1 = z.discriminatedUnion('type', [
|
|
925
707
|
DesignValueSchema$1,
|
|
@@ -931,41 +713,12 @@ const ComponentPropertyValueSchema$1 = z.discriminatedUnion('type', [
|
|
|
931
713
|
]);
|
|
932
714
|
// TODO: finalize schema structure before release
|
|
933
715
|
// https://contentful.atlassian.net/browse/LUMOS-523
|
|
934
|
-
const VariableMappingSchema$1 = z.object({
|
|
935
|
-
patternPropertyDefinitionId: propertyKeySchema$1,
|
|
936
|
-
type: z.literal('ContentTypeMapping'),
|
|
937
|
-
pathsByContentType: z.record(z.string(), z.object({ path: z.string() })),
|
|
938
|
-
});
|
|
939
|
-
const VariableMappingsSchema$1 = z.record(propertyKeySchema$1, VariableMappingSchema$1);
|
|
940
|
-
// TODO: finalize schema structure before release
|
|
941
|
-
// https://contentful.atlassian.net/browse/LUMOS-523
|
|
942
|
-
const PatternPropertyDefinitionSchema$1 = z.object({
|
|
943
|
-
defaultValue: z
|
|
944
|
-
.record(z.string(), z.object({
|
|
945
|
-
sys: z.object({
|
|
946
|
-
type: z.literal('Link'),
|
|
947
|
-
id: z.string(),
|
|
948
|
-
linkType: z.enum(['Entry']),
|
|
949
|
-
}),
|
|
950
|
-
}))
|
|
951
|
-
.optional(),
|
|
952
|
-
contentTypes: z.record(z.string(), z.object({
|
|
953
|
-
sys: z.object({
|
|
954
|
-
type: z.literal('Link'),
|
|
955
|
-
id: z.string(),
|
|
956
|
-
linkType: z.enum(['ContentType']),
|
|
957
|
-
}),
|
|
958
|
-
})),
|
|
959
|
-
});
|
|
960
|
-
const PatternPropertyDefinitionsSchema$1 = z.record(propertyKeySchema$1, PatternPropertyDefinitionSchema$1);
|
|
961
|
-
// TODO: finalize schema structure before release
|
|
962
|
-
// https://contentful.atlassian.net/browse/LUMOS-523
|
|
963
716
|
const PatternPropertySchema$1 = z.object({
|
|
964
717
|
type: z.literal('BoundValue'),
|
|
965
718
|
path: z.string(),
|
|
966
719
|
contentType: z.string(),
|
|
967
720
|
});
|
|
968
|
-
const
|
|
721
|
+
const PatternPropertiesSchema$1 = z.record(propertyKeySchema$1, PatternPropertySchema$1);
|
|
969
722
|
const BreakpointSchema$1 = z
|
|
970
723
|
.object({
|
|
971
724
|
id: propertyKeySchema$1,
|
|
@@ -975,12 +728,6 @@ const BreakpointSchema$1 = z
|
|
|
975
728
|
displayIcon: z.enum(['desktop', 'tablet', 'mobile']).optional(),
|
|
976
729
|
})
|
|
977
730
|
.strict();
|
|
978
|
-
const UnboundValuesSchema$1 = z.record(uuidKeySchema$1, z.object({
|
|
979
|
-
value: PrimitiveValueSchema$1,
|
|
980
|
-
}));
|
|
981
|
-
const ComponentTreeNodeIdSchema$1 = z
|
|
982
|
-
.string()
|
|
983
|
-
.regex(/^[a-zA-Z0-9]{1,8}$/, { message: 'Does not match /^[a-zA-Z0-9]{1,8}$/' });
|
|
984
731
|
// Use helper schema to define a recursive schema with its type correctly below
|
|
985
732
|
const BaseComponentTreeNodeSchema$1 = z.object({
|
|
986
733
|
id: ComponentTreeNodeIdSchema$1.optional(),
|
|
@@ -988,14 +735,8 @@ const BaseComponentTreeNodeSchema$1 = z.object({
|
|
|
988
735
|
displayName: z.string().optional(),
|
|
989
736
|
slotId: z.string().optional(),
|
|
990
737
|
variables: z.record(propertyKeySchema$1, ComponentPropertyValueSchema$1),
|
|
991
|
-
patternProperties:
|
|
992
|
-
});
|
|
993
|
-
const ComponentTreeNodeSchema$1 = BaseComponentTreeNodeSchema$1.extend({
|
|
994
|
-
children: z.lazy(() => ComponentTreeNodeSchema$1.array()),
|
|
738
|
+
patternProperties: PatternPropertiesSchema$1.optional(),
|
|
995
739
|
});
|
|
996
|
-
const BindingSourceTypeEnumSchema$1 = z
|
|
997
|
-
.array(z.enum(['entry', 'asset', 'manual', 'experience']))
|
|
998
|
-
.nonempty();
|
|
999
740
|
const ComponentVariableSchema$1 = z.object({
|
|
1000
741
|
displayName: z.string().optional(),
|
|
1001
742
|
type: DefinitionPropertyTypeSchema$1,
|
|
@@ -1016,8 +757,25 @@ const ComponentVariableSchema$1 = z.object({
|
|
|
1016
757
|
})
|
|
1017
758
|
.optional(),
|
|
1018
759
|
});
|
|
1019
|
-
const
|
|
1020
|
-
|
|
760
|
+
const ComponentTreeNodeSchema$1 = BaseComponentTreeNodeSchema$1.extend({
|
|
761
|
+
children: z.lazy(() => ComponentTreeNodeSchema$1.array()),
|
|
762
|
+
});
|
|
763
|
+
const ComponentTreeSchema$1 = z
|
|
764
|
+
.object({
|
|
765
|
+
breakpoints: z.array(BreakpointSchema$1).superRefine(breakpointsRefinement$1),
|
|
766
|
+
children: z.array(ComponentTreeNodeSchema$1),
|
|
767
|
+
schemaVersion: SchemaVersions$1,
|
|
768
|
+
})
|
|
769
|
+
.strict();
|
|
770
|
+
const localeWrapper$1 = (fieldSchema) => z.record(z.string(), fieldSchema);
|
|
771
|
+
|
|
772
|
+
z.object({
|
|
773
|
+
componentTree: localeWrapper$1(ComponentTreeSchema$1),
|
|
774
|
+
dataSource: localeWrapper$1(DataSourceSchema$1),
|
|
775
|
+
unboundValues: localeWrapper$1(UnboundValuesSchema$1),
|
|
776
|
+
usedComponents: localeWrapper$1(UsedComponentsSchema$1).optional(),
|
|
777
|
+
});
|
|
778
|
+
|
|
1021
779
|
const THUMBNAIL_IDS$1 = [
|
|
1022
780
|
'columns',
|
|
1023
781
|
'columnsPlusRight',
|
|
@@ -1043,6 +801,37 @@ const THUMBNAIL_IDS$1 = [
|
|
|
1043
801
|
'textColumns',
|
|
1044
802
|
'duplex',
|
|
1045
803
|
];
|
|
804
|
+
// TODO: finalize schema structure before release
|
|
805
|
+
// https://contentful.atlassian.net/browse/LUMOS-523
|
|
806
|
+
const VariableMappingSchema$1 = z.object({
|
|
807
|
+
patternPropertyDefinitionId: propertyKeySchema$1,
|
|
808
|
+
type: z.literal('ContentTypeMapping'),
|
|
809
|
+
pathsByContentType: z.record(z.string(), z.object({ path: z.string() })),
|
|
810
|
+
});
|
|
811
|
+
// TODO: finalize schema structure before release
|
|
812
|
+
// https://contentful.atlassian.net/browse/LUMOS-523
|
|
813
|
+
const PatternPropertyDefinitionSchema$1 = z.object({
|
|
814
|
+
defaultValue: z
|
|
815
|
+
.record(z.string(), z.object({
|
|
816
|
+
sys: z.object({
|
|
817
|
+
type: z.literal('Link'),
|
|
818
|
+
id: z.string(),
|
|
819
|
+
linkType: z.enum(['Entry']),
|
|
820
|
+
}),
|
|
821
|
+
}))
|
|
822
|
+
.optional(),
|
|
823
|
+
contentTypes: z.record(z.string(), z.object({
|
|
824
|
+
sys: z.object({
|
|
825
|
+
type: z.literal('Link'),
|
|
826
|
+
id: z.string(),
|
|
827
|
+
linkType: z.enum(['ContentType']),
|
|
828
|
+
}),
|
|
829
|
+
})),
|
|
830
|
+
});
|
|
831
|
+
const PatternPropertyDefinitionsSchema$1 = z.record(propertyKeySchema$1, PatternPropertyDefinitionSchema$1);
|
|
832
|
+
const VariableMappingsSchema$1 = z.record(propertyKeySchema$1, VariableMappingSchema$1);
|
|
833
|
+
const ComponentVariablesSchema$1 = z.record(z.string().regex(/^[a-zA-Z0-9-_]{1,54}$/), // Here the key is <variableName>_<nanoidId> so we need to allow for a longer length
|
|
834
|
+
ComponentVariableSchema$1);
|
|
1046
835
|
const ComponentSettingsSchema$1 = z.object({
|
|
1047
836
|
variableDefinitions: ComponentVariablesSchema$1,
|
|
1048
837
|
thumbnailId: z.enum(THUMBNAIL_IDS$1).optional(),
|
|
@@ -1050,65 +839,12 @@ const ComponentSettingsSchema$1 = z.object({
|
|
|
1050
839
|
variableMappings: VariableMappingsSchema$1.optional(),
|
|
1051
840
|
patternPropertyDefinitions: PatternPropertyDefinitionsSchema$1.optional(),
|
|
1052
841
|
});
|
|
1053
|
-
const UsedComponentsSchema$1 = z.array(z.object({
|
|
1054
|
-
sys: z.object({
|
|
1055
|
-
type: z.literal('Link'),
|
|
1056
|
-
id: z.string(),
|
|
1057
|
-
linkType: z.literal('Entry'),
|
|
1058
|
-
}),
|
|
1059
|
-
}));
|
|
1060
|
-
const breakpointsRefinement$1 = (value, ctx) => {
|
|
1061
|
-
if (!value.length || value[0].query !== '*') {
|
|
1062
|
-
ctx.addIssue({
|
|
1063
|
-
code: z.ZodIssueCode.custom,
|
|
1064
|
-
message: `The first breakpoint should include the following attributes: { "query": "*" }`,
|
|
1065
|
-
});
|
|
1066
|
-
}
|
|
1067
|
-
const hasDuplicateIds = value.some((currentBreakpoint, currentBreakpointIndex) => {
|
|
1068
|
-
// check if the current breakpoint id is found in the rest of the array
|
|
1069
|
-
const breakpointIndex = value.findIndex((breakpoint) => breakpoint.id === currentBreakpoint.id);
|
|
1070
|
-
return breakpointIndex !== currentBreakpointIndex;
|
|
1071
|
-
});
|
|
1072
|
-
if (hasDuplicateIds) {
|
|
1073
|
-
ctx.addIssue({
|
|
1074
|
-
code: z.ZodIssueCode.custom,
|
|
1075
|
-
message: `Breakpoint IDs must be unique`,
|
|
1076
|
-
});
|
|
1077
|
-
}
|
|
1078
|
-
// Extract the queries boundary by removing the special characters around it
|
|
1079
|
-
const queries = value.map((bp) => bp.query === '*' ? bp.query : parseInt(bp.query.replace(/px|<|>/, '')));
|
|
1080
|
-
// sort updates queries array in place so we need to create a copy
|
|
1081
|
-
const originalQueries = [...queries];
|
|
1082
|
-
queries.sort((q1, q2) => {
|
|
1083
|
-
if (q1 === '*') {
|
|
1084
|
-
return -1;
|
|
1085
|
-
}
|
|
1086
|
-
if (q2 === '*') {
|
|
1087
|
-
return 1;
|
|
1088
|
-
}
|
|
1089
|
-
return q1 > q2 ? -1 : 1;
|
|
1090
|
-
});
|
|
1091
|
-
if (originalQueries.join('') !== queries.join('')) {
|
|
1092
|
-
ctx.addIssue({
|
|
1093
|
-
code: z.ZodIssueCode.custom,
|
|
1094
|
-
message: `Breakpoints should be ordered from largest to smallest pixel value`,
|
|
1095
|
-
});
|
|
1096
|
-
}
|
|
1097
|
-
};
|
|
1098
|
-
const ComponentTreeSchema$1 = z
|
|
1099
|
-
.object({
|
|
1100
|
-
breakpoints: z.array(BreakpointSchema$1).superRefine(breakpointsRefinement$1),
|
|
1101
|
-
children: z.array(ComponentTreeNodeSchema$1),
|
|
1102
|
-
schemaVersion: SchemaVersions$1,
|
|
1103
|
-
})
|
|
1104
|
-
.strict();
|
|
1105
|
-
const localeWrapper$1 = (fieldSchema) => z.record(z.string(), fieldSchema);
|
|
1106
842
|
z.object({
|
|
1107
843
|
componentTree: localeWrapper$1(ComponentTreeSchema$1),
|
|
1108
844
|
dataSource: localeWrapper$1(DataSourceSchema$1),
|
|
1109
845
|
unboundValues: localeWrapper$1(UnboundValuesSchema$1),
|
|
1110
846
|
usedComponents: localeWrapper$1(UsedComponentsSchema$1).optional(),
|
|
1111
|
-
componentSettings: localeWrapper$1(ComponentSettingsSchema$1)
|
|
847
|
+
componentSettings: localeWrapper$1(ComponentSettingsSchema$1),
|
|
1112
848
|
});
|
|
1113
849
|
|
|
1114
850
|
z.object({
|
|
@@ -1345,6 +1081,32 @@ propertyName, resolveDesignTokens = true) => {
|
|
|
1345
1081
|
};
|
|
1346
1082
|
|
|
1347
1083
|
const CF_DEBUG_KEY$1 = 'cf_debug';
|
|
1084
|
+
/**
|
|
1085
|
+
* To ensure that the localStorage API can be used safely, we check
|
|
1086
|
+
* for availability (e.g. undefined in Node.js). Additionally, we
|
|
1087
|
+
* check if the localStorage can be used as some browsers throw a
|
|
1088
|
+
* SecurityError (e.g. Brave or Chromium with specific settings).
|
|
1089
|
+
*/
|
|
1090
|
+
const checkLocalStorageAvailability$1 = () => {
|
|
1091
|
+
if (typeof localStorage === 'undefined' || localStorage === null) {
|
|
1092
|
+
return false;
|
|
1093
|
+
}
|
|
1094
|
+
try {
|
|
1095
|
+
// Attempt to set and remove an item to check if localStorage is enabled
|
|
1096
|
+
const TEST_KEY = 'cf_test_local_storage';
|
|
1097
|
+
localStorage.setItem(TEST_KEY, 'yes');
|
|
1098
|
+
if (localStorage.getItem(TEST_KEY) === 'yes') {
|
|
1099
|
+
localStorage.removeItem(TEST_KEY);
|
|
1100
|
+
return true;
|
|
1101
|
+
}
|
|
1102
|
+
else {
|
|
1103
|
+
return false;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
catch (_error) {
|
|
1107
|
+
return false;
|
|
1108
|
+
}
|
|
1109
|
+
};
|
|
1348
1110
|
let DebugLogger$1 = class DebugLogger {
|
|
1349
1111
|
constructor() {
|
|
1350
1112
|
// Public methods for logging
|
|
@@ -1352,7 +1114,7 @@ let DebugLogger$1 = class DebugLogger {
|
|
|
1352
1114
|
this.warn = this.logger('warn');
|
|
1353
1115
|
this.log = this.logger('log');
|
|
1354
1116
|
this.debug = this.logger('debug');
|
|
1355
|
-
if (
|
|
1117
|
+
if (!checkLocalStorageAvailability$1()) {
|
|
1356
1118
|
this.enabled = false;
|
|
1357
1119
|
return;
|
|
1358
1120
|
}
|
|
@@ -1392,18 +1154,63 @@ let DebugLogger$1 = class DebugLogger {
|
|
|
1392
1154
|
DebugLogger$1.instance = null;
|
|
1393
1155
|
DebugLogger$1.getInstance();
|
|
1394
1156
|
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
typeof variable.sys?.id === 'string' &&
|
|
1403
|
-
!!variable.sys?.id &&
|
|
1404
|
-
variable.sys?.type === 'Link');
|
|
1157
|
+
const findOutermostCoordinates = (first, second) => {
|
|
1158
|
+
return {
|
|
1159
|
+
top: Math.min(first.top, second.top),
|
|
1160
|
+
right: Math.max(first.right, second.right),
|
|
1161
|
+
bottom: Math.max(first.bottom, second.bottom),
|
|
1162
|
+
left: Math.min(first.left, second.left),
|
|
1163
|
+
};
|
|
1405
1164
|
};
|
|
1406
|
-
|
|
1165
|
+
const getElementCoordinates = (element) => {
|
|
1166
|
+
const rect = element.getBoundingClientRect();
|
|
1167
|
+
/**
|
|
1168
|
+
* If element does not have children, or element has it's own width or height,
|
|
1169
|
+
* return the element's coordinates.
|
|
1170
|
+
*/
|
|
1171
|
+
if (element.children.length === 0 || rect.width !== 0 || rect.height !== 0) {
|
|
1172
|
+
return rect;
|
|
1173
|
+
}
|
|
1174
|
+
const rects = [];
|
|
1175
|
+
/**
|
|
1176
|
+
* If element has children, or element does not have it's own width and height,
|
|
1177
|
+
* we find the cordinates of the children, and assume the outermost coordinates of the children
|
|
1178
|
+
* as the coordinate of the element.
|
|
1179
|
+
*
|
|
1180
|
+
* E.g child1 => {top: 2, bottom: 3, left: 4, right: 6} & child2 => {top: 1, bottom: 8, left: 12, right: 24}
|
|
1181
|
+
* The final assumed coordinates of the element would be => { top: 1, right: 24, bottom: 8, left: 4 }
|
|
1182
|
+
*/
|
|
1183
|
+
for (const child of element.children) {
|
|
1184
|
+
const childRect = getElementCoordinates(child);
|
|
1185
|
+
if (childRect.width !== 0 || childRect.height !== 0) {
|
|
1186
|
+
const { top, right, bottom, left } = childRect;
|
|
1187
|
+
rects.push({ top, right, bottom, left });
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
if (rects.length === 0) {
|
|
1191
|
+
return rect;
|
|
1192
|
+
}
|
|
1193
|
+
const { top, right, bottom, left } = rects.reduce(findOutermostCoordinates);
|
|
1194
|
+
return DOMRect.fromRect({
|
|
1195
|
+
x: left,
|
|
1196
|
+
y: top,
|
|
1197
|
+
height: bottom - top,
|
|
1198
|
+
width: right - left,
|
|
1199
|
+
});
|
|
1200
|
+
};
|
|
1201
|
+
|
|
1202
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
1203
|
+
const isLinkToAsset = (variable) => {
|
|
1204
|
+
if (!variable)
|
|
1205
|
+
return false;
|
|
1206
|
+
if (typeof variable !== 'object')
|
|
1207
|
+
return false;
|
|
1208
|
+
return (variable.sys?.linkType === 'Asset' &&
|
|
1209
|
+
typeof variable.sys?.id === 'string' &&
|
|
1210
|
+
!!variable.sys?.id &&
|
|
1211
|
+
variable.sys?.type === 'Link');
|
|
1212
|
+
};
|
|
1213
|
+
|
|
1407
1214
|
const isLink = (maybeLink) => {
|
|
1408
1215
|
if (maybeLink === null)
|
|
1409
1216
|
return false;
|
|
@@ -1803,7 +1610,7 @@ const calculateNodeDefaultHeight = ({ blockId, children, value, }) => {
|
|
|
1803
1610
|
if (children.length) {
|
|
1804
1611
|
return '100%';
|
|
1805
1612
|
}
|
|
1806
|
-
return EMPTY_CONTAINER_HEIGHT;
|
|
1613
|
+
return EMPTY_CONTAINER_HEIGHT$1;
|
|
1807
1614
|
};
|
|
1808
1615
|
|
|
1809
1616
|
function getOptimizedImageUrl(url, width, quality, format) {
|
|
@@ -2234,10 +2041,10 @@ const tryParseMessage = (event) => {
|
|
|
2234
2041
|
throw new ParseError(`Field eventData.source must be equal to 'composability-app', instead of '${eventData.source}'`);
|
|
2235
2042
|
}
|
|
2236
2043
|
// check eventData.eventType
|
|
2237
|
-
const supportedEventTypes = Object.values(INCOMING_EVENTS);
|
|
2044
|
+
const supportedEventTypes = Object.values(INCOMING_EVENTS$1);
|
|
2238
2045
|
if (!supportedEventTypes.includes(eventData.eventType)) {
|
|
2239
2046
|
// Expected message: This message is handled in the EntityStore to store fetched entities
|
|
2240
|
-
if (eventData.eventType !== PostMessageMethods$
|
|
2047
|
+
if (eventData.eventType !== PostMessageMethods$3.REQUESTED_ENTITIES) {
|
|
2241
2048
|
throw new ParseError(`Field eventData.eventType must be one of the supported values: [${supportedEventTypes.join(', ')}]`);
|
|
2242
2049
|
}
|
|
2243
2050
|
}
|
|
@@ -2502,7 +2309,7 @@ class EditorEntityStore extends EntityStoreBase {
|
|
|
2502
2309
|
}
|
|
2503
2310
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2504
2311
|
const newPromise = new Promise((resolve, reject) => {
|
|
2505
|
-
const unsubscribe = this.subscribe(PostMessageMethods$
|
|
2312
|
+
const unsubscribe = this.subscribe(PostMessageMethods$3.REQUESTED_ENTITIES, (message) => {
|
|
2506
2313
|
const messageIds = [
|
|
2507
2314
|
...message.entities.map((entity) => entity.sys.id),
|
|
2508
2315
|
...(message.missingEntityIds ?? []),
|
|
@@ -2524,7 +2331,7 @@ class EditorEntityStore extends EntityStoreBase {
|
|
|
2524
2331
|
ids.forEach((id) => this.cleanupPromise(id));
|
|
2525
2332
|
unsubscribe();
|
|
2526
2333
|
}, this.timeoutDuration);
|
|
2527
|
-
this.sendMessage(PostMessageMethods$
|
|
2334
|
+
this.sendMessage(PostMessageMethods$3.REQUEST_ENTITIES, {
|
|
2528
2335
|
entityIds: missing,
|
|
2529
2336
|
entityType: type,
|
|
2530
2337
|
locale: this.locale,
|
|
@@ -2603,97 +2410,590 @@ class EditorModeEntityStore extends EditorEntityStore {
|
|
|
2603
2410
|
super({ entities, sendMessage, subscribe, locale, timeoutDuration: REQUEST_TIMEOUT });
|
|
2604
2411
|
this.locale = locale;
|
|
2605
2412
|
}
|
|
2606
|
-
/**
|
|
2607
|
-
* This function collects and returns the list of requested entries and assets. Additionally, it checks
|
|
2608
|
-
* upfront whether any async fetching logic is actually happening. If not, it returns a plain `false` value, so we
|
|
2609
|
-
* can detect this early and avoid unnecessary re-renders.
|
|
2610
|
-
* @param entityLinks
|
|
2611
|
-
* @returns false if no async fetching is happening, otherwise a promise that resolves when all entities are fetched
|
|
2612
|
-
*/
|
|
2613
|
-
async fetchEntities({ missingEntryIds, missingAssetIds, skipCache = false, }) {
|
|
2614
|
-
// Entries and assets will be stored in entryMap and assetMap
|
|
2615
|
-
await Promise.all([
|
|
2616
|
-
super.fetchEntries(missingEntryIds, skipCache),
|
|
2617
|
-
super.fetchAssets(missingAssetIds, skipCache),
|
|
2618
|
-
]);
|
|
2413
|
+
/**
|
|
2414
|
+
* This function collects and returns the list of requested entries and assets. Additionally, it checks
|
|
2415
|
+
* upfront whether any async fetching logic is actually happening. If not, it returns a plain `false` value, so we
|
|
2416
|
+
* can detect this early and avoid unnecessary re-renders.
|
|
2417
|
+
* @param entityLinks
|
|
2418
|
+
* @returns false if no async fetching is happening, otherwise a promise that resolves when all entities are fetched
|
|
2419
|
+
*/
|
|
2420
|
+
async fetchEntities({ missingEntryIds, missingAssetIds, skipCache = false, }) {
|
|
2421
|
+
// Entries and assets will be stored in entryMap and assetMap
|
|
2422
|
+
await Promise.all([
|
|
2423
|
+
super.fetchEntries(missingEntryIds, skipCache),
|
|
2424
|
+
super.fetchAssets(missingAssetIds, skipCache),
|
|
2425
|
+
]);
|
|
2426
|
+
}
|
|
2427
|
+
getMissingEntityIds(entityLinks) {
|
|
2428
|
+
const entryLinks = entityLinks.filter((link) => link.sys?.linkType === 'Entry');
|
|
2429
|
+
const assetLinks = entityLinks.filter((link) => link.sys?.linkType === 'Asset');
|
|
2430
|
+
const uniqueEntryIds = [...new Set(entryLinks.map((link) => link.sys.id))];
|
|
2431
|
+
const uniqueAssetIds = [...new Set(assetLinks.map((link) => link.sys.id))];
|
|
2432
|
+
const { missing: missingEntryIds } = this.getEntitiesFromMap('Entry', uniqueEntryIds);
|
|
2433
|
+
const { missing: missingAssetIds } = this.getEntitiesFromMap('Asset', uniqueAssetIds);
|
|
2434
|
+
return { missingEntryIds, missingAssetIds };
|
|
2435
|
+
}
|
|
2436
|
+
getValue(entityLinkOrEntity, path) {
|
|
2437
|
+
const entity = this.getEntryOrAsset(entityLinkOrEntity, path.join('/'));
|
|
2438
|
+
if (!entity) {
|
|
2439
|
+
return;
|
|
2440
|
+
}
|
|
2441
|
+
const fieldValue = get(entity, path);
|
|
2442
|
+
return transformAssetFileToUrl(fieldValue);
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
|
|
2446
|
+
var VisualEditorMode$1;
|
|
2447
|
+
(function (VisualEditorMode) {
|
|
2448
|
+
VisualEditorMode["LazyLoad"] = "lazyLoad";
|
|
2449
|
+
VisualEditorMode["InjectScript"] = "injectScript";
|
|
2450
|
+
})(VisualEditorMode$1 || (VisualEditorMode$1 = {}));
|
|
2451
|
+
|
|
2452
|
+
class DeepReference {
|
|
2453
|
+
constructor({ path, dataSource }) {
|
|
2454
|
+
const { key, field, referentField } = parseDataSourcePathWithL1DeepBindings(path);
|
|
2455
|
+
this.originalPath = path;
|
|
2456
|
+
this.entityId = dataSource[key].sys.id;
|
|
2457
|
+
this.entityLink = dataSource[key];
|
|
2458
|
+
this.field = field;
|
|
2459
|
+
this.referentField = referentField;
|
|
2460
|
+
}
|
|
2461
|
+
get headEntityId() {
|
|
2462
|
+
return this.entityId;
|
|
2463
|
+
}
|
|
2464
|
+
/**
|
|
2465
|
+
* Extracts referent from the path, using EntityStore as source of
|
|
2466
|
+
* entities during the resolution path.
|
|
2467
|
+
*/
|
|
2468
|
+
extractReferent(entityStore) {
|
|
2469
|
+
const headEntity = entityStore.getEntityFromLink(this.entityLink);
|
|
2470
|
+
const maybeReferentLink = headEntity?.fields[this.field];
|
|
2471
|
+
if (undefined === maybeReferentLink) {
|
|
2472
|
+
// field references nothing (or even field doesn't exist)
|
|
2473
|
+
return undefined;
|
|
2474
|
+
}
|
|
2475
|
+
if (!isLink(maybeReferentLink)) {
|
|
2476
|
+
// Scenario of "impostor referent", where one of the deepPath's segments is not a Link but some other type
|
|
2477
|
+
// Under normal circumstance we expect field to be a Link, but it could be an "impostor"
|
|
2478
|
+
// eg. `Text` or `Number` or anything like that; could be due to CT changes or manual path creation via CMA
|
|
2479
|
+
return undefined;
|
|
2480
|
+
}
|
|
2481
|
+
return maybeReferentLink;
|
|
2482
|
+
}
|
|
2483
|
+
static from(opt) {
|
|
2484
|
+
return new DeepReference(opt);
|
|
2485
|
+
}
|
|
2486
|
+
}
|
|
2487
|
+
function gatherDeepReferencesFromTree(startingNode, dataSource) {
|
|
2488
|
+
const deepReferences = [];
|
|
2489
|
+
treeVisit(startingNode, (node) => {
|
|
2490
|
+
if (!node.data.props)
|
|
2491
|
+
return;
|
|
2492
|
+
for (const [, variableMapping] of Object.entries(node.data.props)) {
|
|
2493
|
+
if (variableMapping.type !== 'BoundValue')
|
|
2494
|
+
continue;
|
|
2495
|
+
if (!isDeepPath(variableMapping.path))
|
|
2496
|
+
continue;
|
|
2497
|
+
deepReferences.push(DeepReference.from({
|
|
2498
|
+
path: variableMapping.path,
|
|
2499
|
+
dataSource,
|
|
2500
|
+
}));
|
|
2501
|
+
}
|
|
2502
|
+
});
|
|
2503
|
+
return deepReferences;
|
|
2504
|
+
}
|
|
2505
|
+
|
|
2506
|
+
const useDraggedItemStore = create((set) => ({
|
|
2507
|
+
draggedItem: undefined,
|
|
2508
|
+
hoveredComponentId: undefined,
|
|
2509
|
+
domRect: undefined,
|
|
2510
|
+
componentId: '',
|
|
2511
|
+
isDraggingOnCanvas: false,
|
|
2512
|
+
onBeforeCaptureId: '',
|
|
2513
|
+
mouseX: 0,
|
|
2514
|
+
mouseY: 0,
|
|
2515
|
+
scrollY: 0,
|
|
2516
|
+
setComponentId(id) {
|
|
2517
|
+
set({ componentId: id });
|
|
2518
|
+
},
|
|
2519
|
+
setHoveredComponentId(id) {
|
|
2520
|
+
set({ hoveredComponentId: id });
|
|
2521
|
+
},
|
|
2522
|
+
updateItem: (item) => {
|
|
2523
|
+
set({ draggedItem: item });
|
|
2524
|
+
},
|
|
2525
|
+
setDraggingOnCanvas: (isDraggingOnCanvas) => {
|
|
2526
|
+
set({ isDraggingOnCanvas });
|
|
2527
|
+
},
|
|
2528
|
+
setOnBeforeCaptureId: (onBeforeCaptureId) => {
|
|
2529
|
+
set({ onBeforeCaptureId });
|
|
2530
|
+
},
|
|
2531
|
+
setMousePosition(x, y) {
|
|
2532
|
+
set({ mouseX: x, mouseY: y });
|
|
2533
|
+
},
|
|
2534
|
+
setDomRect(domRect) {
|
|
2535
|
+
set({ domRect });
|
|
2536
|
+
},
|
|
2537
|
+
setScrollY(y) {
|
|
2538
|
+
set({ scrollY: y });
|
|
2539
|
+
},
|
|
2540
|
+
}));
|
|
2541
|
+
|
|
2542
|
+
const SCROLL_STATES = {
|
|
2543
|
+
Start: 'scrollStart',
|
|
2544
|
+
IsScrolling: 'isScrolling',
|
|
2545
|
+
End: 'scrollEnd',
|
|
2546
|
+
};
|
|
2547
|
+
const OUTGOING_EVENTS = {
|
|
2548
|
+
Connected: 'connected',
|
|
2549
|
+
DesignTokens: 'registerDesignTokens',
|
|
2550
|
+
RegisteredBreakpoints: 'registeredBreakpoints',
|
|
2551
|
+
MouseMove: 'mouseMove',
|
|
2552
|
+
NewHoveredElement: 'newHoveredElement',
|
|
2553
|
+
ComponentSelected: 'componentSelected',
|
|
2554
|
+
RegisteredComponents: 'registeredComponents',
|
|
2555
|
+
RequestComponentTreeUpdate: 'requestComponentTreeUpdate',
|
|
2556
|
+
ComponentDragCanceled: 'componentDragCanceled',
|
|
2557
|
+
ComponentDropped: 'componentDropped',
|
|
2558
|
+
ComponentMoved: 'componentMoved',
|
|
2559
|
+
CanvasReload: 'canvasReload',
|
|
2560
|
+
UpdateSelectedComponentCoordinates: 'updateSelectedComponentCoordinates',
|
|
2561
|
+
CanvasScroll: 'canvasScrolling',
|
|
2562
|
+
CanvasError: 'canvasError',
|
|
2563
|
+
ComponentMoveStarted: 'componentMoveStarted',
|
|
2564
|
+
ComponentMoveEnded: 'componentMoveEnded',
|
|
2565
|
+
OutsideCanvasClick: 'outsideCanvasClick',
|
|
2566
|
+
SDKFeatures: 'sdkFeatures',
|
|
2567
|
+
RequestEntities: 'REQUEST_ENTITIES',
|
|
2568
|
+
};
|
|
2569
|
+
const INCOMING_EVENTS = {
|
|
2570
|
+
RequestEditorMode: 'requestEditorMode',
|
|
2571
|
+
RequestReadOnlyMode: 'requestReadOnlyMode',
|
|
2572
|
+
ExperienceUpdated: 'componentTreeUpdated',
|
|
2573
|
+
ComponentDraggingChanged: 'componentDraggingChanged',
|
|
2574
|
+
ComponentDragCanceled: 'componentDragCanceled',
|
|
2575
|
+
ComponentDragStarted: 'componentDragStarted',
|
|
2576
|
+
ComponentDragEnded: 'componentDragEnded',
|
|
2577
|
+
ComponentMoveEnded: 'componentMoveEnded',
|
|
2578
|
+
CanvasResized: 'canvasResized',
|
|
2579
|
+
SelectComponent: 'selectComponent',
|
|
2580
|
+
HoverComponent: 'hoverComponent',
|
|
2581
|
+
UpdatedEntity: 'updatedEntity',
|
|
2582
|
+
AssembliesAdded: 'assembliesAdded',
|
|
2583
|
+
AssembliesRegistered: 'assembliesRegistered',
|
|
2584
|
+
MouseMove: 'mouseMove',
|
|
2585
|
+
RequestedEntities: 'REQUESTED_ENTITIES',
|
|
2586
|
+
};
|
|
2587
|
+
const INTERNAL_EVENTS = {
|
|
2588
|
+
ComponentsRegistered: 'cfComponentsRegistered',
|
|
2589
|
+
VisualEditorInitialize: 'cfVisualEditorInitialize',
|
|
2590
|
+
};
|
|
2591
|
+
const VISUAL_EDITOR_EVENTS = {
|
|
2592
|
+
Ready: 'cfVisualEditorReady',
|
|
2593
|
+
};
|
|
2594
|
+
/**
|
|
2595
|
+
* These modes are ONLY intended to be internally used within the context of
|
|
2596
|
+
* editing an experience inside of Contentful Studio. i.e. these modes
|
|
2597
|
+
* intentionally do not include preview/delivery modes.
|
|
2598
|
+
*/
|
|
2599
|
+
var StudioCanvasMode$2;
|
|
2600
|
+
(function (StudioCanvasMode) {
|
|
2601
|
+
StudioCanvasMode["READ_ONLY"] = "readOnlyMode";
|
|
2602
|
+
StudioCanvasMode["EDITOR"] = "editorMode";
|
|
2603
|
+
StudioCanvasMode["NONE"] = "none";
|
|
2604
|
+
})(StudioCanvasMode$2 || (StudioCanvasMode$2 = {}));
|
|
2605
|
+
const CONTENTFUL_COMPONENTS$1 = {
|
|
2606
|
+
section: {
|
|
2607
|
+
id: 'contentful-section',
|
|
2608
|
+
name: 'Section',
|
|
2609
|
+
},
|
|
2610
|
+
container: {
|
|
2611
|
+
id: 'contentful-container',
|
|
2612
|
+
name: 'Container',
|
|
2613
|
+
},
|
|
2614
|
+
columns: {
|
|
2615
|
+
id: 'contentful-columns',
|
|
2616
|
+
name: 'Columns',
|
|
2617
|
+
},
|
|
2618
|
+
singleColumn: {
|
|
2619
|
+
id: 'contentful-single-column',
|
|
2620
|
+
name: 'Column',
|
|
2621
|
+
},
|
|
2622
|
+
button: {
|
|
2623
|
+
id: 'contentful-button',
|
|
2624
|
+
name: 'Button',
|
|
2625
|
+
},
|
|
2626
|
+
heading: {
|
|
2627
|
+
id: 'contentful-heading',
|
|
2628
|
+
name: 'Heading',
|
|
2629
|
+
},
|
|
2630
|
+
image: {
|
|
2631
|
+
id: 'contentful-image',
|
|
2632
|
+
name: 'Image',
|
|
2633
|
+
},
|
|
2634
|
+
richText: {
|
|
2635
|
+
id: 'contentful-richText',
|
|
2636
|
+
name: 'Rich Text',
|
|
2637
|
+
},
|
|
2638
|
+
text: {
|
|
2639
|
+
id: 'contentful-text',
|
|
2640
|
+
name: 'Text',
|
|
2641
|
+
},
|
|
2642
|
+
divider: {
|
|
2643
|
+
id: 'contentful-divider',
|
|
2644
|
+
name: 'Divider',
|
|
2645
|
+
},
|
|
2646
|
+
carousel: {
|
|
2647
|
+
id: 'contentful-carousel',
|
|
2648
|
+
name: 'Carousel',
|
|
2649
|
+
},
|
|
2650
|
+
};
|
|
2651
|
+
const ASSEMBLY_NODE_TYPE = 'assembly';
|
|
2652
|
+
const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
|
|
2653
|
+
const ASSEMBLY_BLOCK_NODE_TYPE = 'assemblyBlock';
|
|
2654
|
+
const ASSEMBLY_NODE_TYPES = [ASSEMBLY_NODE_TYPE, ASSEMBLY_BLOCK_NODE_TYPE];
|
|
2655
|
+
const EMPTY_CONTAINER_HEIGHT = '80px';
|
|
2656
|
+
const HYPERLINK_DEFAULT_PATTERN = `/{locale}/{entry.fields.slug}/`;
|
|
2657
|
+
var PostMessageMethods$2;
|
|
2658
|
+
(function (PostMessageMethods) {
|
|
2659
|
+
PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
|
|
2660
|
+
PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
|
|
2661
|
+
})(PostMessageMethods$2 || (PostMessageMethods$2 = {}));
|
|
2662
|
+
|
|
2663
|
+
const DRAGGABLE_HEIGHT = 30;
|
|
2664
|
+
const DRAGGABLE_WIDTH = 50;
|
|
2665
|
+
const DRAG_PADDING = 4;
|
|
2666
|
+
const ROOT_ID = 'root';
|
|
2667
|
+
const COMPONENT_LIST_ID = 'component-list';
|
|
2668
|
+
const NEW_COMPONENT_ID = 'ctfl-new-draggable';
|
|
2669
|
+
const CTFL_ZONE_ID = 'data-ctfl-zone-id';
|
|
2670
|
+
const CTFL_DRAGGING_ELEMENT = 'data-ctfl-dragging-element';
|
|
2671
|
+
const HITBOX = {
|
|
2672
|
+
WIDTH: 70,
|
|
2673
|
+
HEIGHT: 20,
|
|
2674
|
+
INITIAL_OFFSET: 10,
|
|
2675
|
+
OFFSET_INCREMENT: 8,
|
|
2676
|
+
MIN_HEIGHT: 45,
|
|
2677
|
+
MIN_DEPTH_HEIGHT: 20,
|
|
2678
|
+
DEEP_ZONE: 5,
|
|
2679
|
+
};
|
|
2680
|
+
var TreeAction;
|
|
2681
|
+
(function (TreeAction) {
|
|
2682
|
+
TreeAction[TreeAction["REMOVE_NODE"] = 0] = "REMOVE_NODE";
|
|
2683
|
+
TreeAction[TreeAction["ADD_NODE"] = 1] = "ADD_NODE";
|
|
2684
|
+
TreeAction[TreeAction["MOVE_NODE"] = 2] = "MOVE_NODE";
|
|
2685
|
+
TreeAction[TreeAction["UPDATE_NODE"] = 3] = "UPDATE_NODE";
|
|
2686
|
+
TreeAction[TreeAction["REORDER_NODE"] = 4] = "REORDER_NODE";
|
|
2687
|
+
TreeAction[TreeAction["REPLACE_NODE"] = 5] = "REPLACE_NODE";
|
|
2688
|
+
})(TreeAction || (TreeAction = {}));
|
|
2689
|
+
var HitboxDirection;
|
|
2690
|
+
(function (HitboxDirection) {
|
|
2691
|
+
HitboxDirection[HitboxDirection["TOP"] = 0] = "TOP";
|
|
2692
|
+
HitboxDirection[HitboxDirection["LEFT"] = 1] = "LEFT";
|
|
2693
|
+
HitboxDirection[HitboxDirection["RIGHT"] = 2] = "RIGHT";
|
|
2694
|
+
HitboxDirection[HitboxDirection["BOTTOM"] = 3] = "BOTTOM";
|
|
2695
|
+
HitboxDirection[HitboxDirection["SELF_VERTICAL"] = 4] = "SELF_VERTICAL";
|
|
2696
|
+
HitboxDirection[HitboxDirection["SELF_HORIZONTAL"] = 5] = "SELF_HORIZONTAL";
|
|
2697
|
+
})(HitboxDirection || (HitboxDirection = {}));
|
|
2698
|
+
var DraggablePosition;
|
|
2699
|
+
(function (DraggablePosition) {
|
|
2700
|
+
DraggablePosition[DraggablePosition["CENTERED"] = 0] = "CENTERED";
|
|
2701
|
+
DraggablePosition[DraggablePosition["MOUSE_POSITION"] = 1] = "MOUSE_POSITION";
|
|
2702
|
+
})(DraggablePosition || (DraggablePosition = {}));
|
|
2703
|
+
|
|
2704
|
+
function useDraggablePosition({ draggableId, draggableRef, position }) {
|
|
2705
|
+
const isDraggingOnCanvas = useDraggedItemStore((state) => state.isDraggingOnCanvas);
|
|
2706
|
+
const draggingId = useDraggedItemStore((state) => state.onBeforeCaptureId);
|
|
2707
|
+
const preDragDomRect = useDraggedItemStore((state) => state.domRect);
|
|
2708
|
+
useEffect(() => {
|
|
2709
|
+
const el = draggableRef?.current ??
|
|
2710
|
+
document.querySelector(`[${CTFL_DRAGGING_ELEMENT}][data-cf-node-id="${draggableId}"]`);
|
|
2711
|
+
if (!isDraggingOnCanvas || draggingId !== draggableId || !el) {
|
|
2712
|
+
return;
|
|
2713
|
+
}
|
|
2714
|
+
const isCentered = position === DraggablePosition.CENTERED || !preDragDomRect;
|
|
2715
|
+
const domRect = isCentered ? el.getBoundingClientRect() : preDragDomRect;
|
|
2716
|
+
const { mouseX, mouseY } = useDraggedItemStore.getState();
|
|
2717
|
+
const top = isCentered ? mouseY - domRect.height / 2 : domRect.top;
|
|
2718
|
+
const left = isCentered ? mouseX - domRect.width / 2 : domRect.left;
|
|
2719
|
+
el.style.position = 'fixed';
|
|
2720
|
+
el.style.left = `${left}px`;
|
|
2721
|
+
el.style.top = `${top}px`;
|
|
2722
|
+
el.style.width = `${domRect.width}px`;
|
|
2723
|
+
el.style.height = `${domRect.height}px`;
|
|
2724
|
+
}, [draggableRef, draggableId, isDraggingOnCanvas, draggingId, position, preDragDomRect]);
|
|
2725
|
+
}
|
|
2726
|
+
|
|
2727
|
+
function getStyle$2(style, snapshot) {
|
|
2728
|
+
if (!snapshot.isDropAnimating) {
|
|
2729
|
+
return style;
|
|
2730
|
+
}
|
|
2731
|
+
return {
|
|
2732
|
+
...style,
|
|
2733
|
+
// cannot be 0, but make it super tiny
|
|
2734
|
+
transitionDuration: `0.001s`,
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
const DraggableContainer = ({ id }) => {
|
|
2738
|
+
const ref = useRef(null);
|
|
2739
|
+
useDraggablePosition({
|
|
2740
|
+
draggableId: id,
|
|
2741
|
+
draggableRef: ref,
|
|
2742
|
+
position: DraggablePosition.CENTERED,
|
|
2743
|
+
});
|
|
2744
|
+
return (React.createElement("div", { id: COMPONENT_LIST_ID, style: {
|
|
2745
|
+
position: 'absolute',
|
|
2746
|
+
top: 0,
|
|
2747
|
+
left: 0,
|
|
2748
|
+
pointerEvents: 'none',
|
|
2749
|
+
zIndex: -1,
|
|
2750
|
+
} },
|
|
2751
|
+
React.createElement(Droppable, { droppableId: COMPONENT_LIST_ID, isDropDisabled: true }, (provided) => (React.createElement("div", { ...provided.droppableProps, ref: provided.innerRef },
|
|
2752
|
+
React.createElement(Draggable, { draggableId: id, key: id, index: 0 }, (provided, snapshot) => (React.createElement("div", { id: NEW_COMPONENT_ID, "data-ctfl-dragging-element": true, ref: (node) => {
|
|
2753
|
+
provided.innerRef(node);
|
|
2754
|
+
ref.current = node;
|
|
2755
|
+
}, ...provided.draggableProps, ...provided.dragHandleProps, style: {
|
|
2756
|
+
...getStyle$2(provided.draggableProps.style, snapshot),
|
|
2757
|
+
width: DRAGGABLE_WIDTH,
|
|
2758
|
+
height: DRAGGABLE_HEIGHT,
|
|
2759
|
+
pointerEvents: 'none',
|
|
2760
|
+
} }))),
|
|
2761
|
+
provided.placeholder)))));
|
|
2762
|
+
};
|
|
2763
|
+
|
|
2764
|
+
function getItemFromTree(id, node) {
|
|
2765
|
+
// Check if the current node's id matches the search id
|
|
2766
|
+
if (node.data.id === id) {
|
|
2767
|
+
return node;
|
|
2768
|
+
}
|
|
2769
|
+
// Recursively search through each child
|
|
2770
|
+
for (const child of node.children) {
|
|
2771
|
+
const foundNode = getItemFromTree(id, child);
|
|
2772
|
+
if (foundNode) {
|
|
2773
|
+
// Node found in children
|
|
2774
|
+
return foundNode;
|
|
2775
|
+
}
|
|
2776
|
+
}
|
|
2777
|
+
// If the node is not found in this branch of the tree, return undefined
|
|
2778
|
+
return undefined;
|
|
2779
|
+
}
|
|
2780
|
+
function findDepthById(node, id, currentDepth = 1) {
|
|
2781
|
+
if (node.data.id === id) {
|
|
2782
|
+
return currentDepth;
|
|
2783
|
+
}
|
|
2784
|
+
// If the node has children, check each one
|
|
2785
|
+
for (const child of node.children) {
|
|
2786
|
+
const childDepth = findDepthById(child, id, currentDepth + 1);
|
|
2787
|
+
if (childDepth !== -1) {
|
|
2788
|
+
return childDepth; // Found the node in a child
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
return -1; // Node not found in this branch
|
|
2792
|
+
}
|
|
2793
|
+
const getChildFromTree = (parentId, index, node) => {
|
|
2794
|
+
// Check if the current node's id matches the search id
|
|
2795
|
+
if (node.data.id === parentId) {
|
|
2796
|
+
return node.children[index];
|
|
2797
|
+
}
|
|
2798
|
+
// Recursively search through each child
|
|
2799
|
+
for (const child of node.children) {
|
|
2800
|
+
const foundNode = getChildFromTree(parentId, index, child);
|
|
2801
|
+
if (foundNode) {
|
|
2802
|
+
// Node found in children
|
|
2803
|
+
return foundNode;
|
|
2804
|
+
}
|
|
2805
|
+
}
|
|
2806
|
+
// If the node is not found in this branch of the tree, return undefined
|
|
2807
|
+
return undefined;
|
|
2808
|
+
};
|
|
2809
|
+
const getItem = (selector, tree) => {
|
|
2810
|
+
return getItemFromTree(selector.id, {
|
|
2811
|
+
type: 'block',
|
|
2812
|
+
data: {
|
|
2813
|
+
id: ROOT_ID,
|
|
2814
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2815
|
+
},
|
|
2816
|
+
children: tree.root.children,
|
|
2817
|
+
});
|
|
2818
|
+
};
|
|
2819
|
+
const getItemDepthFromNode = (selector, node) => {
|
|
2820
|
+
return findDepthById(node, selector.id);
|
|
2821
|
+
};
|
|
2822
|
+
|
|
2823
|
+
function updateNode(nodeId, updatedNode, node) {
|
|
2824
|
+
if (node.data.id === nodeId) {
|
|
2825
|
+
node.data = updatedNode.data;
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
node.children.forEach((childNode) => updateNode(nodeId, updatedNode, childNode));
|
|
2829
|
+
}
|
|
2830
|
+
function replaceNode(indexToReplace, updatedNode, node) {
|
|
2831
|
+
if (node.data.id === updatedNode.parentId) {
|
|
2832
|
+
node.children = [
|
|
2833
|
+
...node.children.slice(0, indexToReplace),
|
|
2834
|
+
updatedNode,
|
|
2835
|
+
...node.children.slice(indexToReplace + 1),
|
|
2836
|
+
];
|
|
2837
|
+
return;
|
|
2838
|
+
}
|
|
2839
|
+
node.children.forEach((childNode) => replaceNode(indexToReplace, updatedNode, childNode));
|
|
2840
|
+
}
|
|
2841
|
+
function removeChildNode(indexToRemove, nodeId, parentNodeId, node) {
|
|
2842
|
+
if (node.data.id === parentNodeId) {
|
|
2843
|
+
const childIndex = node.children.findIndex((child) => child.data.id === nodeId);
|
|
2844
|
+
node.children.splice(childIndex === -1 ? indexToRemove : childIndex, 1);
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
node.children.forEach((childNode) => removeChildNode(indexToRemove, nodeId, parentNodeId, childNode));
|
|
2848
|
+
}
|
|
2849
|
+
function addChildNode(indexToAdd, parentNodeId, nodeToAdd, node) {
|
|
2850
|
+
if (node.data.id === parentNodeId) {
|
|
2851
|
+
node.children = [
|
|
2852
|
+
...node.children.slice(0, indexToAdd),
|
|
2853
|
+
nodeToAdd,
|
|
2854
|
+
...node.children.slice(indexToAdd),
|
|
2855
|
+
];
|
|
2856
|
+
return;
|
|
2619
2857
|
}
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
const
|
|
2626
|
-
|
|
2627
|
-
|
|
2858
|
+
node.children.forEach((childNode) => addChildNode(indexToAdd, parentNodeId, nodeToAdd, childNode));
|
|
2859
|
+
}
|
|
2860
|
+
function reorderChildNode(oldIndex, newIndex, parentNodeId, node) {
|
|
2861
|
+
if (node.data.id === parentNodeId) {
|
|
2862
|
+
// Remove the child from the old position
|
|
2863
|
+
const [childToMove] = node.children.splice(oldIndex, 1);
|
|
2864
|
+
// Insert the child at the new position
|
|
2865
|
+
node.children.splice(newIndex, 0, childToMove);
|
|
2866
|
+
return;
|
|
2628
2867
|
}
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
return transformAssetFileToUrl(fieldValue);
|
|
2868
|
+
node.children.forEach((childNode) => reorderChildNode(oldIndex, newIndex, parentNodeId, childNode));
|
|
2869
|
+
}
|
|
2870
|
+
function reparentChildNode(oldIndex, newIndex, sourceNodeId, destinationNodeId, node) {
|
|
2871
|
+
const nodeToMove = getChildFromTree(sourceNodeId, oldIndex, node);
|
|
2872
|
+
if (!nodeToMove) {
|
|
2873
|
+
return;
|
|
2636
2874
|
}
|
|
2875
|
+
removeChildNode(oldIndex, nodeToMove.data.id, sourceNodeId, node);
|
|
2876
|
+
addChildNode(newIndex, destinationNodeId, nodeToMove, node);
|
|
2637
2877
|
}
|
|
2638
2878
|
|
|
2639
|
-
|
|
2640
|
-
|
|
2641
|
-
|
|
2642
|
-
VisualEditorMode["InjectScript"] = "injectScript";
|
|
2643
|
-
})(VisualEditorMode$1 || (VisualEditorMode$1 = {}));
|
|
2644
|
-
|
|
2645
|
-
class DeepReference {
|
|
2646
|
-
constructor({ path, dataSource }) {
|
|
2647
|
-
const { key, field, referentField } = parseDataSourcePathWithL1DeepBindings(path);
|
|
2648
|
-
this.originalPath = path;
|
|
2649
|
-
this.entityId = dataSource[key].sys.id;
|
|
2650
|
-
this.entityLink = dataSource[key];
|
|
2651
|
-
this.field = field;
|
|
2652
|
-
this.referentField = referentField;
|
|
2653
|
-
}
|
|
2654
|
-
get headEntityId() {
|
|
2655
|
-
return this.entityId;
|
|
2879
|
+
function missingNodeAction({ index, nodeAdded, child, tree, parentNodeId, currentNode, }) {
|
|
2880
|
+
if (nodeAdded) {
|
|
2881
|
+
return { type: TreeAction.ADD_NODE, indexToAdd: index, nodeToAdd: child, parentNodeId };
|
|
2656
2882
|
}
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
const headEntity = entityStore.getEntityFromLink(this.entityLink);
|
|
2663
|
-
const maybeReferentLink = headEntity?.fields[this.field];
|
|
2664
|
-
if (undefined === maybeReferentLink) {
|
|
2665
|
-
// field references nothing (or even field doesn't exist)
|
|
2666
|
-
return undefined;
|
|
2667
|
-
}
|
|
2668
|
-
if (!isLink(maybeReferentLink)) {
|
|
2669
|
-
// Scenario of "impostor referent", where one of the deepPath's segments is not a Link but some other type
|
|
2670
|
-
// Under normal circumstance we expect field to be a Link, but it could be an "impostor"
|
|
2671
|
-
// eg. `Text` or `Number` or anything like that; could be due to CT changes or manual path creation via CMA
|
|
2672
|
-
return undefined;
|
|
2883
|
+
const item = getItem({ id: child.data.id }, tree);
|
|
2884
|
+
if (item) {
|
|
2885
|
+
const parentNode = getItem({ id: item.parentId }, tree);
|
|
2886
|
+
if (!parentNode) {
|
|
2887
|
+
return null;
|
|
2673
2888
|
}
|
|
2674
|
-
|
|
2889
|
+
const sourceIndex = parentNode.children.findIndex((c) => c.data.id === child.data.id);
|
|
2890
|
+
return { type: TreeAction.MOVE_NODE, sourceIndex, destinationIndex: index, parentNodeId };
|
|
2675
2891
|
}
|
|
2676
|
-
|
|
2677
|
-
|
|
2892
|
+
return {
|
|
2893
|
+
type: TreeAction.REPLACE_NODE,
|
|
2894
|
+
originalId: currentNode.children[index].data.id,
|
|
2895
|
+
indexToReplace: index,
|
|
2896
|
+
node: child,
|
|
2897
|
+
};
|
|
2898
|
+
}
|
|
2899
|
+
function matchingNodeAction({ index, originalIndex, nodeRemoved, nodeAdded, parentNodeId, }) {
|
|
2900
|
+
if (index !== originalIndex && !nodeRemoved && !nodeAdded) {
|
|
2901
|
+
return {
|
|
2902
|
+
type: TreeAction.REORDER_NODE,
|
|
2903
|
+
sourceIndex: originalIndex,
|
|
2904
|
+
destinationIndex: index,
|
|
2905
|
+
parentNodeId,
|
|
2906
|
+
};
|
|
2678
2907
|
}
|
|
2908
|
+
return null;
|
|
2679
2909
|
}
|
|
2680
|
-
function
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2910
|
+
function compareNodes({ currentNode, updatedNode, originalTree, differences = [], }) {
|
|
2911
|
+
// In the end, this map contains the list of nodes that are not present
|
|
2912
|
+
// in the updated tree and must be removed
|
|
2913
|
+
const map = new Map();
|
|
2914
|
+
if (!currentNode || !updatedNode) {
|
|
2915
|
+
return differences;
|
|
2916
|
+
}
|
|
2917
|
+
// On each tree level, consider only the children of the current node to differentiate between added, removed, or replaced case
|
|
2918
|
+
const currentNodeCount = currentNode.children.length;
|
|
2919
|
+
const updatedNodeCount = updatedNode.children.length;
|
|
2920
|
+
const nodeRemoved = currentNodeCount > updatedNodeCount;
|
|
2921
|
+
const nodeAdded = currentNodeCount < updatedNodeCount;
|
|
2922
|
+
const parentNodeId = updatedNode.data.id;
|
|
2923
|
+
/**
|
|
2924
|
+
* The data of the current node has changed, we need to update
|
|
2925
|
+
* this node to reflect the data change. (design, content, unbound values)
|
|
2926
|
+
*/
|
|
2927
|
+
if (!isEqual(currentNode.data, updatedNode.data)) {
|
|
2928
|
+
differences.push({
|
|
2929
|
+
type: TreeAction.UPDATE_NODE,
|
|
2930
|
+
nodeId: currentNode.data.id,
|
|
2931
|
+
node: updatedNode,
|
|
2932
|
+
});
|
|
2933
|
+
}
|
|
2934
|
+
// Map children of the first tree by their ID
|
|
2935
|
+
currentNode.children.forEach((child, index) => map.set(child.data.id, index));
|
|
2936
|
+
// Compare with the second tree
|
|
2937
|
+
updatedNode.children.forEach((child, index) => {
|
|
2938
|
+
const childId = child.data.id;
|
|
2939
|
+
// The original tree does not have this node in the updated tree.
|
|
2940
|
+
if (!map.has(childId)) {
|
|
2941
|
+
const diff = missingNodeAction({
|
|
2942
|
+
index,
|
|
2943
|
+
child,
|
|
2944
|
+
nodeAdded,
|
|
2945
|
+
parentNodeId,
|
|
2946
|
+
tree: originalTree,
|
|
2947
|
+
currentNode,
|
|
2948
|
+
});
|
|
2949
|
+
if (diff?.type === TreeAction.REPLACE_NODE) {
|
|
2950
|
+
// Remove it from the deletion map to avoid adding another REMOVE_NODE action
|
|
2951
|
+
map.delete(diff.originalId);
|
|
2952
|
+
}
|
|
2953
|
+
return differences.push(diff);
|
|
2954
|
+
}
|
|
2955
|
+
const originalIndex = map.get(childId);
|
|
2956
|
+
const diff = matchingNodeAction({
|
|
2957
|
+
index,
|
|
2958
|
+
originalIndex,
|
|
2959
|
+
nodeAdded,
|
|
2960
|
+
nodeRemoved,
|
|
2961
|
+
parentNodeId,
|
|
2962
|
+
});
|
|
2963
|
+
differences.push(diff);
|
|
2964
|
+
map.delete(childId);
|
|
2965
|
+
compareNodes({
|
|
2966
|
+
currentNode: currentNode.children[originalIndex],
|
|
2967
|
+
updatedNode: child,
|
|
2968
|
+
originalTree,
|
|
2969
|
+
differences,
|
|
2970
|
+
});
|
|
2971
|
+
});
|
|
2972
|
+
map.forEach((index, key) => {
|
|
2973
|
+
// If the node count of the entire tree doesn't signify
|
|
2974
|
+
// a node was removed, don't add that as a diff
|
|
2975
|
+
if (!nodeRemoved) {
|
|
2684
2976
|
return;
|
|
2685
|
-
for (const [, variableMapping] of Object.entries(node.data.props)) {
|
|
2686
|
-
if (variableMapping.type !== 'BoundValue')
|
|
2687
|
-
continue;
|
|
2688
|
-
if (!isDeepPath(variableMapping.path))
|
|
2689
|
-
continue;
|
|
2690
|
-
deepReferences.push(DeepReference.from({
|
|
2691
|
-
path: variableMapping.path,
|
|
2692
|
-
dataSource,
|
|
2693
|
-
}));
|
|
2694
2977
|
}
|
|
2978
|
+
// Remaining nodes in the map are removed in the second tree
|
|
2979
|
+
differences.push({
|
|
2980
|
+
type: TreeAction.REMOVE_NODE,
|
|
2981
|
+
indexToRemove: index,
|
|
2982
|
+
parentNodeId,
|
|
2983
|
+
idToRemove: key,
|
|
2984
|
+
});
|
|
2695
2985
|
});
|
|
2696
|
-
return
|
|
2986
|
+
return differences;
|
|
2987
|
+
}
|
|
2988
|
+
function getTreeDiffs(tree1, tree2, originalTree) {
|
|
2989
|
+
const differences = [];
|
|
2990
|
+
compareNodes({
|
|
2991
|
+
currentNode: tree1,
|
|
2992
|
+
updatedNode: tree2,
|
|
2993
|
+
originalTree,
|
|
2994
|
+
differences,
|
|
2995
|
+
});
|
|
2996
|
+
return differences.filter((diff) => diff);
|
|
2697
2997
|
}
|
|
2698
2998
|
|
|
2699
2999
|
const useTreeStore = create((set, get) => ({
|
|
@@ -2727,6 +3027,20 @@ const useTreeStore = create((set, get) => ({
|
|
|
2727
3027
|
});
|
|
2728
3028
|
}));
|
|
2729
3029
|
},
|
|
3030
|
+
/**
|
|
3031
|
+
* NOTE: this is for debugging purposes only as it causes ugly canvas flash.
|
|
3032
|
+
*
|
|
3033
|
+
* Force updates entire tree. Usually shouldn't be used as updateTree()
|
|
3034
|
+
* uses smart update algorithm based on diffs. But for troubleshooting
|
|
3035
|
+
* you may want to force update the tree so leaving this in.
|
|
3036
|
+
*/
|
|
3037
|
+
updateTreeForced: (tree) => {
|
|
3038
|
+
set({
|
|
3039
|
+
tree,
|
|
3040
|
+
// Breakpoints must be updated, as we receive completely new tree with possibly new breakpoints
|
|
3041
|
+
breakpoints: tree?.root?.data?.breakpoints || [],
|
|
3042
|
+
});
|
|
3043
|
+
},
|
|
2730
3044
|
updateTree: (tree) => {
|
|
2731
3045
|
const currentTree = get().tree;
|
|
2732
3046
|
/**
|
|
@@ -2772,6 +3086,21 @@ const useTreeStore = create((set, get) => ({
|
|
|
2772
3086
|
state.breakpoints = tree?.root?.data?.breakpoints || [];
|
|
2773
3087
|
}));
|
|
2774
3088
|
},
|
|
3089
|
+
addChild: (index, parentId, node) => {
|
|
3090
|
+
set(produce((state) => {
|
|
3091
|
+
addChildNode(index, parentId, node, state.tree.root);
|
|
3092
|
+
}));
|
|
3093
|
+
},
|
|
3094
|
+
reorderChildren: (destinationIndex, destinationParentId, sourceIndex) => {
|
|
3095
|
+
set(produce((state) => {
|
|
3096
|
+
reorderChildNode(sourceIndex, destinationIndex, destinationParentId, state.tree.root);
|
|
3097
|
+
}));
|
|
3098
|
+
},
|
|
3099
|
+
reparentChild: (destinationIndex, destinationParentId, sourceIndex, sourceParentId) => {
|
|
3100
|
+
set(produce((state) => {
|
|
3101
|
+
reparentChildNode(sourceIndex, destinationIndex, sourceParentId, destinationParentId, state.tree.root);
|
|
3102
|
+
}));
|
|
3103
|
+
},
|
|
2775
3104
|
}));
|
|
2776
3105
|
const hasBreakpointDiffs = (currentTree, newTree) => {
|
|
2777
3106
|
const currentBreakpoints = currentTree?.root?.data?.breakpoints ?? [];
|
|
@@ -2789,8 +3118,8 @@ const cloneDeepAsPOJO = (obj) => {
|
|
|
2789
3118
|
return JSON.parse(JSON.stringify(obj));
|
|
2790
3119
|
};
|
|
2791
3120
|
|
|
2792
|
-
var css_248z$a = ".
|
|
2793
|
-
var styles$
|
|
3121
|
+
var css_248z$a = ".render-module_hitbox__l4ysJ {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 10px;\n z-index: 1000000;\n}\n\n.render-module_hitboxLower__tgsA1 {\n position: absolute;\n bottom: -20px;\n left: 0;\n width: 100%;\n height: 20px;\n z-index: 1000000;\n}\n\n.render-module_canvasBottomSpacer__JuxVh {\n position: absolute;\n width: 100%;\n height: 50px;\n}\n\n.render-module_container__-C3d7 {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\nbody {\n margin: 0;\n}\n\nhtml {\n -ms-overflow-style: none; /* Internet Explorer 10+ */\n scrollbar-width: none; /* Firefox */\n}\n\nhtml::-webkit-scrollbar {\n display: none;\n}\n";
|
|
3122
|
+
var styles$3 = {"hitbox":"render-module_hitbox__l4ysJ","hitboxLower":"render-module_hitboxLower__tgsA1","canvasBottomSpacer":"render-module_canvasBottomSpacer__JuxVh","container":"render-module_container__-C3d7"};
|
|
2794
3123
|
styleInject(css_248z$a);
|
|
2795
3124
|
|
|
2796
3125
|
// TODO: In order to support integrations without React, we should extract this heavy logic into simple
|
|
@@ -2829,6 +3158,66 @@ const useBreakpoints = (breakpoints) => {
|
|
|
2829
3158
|
return { resolveDesignValue };
|
|
2830
3159
|
};
|
|
2831
3160
|
|
|
3161
|
+
/**
|
|
3162
|
+
* This function gets the element co-ordinates of a specified component in the DOM and its parent
|
|
3163
|
+
* and sends the DOM Rect to the client app
|
|
3164
|
+
*/
|
|
3165
|
+
const sendSelectedComponentCoordinates = (instanceId) => {
|
|
3166
|
+
const selection = getSelectionNodes(instanceId);
|
|
3167
|
+
if (selection?.target) {
|
|
3168
|
+
const sendUpdateSelectedComponentCoordinates = () => {
|
|
3169
|
+
sendMessage(OUTGOING_EVENTS.UpdateSelectedComponentCoordinates, {
|
|
3170
|
+
selectedNodeCoordinates: getElementCoordinates(selection.target),
|
|
3171
|
+
selectedAssemblyChildCoordinates: selection.patternChild
|
|
3172
|
+
? getElementCoordinates(selection.patternChild)
|
|
3173
|
+
: undefined,
|
|
3174
|
+
parentCoordinates: selection.parent ? getElementCoordinates(selection.parent) : undefined,
|
|
3175
|
+
});
|
|
3176
|
+
};
|
|
3177
|
+
// If the target contains an image, wait for this image to be loaded before sending the coordinates
|
|
3178
|
+
const childImage = selection.target.querySelector('img');
|
|
3179
|
+
if (childImage) {
|
|
3180
|
+
const handleImageLoad = () => {
|
|
3181
|
+
sendUpdateSelectedComponentCoordinates();
|
|
3182
|
+
childImage.removeEventListener('load', handleImageLoad);
|
|
3183
|
+
};
|
|
3184
|
+
childImage.addEventListener('load', handleImageLoad);
|
|
3185
|
+
}
|
|
3186
|
+
sendUpdateSelectedComponentCoordinates();
|
|
3187
|
+
}
|
|
3188
|
+
};
|
|
3189
|
+
const getSelectionNodes = (instanceId) => {
|
|
3190
|
+
if (!instanceId)
|
|
3191
|
+
return;
|
|
3192
|
+
let selectedNode = document.querySelector(`[data-cf-node-id="${instanceId}"]`);
|
|
3193
|
+
let selectedPatternChild = null;
|
|
3194
|
+
let selectedParent = null;
|
|
3195
|
+
// Use RegEx instead of split to match the last occurrence of '---' in the instanceId instead of the first one
|
|
3196
|
+
const idMatch = instanceId.match(/(.*)---(.*)/);
|
|
3197
|
+
const rootNodeId = idMatch?.[1] ?? instanceId;
|
|
3198
|
+
const nodeLocation = idMatch?.[2];
|
|
3199
|
+
const isNestedPattern = nodeLocation && selectedNode?.dataset?.cfNodeBlockType === ASSEMBLY_NODE_TYPE;
|
|
3200
|
+
const isPatternChild = !isNestedPattern && nodeLocation;
|
|
3201
|
+
if (isPatternChild) {
|
|
3202
|
+
// For pattern child nodes, render the pattern itself as selected component
|
|
3203
|
+
selectedPatternChild = selectedNode;
|
|
3204
|
+
selectedNode = document.querySelector(`[data-cf-node-id="${rootNodeId}"]`);
|
|
3205
|
+
}
|
|
3206
|
+
else if (isNestedPattern) {
|
|
3207
|
+
// For nested patterns, return the upper pattern as parent
|
|
3208
|
+
selectedParent = document.querySelector(`[data-cf-node-id="${rootNodeId}"]`);
|
|
3209
|
+
}
|
|
3210
|
+
else {
|
|
3211
|
+
// Find the next valid parent of the selected element
|
|
3212
|
+
selectedParent = selectedNode?.parentElement ?? null;
|
|
3213
|
+
// Ensure that the selection parent is a VisualEditorBlock
|
|
3214
|
+
while (selectedParent && !selectedParent.dataset?.cfNodeId) {
|
|
3215
|
+
selectedParent = selectedParent?.parentElement;
|
|
3216
|
+
}
|
|
3217
|
+
}
|
|
3218
|
+
return { target: selectedNode, patternChild: selectedPatternChild, parent: selectedParent };
|
|
3219
|
+
};
|
|
3220
|
+
|
|
2832
3221
|
// Note: During development, the hot reloading might empty this and it
|
|
2833
3222
|
// stays empty leading to not rendering assemblies. Ideally, this is
|
|
2834
3223
|
// integrated into the state machine to keep track of its state.
|
|
@@ -2862,10 +3251,14 @@ const useEditorStore = create((set, get) => ({
|
|
|
2862
3251
|
dataSource: {},
|
|
2863
3252
|
hyperLinkPattern: undefined,
|
|
2864
3253
|
unboundValues: {},
|
|
3254
|
+
selectedNodeId: null,
|
|
2865
3255
|
locale: null,
|
|
2866
3256
|
setHyperLinkPattern: (pattern) => {
|
|
2867
3257
|
set({ hyperLinkPattern: pattern });
|
|
2868
3258
|
},
|
|
3259
|
+
setSelectedNodeId: (id) => {
|
|
3260
|
+
set({ selectedNodeId: id });
|
|
3261
|
+
},
|
|
2869
3262
|
setDataSource(data) {
|
|
2870
3263
|
const dataSource = get().dataSource;
|
|
2871
3264
|
const newDataSource = { ...dataSource, ...data };
|
|
@@ -2897,7 +3290,6 @@ const useEditorStore = create((set, get) => ({
|
|
|
2897
3290
|
var css_248z$8 = "/* Initially added with PR #253 for each component, this is now a global setting\n * It is recommended on MDN to use this as a default for layouting.\n*/\n* {\n box-sizing: border-box;\n}\n";
|
|
2898
3291
|
styleInject(css_248z$8);
|
|
2899
3292
|
|
|
2900
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
2901
3293
|
/**
|
|
2902
3294
|
* These modes are ONLY intended to be internally used within the context of
|
|
2903
3295
|
* editing an experience inside of Contentful Studio. i.e. these modes
|
|
@@ -3026,25 +3418,79 @@ const PrimitiveValueSchema = z.union([
|
|
|
3026
3418
|
z.record(z.any(), z.any()),
|
|
3027
3419
|
z.undefined(),
|
|
3028
3420
|
]);
|
|
3421
|
+
const UsedComponentsSchema = z.array(z.object({
|
|
3422
|
+
sys: z.object({
|
|
3423
|
+
type: z.literal('Link'),
|
|
3424
|
+
id: z.string(),
|
|
3425
|
+
linkType: z.literal('Entry'),
|
|
3426
|
+
}),
|
|
3427
|
+
}));
|
|
3029
3428
|
const uuidKeySchema = z
|
|
3030
3429
|
.string()
|
|
3031
3430
|
.regex(/^[a-zA-Z0-9-_]{1,21}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,21}$/' });
|
|
3431
|
+
const DataSourceSchema = z.record(uuidKeySchema, z.object({
|
|
3432
|
+
sys: z.object({
|
|
3433
|
+
type: z.literal('Link'),
|
|
3434
|
+
id: z.string(),
|
|
3435
|
+
linkType: z.enum(['Entry', 'Asset']),
|
|
3436
|
+
}),
|
|
3437
|
+
}));
|
|
3438
|
+
const UnboundValuesSchema = z.record(uuidKeySchema, z.object({
|
|
3439
|
+
value: PrimitiveValueSchema,
|
|
3440
|
+
}));
|
|
3032
3441
|
/**
|
|
3033
3442
|
* Property keys for imported components have a limit of 32 characters (to be implemented) while
|
|
3034
|
-
* property keys for patterns have a limit of 54 characters (<32-char-
|
|
3443
|
+
* property keys for patterns have a limit of 54 characters (<32-char-variable-name>_<21-char-nanoid-id>).
|
|
3035
3444
|
* Because we cannot distinguish between the two in the componentTree, we will use the larger limit for both.
|
|
3036
3445
|
*/
|
|
3037
3446
|
const propertyKeySchema = z
|
|
3038
3447
|
.string()
|
|
3039
|
-
.regex(/^[a-zA-Z0-9-_]{1,54}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,54}$/' });
|
|
3040
|
-
const
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3448
|
+
.regex(/^[a-zA-Z0-9-_]{1,54}$/, { message: 'Does not match /^[a-zA-Z0-9-_]{1,54}$/' });
|
|
3449
|
+
const ComponentTreeNodeIdSchema = z
|
|
3450
|
+
.string()
|
|
3451
|
+
.regex(/^[a-zA-Z0-9]{1,8}$/, { message: 'Does not match /^[a-zA-Z0-9]{1,8}$/' });
|
|
3452
|
+
const breakpointsRefinement = (value, ctx) => {
|
|
3453
|
+
if (!value.length || value[0].query !== '*') {
|
|
3454
|
+
ctx.addIssue({
|
|
3455
|
+
code: z.ZodIssueCode.custom,
|
|
3456
|
+
message: `The first breakpoint should include the following attributes: { "query": "*" }`,
|
|
3457
|
+
});
|
|
3458
|
+
}
|
|
3459
|
+
const hasDuplicateIds = value.some((currentBreakpoint, currentBreakpointIndex) => {
|
|
3460
|
+
// check if the current breakpoint id is found in the rest of the array
|
|
3461
|
+
const breakpointIndex = value.findIndex((breakpoint) => breakpoint.id === currentBreakpoint.id);
|
|
3462
|
+
return breakpointIndex !== currentBreakpointIndex;
|
|
3463
|
+
});
|
|
3464
|
+
if (hasDuplicateIds) {
|
|
3465
|
+
ctx.addIssue({
|
|
3466
|
+
code: z.ZodIssueCode.custom,
|
|
3467
|
+
message: `Breakpoint IDs must be unique`,
|
|
3468
|
+
});
|
|
3469
|
+
}
|
|
3470
|
+
// Extract the queries boundary by removing the special characters around it
|
|
3471
|
+
const queries = value.map((bp) => bp.query === '*' ? bp.query : parseInt(bp.query.replace(/px|<|>/, '')));
|
|
3472
|
+
// sort updates queries array in place so we need to create a copy
|
|
3473
|
+
const originalQueries = [...queries];
|
|
3474
|
+
queries.sort((q1, q2) => {
|
|
3475
|
+
if (q1 === '*') {
|
|
3476
|
+
return -1;
|
|
3477
|
+
}
|
|
3478
|
+
if (q2 === '*') {
|
|
3479
|
+
return 1;
|
|
3480
|
+
}
|
|
3481
|
+
return q1 > q2 ? -1 : 1;
|
|
3482
|
+
});
|
|
3483
|
+
if (originalQueries.join('') !== queries.join('')) {
|
|
3484
|
+
ctx.addIssue({
|
|
3485
|
+
code: z.ZodIssueCode.custom,
|
|
3486
|
+
message: `Breakpoints should be ordered from largest to smallest pixel value`,
|
|
3487
|
+
});
|
|
3488
|
+
}
|
|
3489
|
+
};
|
|
3047
3490
|
const ValuesByBreakpointSchema = z.record(z.lazy(() => PrimitiveValueSchema));
|
|
3491
|
+
const BindingSourceTypeEnumSchema = z
|
|
3492
|
+
.array(z.enum(['entry', 'asset', 'manual', 'experience']))
|
|
3493
|
+
.nonempty();
|
|
3048
3494
|
const DesignValueSchema = z
|
|
3049
3495
|
.object({
|
|
3050
3496
|
type: z.literal('DesignValue'),
|
|
@@ -3077,8 +3523,6 @@ const ComponentValueSchema = z
|
|
|
3077
3523
|
key: z.string(),
|
|
3078
3524
|
})
|
|
3079
3525
|
.strict();
|
|
3080
|
-
// TODO: finalize schema structure before release
|
|
3081
|
-
// https://contentful.atlassian.net/browse/LUMOS-523
|
|
3082
3526
|
const NoValueSchema = z.object({ type: z.literal('NoValue') }).strict();
|
|
3083
3527
|
const ComponentPropertyValueSchema = z.discriminatedUnion('type', [
|
|
3084
3528
|
DesignValueSchema,
|
|
@@ -3090,41 +3534,12 @@ const ComponentPropertyValueSchema = z.discriminatedUnion('type', [
|
|
|
3090
3534
|
]);
|
|
3091
3535
|
// TODO: finalize schema structure before release
|
|
3092
3536
|
// https://contentful.atlassian.net/browse/LUMOS-523
|
|
3093
|
-
const VariableMappingSchema = z.object({
|
|
3094
|
-
patternPropertyDefinitionId: propertyKeySchema,
|
|
3095
|
-
type: z.literal('ContentTypeMapping'),
|
|
3096
|
-
pathsByContentType: z.record(z.string(), z.object({ path: z.string() })),
|
|
3097
|
-
});
|
|
3098
|
-
const VariableMappingsSchema = z.record(propertyKeySchema, VariableMappingSchema);
|
|
3099
|
-
// TODO: finalize schema structure before release
|
|
3100
|
-
// https://contentful.atlassian.net/browse/LUMOS-523
|
|
3101
|
-
const PatternPropertyDefinitionSchema = z.object({
|
|
3102
|
-
defaultValue: z
|
|
3103
|
-
.record(z.string(), z.object({
|
|
3104
|
-
sys: z.object({
|
|
3105
|
-
type: z.literal('Link'),
|
|
3106
|
-
id: z.string(),
|
|
3107
|
-
linkType: z.enum(['Entry']),
|
|
3108
|
-
}),
|
|
3109
|
-
}))
|
|
3110
|
-
.optional(),
|
|
3111
|
-
contentTypes: z.record(z.string(), z.object({
|
|
3112
|
-
sys: z.object({
|
|
3113
|
-
type: z.literal('Link'),
|
|
3114
|
-
id: z.string(),
|
|
3115
|
-
linkType: z.enum(['ContentType']),
|
|
3116
|
-
}),
|
|
3117
|
-
})),
|
|
3118
|
-
});
|
|
3119
|
-
const PatternPropertyDefinitionsSchema = z.record(propertyKeySchema, PatternPropertyDefinitionSchema);
|
|
3120
|
-
// TODO: finalize schema structure before release
|
|
3121
|
-
// https://contentful.atlassian.net/browse/LUMOS-523
|
|
3122
3537
|
const PatternPropertySchema = z.object({
|
|
3123
3538
|
type: z.literal('BoundValue'),
|
|
3124
3539
|
path: z.string(),
|
|
3125
3540
|
contentType: z.string(),
|
|
3126
3541
|
});
|
|
3127
|
-
const
|
|
3542
|
+
const PatternPropertiesSchema = z.record(propertyKeySchema, PatternPropertySchema);
|
|
3128
3543
|
const BreakpointSchema = z
|
|
3129
3544
|
.object({
|
|
3130
3545
|
id: propertyKeySchema,
|
|
@@ -3134,12 +3549,6 @@ const BreakpointSchema = z
|
|
|
3134
3549
|
displayIcon: z.enum(['desktop', 'tablet', 'mobile']).optional(),
|
|
3135
3550
|
})
|
|
3136
3551
|
.strict();
|
|
3137
|
-
const UnboundValuesSchema = z.record(uuidKeySchema, z.object({
|
|
3138
|
-
value: PrimitiveValueSchema,
|
|
3139
|
-
}));
|
|
3140
|
-
const ComponentTreeNodeIdSchema = z
|
|
3141
|
-
.string()
|
|
3142
|
-
.regex(/^[a-zA-Z0-9]{1,8}$/, { message: 'Does not match /^[a-zA-Z0-9]{1,8}$/' });
|
|
3143
3552
|
// Use helper schema to define a recursive schema with its type correctly below
|
|
3144
3553
|
const BaseComponentTreeNodeSchema = z.object({
|
|
3145
3554
|
id: ComponentTreeNodeIdSchema.optional(),
|
|
@@ -3147,14 +3556,8 @@ const BaseComponentTreeNodeSchema = z.object({
|
|
|
3147
3556
|
displayName: z.string().optional(),
|
|
3148
3557
|
slotId: z.string().optional(),
|
|
3149
3558
|
variables: z.record(propertyKeySchema, ComponentPropertyValueSchema),
|
|
3150
|
-
patternProperties:
|
|
3151
|
-
});
|
|
3152
|
-
const ComponentTreeNodeSchema = BaseComponentTreeNodeSchema.extend({
|
|
3153
|
-
children: z.lazy(() => ComponentTreeNodeSchema.array()),
|
|
3559
|
+
patternProperties: PatternPropertiesSchema.optional(),
|
|
3154
3560
|
});
|
|
3155
|
-
const BindingSourceTypeEnumSchema = z
|
|
3156
|
-
.array(z.enum(['entry', 'asset', 'manual', 'experience']))
|
|
3157
|
-
.nonempty();
|
|
3158
3561
|
const ComponentVariableSchema = z.object({
|
|
3159
3562
|
displayName: z.string().optional(),
|
|
3160
3563
|
type: DefinitionPropertyTypeSchema,
|
|
@@ -3175,8 +3578,25 @@ const ComponentVariableSchema = z.object({
|
|
|
3175
3578
|
})
|
|
3176
3579
|
.optional(),
|
|
3177
3580
|
});
|
|
3178
|
-
const
|
|
3179
|
-
|
|
3581
|
+
const ComponentTreeNodeSchema = BaseComponentTreeNodeSchema.extend({
|
|
3582
|
+
children: z.lazy(() => ComponentTreeNodeSchema.array()),
|
|
3583
|
+
});
|
|
3584
|
+
const ComponentTreeSchema = z
|
|
3585
|
+
.object({
|
|
3586
|
+
breakpoints: z.array(BreakpointSchema).superRefine(breakpointsRefinement),
|
|
3587
|
+
children: z.array(ComponentTreeNodeSchema),
|
|
3588
|
+
schemaVersion: SchemaVersions,
|
|
3589
|
+
})
|
|
3590
|
+
.strict();
|
|
3591
|
+
const localeWrapper = (fieldSchema) => z.record(z.string(), fieldSchema);
|
|
3592
|
+
|
|
3593
|
+
z.object({
|
|
3594
|
+
componentTree: localeWrapper(ComponentTreeSchema),
|
|
3595
|
+
dataSource: localeWrapper(DataSourceSchema),
|
|
3596
|
+
unboundValues: localeWrapper(UnboundValuesSchema),
|
|
3597
|
+
usedComponents: localeWrapper(UsedComponentsSchema).optional(),
|
|
3598
|
+
});
|
|
3599
|
+
|
|
3180
3600
|
const THUMBNAIL_IDS = [
|
|
3181
3601
|
'columns',
|
|
3182
3602
|
'columnsPlusRight',
|
|
@@ -3202,6 +3622,37 @@ const THUMBNAIL_IDS = [
|
|
|
3202
3622
|
'textColumns',
|
|
3203
3623
|
'duplex',
|
|
3204
3624
|
];
|
|
3625
|
+
// TODO: finalize schema structure before release
|
|
3626
|
+
// https://contentful.atlassian.net/browse/LUMOS-523
|
|
3627
|
+
const VariableMappingSchema = z.object({
|
|
3628
|
+
patternPropertyDefinitionId: propertyKeySchema,
|
|
3629
|
+
type: z.literal('ContentTypeMapping'),
|
|
3630
|
+
pathsByContentType: z.record(z.string(), z.object({ path: z.string() })),
|
|
3631
|
+
});
|
|
3632
|
+
// TODO: finalize schema structure before release
|
|
3633
|
+
// https://contentful.atlassian.net/browse/LUMOS-523
|
|
3634
|
+
const PatternPropertyDefinitionSchema = z.object({
|
|
3635
|
+
defaultValue: z
|
|
3636
|
+
.record(z.string(), z.object({
|
|
3637
|
+
sys: z.object({
|
|
3638
|
+
type: z.literal('Link'),
|
|
3639
|
+
id: z.string(),
|
|
3640
|
+
linkType: z.enum(['Entry']),
|
|
3641
|
+
}),
|
|
3642
|
+
}))
|
|
3643
|
+
.optional(),
|
|
3644
|
+
contentTypes: z.record(z.string(), z.object({
|
|
3645
|
+
sys: z.object({
|
|
3646
|
+
type: z.literal('Link'),
|
|
3647
|
+
id: z.string(),
|
|
3648
|
+
linkType: z.enum(['ContentType']),
|
|
3649
|
+
}),
|
|
3650
|
+
})),
|
|
3651
|
+
});
|
|
3652
|
+
const PatternPropertyDefinitionsSchema = z.record(propertyKeySchema, PatternPropertyDefinitionSchema);
|
|
3653
|
+
const VariableMappingsSchema = z.record(propertyKeySchema, VariableMappingSchema);
|
|
3654
|
+
const ComponentVariablesSchema = z.record(z.string().regex(/^[a-zA-Z0-9-_]{1,54}$/), // Here the key is <variableName>_<nanoidId> so we need to allow for a longer length
|
|
3655
|
+
ComponentVariableSchema);
|
|
3205
3656
|
const ComponentSettingsSchema = z.object({
|
|
3206
3657
|
variableDefinitions: ComponentVariablesSchema,
|
|
3207
3658
|
thumbnailId: z.enum(THUMBNAIL_IDS).optional(),
|
|
@@ -3209,65 +3660,12 @@ const ComponentSettingsSchema = z.object({
|
|
|
3209
3660
|
variableMappings: VariableMappingsSchema.optional(),
|
|
3210
3661
|
patternPropertyDefinitions: PatternPropertyDefinitionsSchema.optional(),
|
|
3211
3662
|
});
|
|
3212
|
-
const UsedComponentsSchema = z.array(z.object({
|
|
3213
|
-
sys: z.object({
|
|
3214
|
-
type: z.literal('Link'),
|
|
3215
|
-
id: z.string(),
|
|
3216
|
-
linkType: z.literal('Entry'),
|
|
3217
|
-
}),
|
|
3218
|
-
}));
|
|
3219
|
-
const breakpointsRefinement = (value, ctx) => {
|
|
3220
|
-
if (!value.length || value[0].query !== '*') {
|
|
3221
|
-
ctx.addIssue({
|
|
3222
|
-
code: z.ZodIssueCode.custom,
|
|
3223
|
-
message: `The first breakpoint should include the following attributes: { "query": "*" }`,
|
|
3224
|
-
});
|
|
3225
|
-
}
|
|
3226
|
-
const hasDuplicateIds = value.some((currentBreakpoint, currentBreakpointIndex) => {
|
|
3227
|
-
// check if the current breakpoint id is found in the rest of the array
|
|
3228
|
-
const breakpointIndex = value.findIndex((breakpoint) => breakpoint.id === currentBreakpoint.id);
|
|
3229
|
-
return breakpointIndex !== currentBreakpointIndex;
|
|
3230
|
-
});
|
|
3231
|
-
if (hasDuplicateIds) {
|
|
3232
|
-
ctx.addIssue({
|
|
3233
|
-
code: z.ZodIssueCode.custom,
|
|
3234
|
-
message: `Breakpoint IDs must be unique`,
|
|
3235
|
-
});
|
|
3236
|
-
}
|
|
3237
|
-
// Extract the queries boundary by removing the special characters around it
|
|
3238
|
-
const queries = value.map((bp) => bp.query === '*' ? bp.query : parseInt(bp.query.replace(/px|<|>/, '')));
|
|
3239
|
-
// sort updates queries array in place so we need to create a copy
|
|
3240
|
-
const originalQueries = [...queries];
|
|
3241
|
-
queries.sort((q1, q2) => {
|
|
3242
|
-
if (q1 === '*') {
|
|
3243
|
-
return -1;
|
|
3244
|
-
}
|
|
3245
|
-
if (q2 === '*') {
|
|
3246
|
-
return 1;
|
|
3247
|
-
}
|
|
3248
|
-
return q1 > q2 ? -1 : 1;
|
|
3249
|
-
});
|
|
3250
|
-
if (originalQueries.join('') !== queries.join('')) {
|
|
3251
|
-
ctx.addIssue({
|
|
3252
|
-
code: z.ZodIssueCode.custom,
|
|
3253
|
-
message: `Breakpoints should be ordered from largest to smallest pixel value`,
|
|
3254
|
-
});
|
|
3255
|
-
}
|
|
3256
|
-
};
|
|
3257
|
-
const ComponentTreeSchema = z
|
|
3258
|
-
.object({
|
|
3259
|
-
breakpoints: z.array(BreakpointSchema).superRefine(breakpointsRefinement),
|
|
3260
|
-
children: z.array(ComponentTreeNodeSchema),
|
|
3261
|
-
schemaVersion: SchemaVersions,
|
|
3262
|
-
})
|
|
3263
|
-
.strict();
|
|
3264
|
-
const localeWrapper = (fieldSchema) => z.record(z.string(), fieldSchema);
|
|
3265
3663
|
z.object({
|
|
3266
3664
|
componentTree: localeWrapper(ComponentTreeSchema),
|
|
3267
3665
|
dataSource: localeWrapper(DataSourceSchema),
|
|
3268
3666
|
unboundValues: localeWrapper(UnboundValuesSchema),
|
|
3269
3667
|
usedComponents: localeWrapper(UsedComponentsSchema).optional(),
|
|
3270
|
-
componentSettings: localeWrapper(ComponentSettingsSchema)
|
|
3668
|
+
componentSettings: localeWrapper(ComponentSettingsSchema),
|
|
3271
3669
|
});
|
|
3272
3670
|
|
|
3273
3671
|
z.object({
|
|
@@ -3391,6 +3789,32 @@ var CodeNames;
|
|
|
3391
3789
|
})(CodeNames || (CodeNames = {}));
|
|
3392
3790
|
|
|
3393
3791
|
const CF_DEBUG_KEY = 'cf_debug';
|
|
3792
|
+
/**
|
|
3793
|
+
* To ensure that the localStorage API can be used safely, we check
|
|
3794
|
+
* for availability (e.g. undefined in Node.js). Additionally, we
|
|
3795
|
+
* check if the localStorage can be used as some browsers throw a
|
|
3796
|
+
* SecurityError (e.g. Brave or Chromium with specific settings).
|
|
3797
|
+
*/
|
|
3798
|
+
const checkLocalStorageAvailability = () => {
|
|
3799
|
+
if (typeof localStorage === 'undefined' || localStorage === null) {
|
|
3800
|
+
return false;
|
|
3801
|
+
}
|
|
3802
|
+
try {
|
|
3803
|
+
// Attempt to set and remove an item to check if localStorage is enabled
|
|
3804
|
+
const TEST_KEY = 'cf_test_local_storage';
|
|
3805
|
+
localStorage.setItem(TEST_KEY, 'yes');
|
|
3806
|
+
if (localStorage.getItem(TEST_KEY) === 'yes') {
|
|
3807
|
+
localStorage.removeItem(TEST_KEY);
|
|
3808
|
+
return true;
|
|
3809
|
+
}
|
|
3810
|
+
else {
|
|
3811
|
+
return false;
|
|
3812
|
+
}
|
|
3813
|
+
}
|
|
3814
|
+
catch (_error) {
|
|
3815
|
+
return false;
|
|
3816
|
+
}
|
|
3817
|
+
};
|
|
3394
3818
|
class DebugLogger {
|
|
3395
3819
|
constructor() {
|
|
3396
3820
|
// Public methods for logging
|
|
@@ -3398,7 +3822,7 @@ class DebugLogger {
|
|
|
3398
3822
|
this.warn = this.logger('warn');
|
|
3399
3823
|
this.log = this.logger('log');
|
|
3400
3824
|
this.debug = this.logger('debug');
|
|
3401
|
-
if (
|
|
3825
|
+
if (!checkLocalStorageAvailability()) {
|
|
3402
3826
|
this.enabled = false;
|
|
3403
3827
|
return;
|
|
3404
3828
|
}
|
|
@@ -3444,8 +3868,8 @@ var VisualEditorMode;
|
|
|
3444
3868
|
VisualEditorMode["InjectScript"] = "injectScript";
|
|
3445
3869
|
})(VisualEditorMode || (VisualEditorMode = {}));
|
|
3446
3870
|
|
|
3447
|
-
var css_248z$2 = ".contentful-container {\n position: relative;\n display: flex;\n box-sizing: border-box;\n pointer-events: all;\n}\n\n.contentful-container::-webkit-scrollbar {\n display: none; /* Safari and Chrome */\n}\n\n.cf-container-wrapper {\n position: relative;\n width: 100%;\n}\n\n.contentful-container:after {\n content: '';\n display: block;\n position: absolute;\n pointer-events: none;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n overflow-x: clip;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 12px;\n color: var(--exp-builder-gray400);\n z-index: 1;\n}\n\n.contentful-section-label:after {\n content: 'Section';\n}\n\n.contentful-container-label:after {\n content: 'Container';\n}\n\n/* used by ContentfulSectionAsHyperlink.tsx */\n\n.contentful-container-link,\n.contentful-container-link:active,\n.contentful-container-link:visited,\n.contentful-container-link:hover,\n.contentful-container-link:read-write,\n.contentful-container-link:focus-visible {\n color: inherit;\n text-decoration: unset;\n outline: unset;\n}\n";
|
|
3448
|
-
styleInject(css_248z$2);
|
|
3871
|
+
var css_248z$2$1 = ".contentful-container {\n position: relative;\n display: flex;\n box-sizing: border-box;\n pointer-events: all;\n}\n\n.contentful-container::-webkit-scrollbar {\n display: none; /* Safari and Chrome */\n}\n\n.cf-single-column-wrapper {\n position: relative;\n}\n\n.cf-container-wrapper {\n position: relative;\n width: 100%;\n}\n\n.contentful-container:after {\n content: '';\n display: block;\n position: absolute;\n pointer-events: none;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n overflow-x: clip;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 12px;\n color: var(--exp-builder-gray400);\n z-index: 1;\n}\n\n.contentful-section-label:after {\n content: 'Section';\n}\n\n.contentful-container-label:after {\n content: 'Container';\n}\n\n/* used by ContentfulSectionAsHyperlink.tsx */\n\n.contentful-container-link,\n.contentful-container-link:active,\n.contentful-container-link:visited,\n.contentful-container-link:hover,\n.contentful-container-link:read-write,\n.contentful-container-link:focus-visible {\n color: inherit;\n text-decoration: unset;\n outline: unset;\n}\n";
|
|
3872
|
+
styleInject(css_248z$2$1);
|
|
3449
3873
|
|
|
3450
3874
|
const Flex = forwardRef(({ id, children, onMouseEnter, onMouseUp, onMouseLeave, onMouseDown, onClick, flex, flexBasis, flexShrink, flexDirection, gap, justifyContent, justifyItems, justifySelf, alignItems, alignSelf, alignContent, order, flexWrap, flexGrow, className, cssStyles, ...props }, ref) => {
|
|
3451
3875
|
return (React.createElement("div", { id: id, ref: ref, style: {
|
|
@@ -3469,14 +3893,34 @@ const Flex = forwardRef(({ id, children, onMouseEnter, onMouseUp, onMouseLeave,
|
|
|
3469
3893
|
});
|
|
3470
3894
|
Flex.displayName = 'Flex';
|
|
3471
3895
|
|
|
3472
|
-
var css_248z$1$1 = ".cf-divider {\n display: contents;\n position: relative;\n width: 100%;\n height: 100%;\n}\n\n.cf-divider hr {\n border: none;\n}\n";
|
|
3896
|
+
var css_248z$1$1 = ".cf-divider {\n display: contents;\n position: relative;\n width: 100%;\n height: 100%;\n}\n\n.cf-divider hr {\n border: none;\n}\n\n/* For the editor mode add this 10px tolerance to make it easier picking up the divider component.\n * Using the DND zone as precondition makes sure that we don't render this pseudo element in delivery. */\n\n[data-ctfl-zone-id='root'] .cf-divider::before {\n content: \"\";\n position: absolute;\n top: -5px;\n left: -5px;\n bottom: -5px;\n right: -5px;\n pointer-events: all;\n}\n";
|
|
3473
3897
|
styleInject(css_248z$1$1);
|
|
3474
3898
|
|
|
3475
|
-
var css_248z$9 = ".cf-columns {\n display: flex;\n gap: 24px;\n grid-template-columns: repeat(12, 1fr);\n flex-direction: column;\n min-height: 0; /* NEW */\n min-width: 0; /* NEW; needed for Firefox */\n}\n\n@media (min-width: 768px) {\n .cf-columns {\n display: grid;\n }\n}\n\n.cf-single-column-wrapper {\n position: relative;\n}\n\n.cf-single-column-wrapper:after {\n content: '';\n display: block;\n position: absolute;\n pointer-events: none;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n overflow-x: clip;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 12px;\n color: var(--exp-builder-gray400);\n z-index: 1;\n}\n\n.cf-single-column-label:after {\n content: 'Column';\n}\n";
|
|
3899
|
+
var css_248z$9 = ".cf-columns {\n display: flex;\n gap: 24px;\n grid-template-columns: repeat(12, 1fr);\n flex-direction: column;\n min-height: 0; /* NEW */\n min-width: 0; /* NEW; needed for Firefox */\n}\n\n@media (min-width: 768px) {\n .cf-columns {\n display: grid;\n }\n}\n\n.cf-single-column-wrapper {\n position: relative;\n display: flex;\n}\n\n.cf-single-column {\n pointer-events: all;\n}\n\n.cf-single-column-wrapper:after {\n content: '';\n display: block;\n position: absolute;\n pointer-events: none;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n overflow-x: clip;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 12px;\n color: var(--exp-builder-gray400);\n z-index: 1;\n}\n\n.cf-single-column-label:after {\n content: 'Column';\n}\n";
|
|
3476
3900
|
styleInject(css_248z$9);
|
|
3477
3901
|
|
|
3902
|
+
const ColumnWrapper = forwardRef((props, ref) => {
|
|
3903
|
+
return (React.createElement("div", { ref: ref, ...props, style: {
|
|
3904
|
+
...(props.style || {}),
|
|
3905
|
+
display: 'grid',
|
|
3906
|
+
gridTemplateColumns: 'repeat(12, [col-start] 1fr)',
|
|
3907
|
+
} }, props.children));
|
|
3908
|
+
});
|
|
3909
|
+
ColumnWrapper.displayName = 'ColumnWrapper';
|
|
3910
|
+
|
|
3478
3911
|
const assemblyStyle = { display: 'contents' };
|
|
3912
|
+
// Feel free to do any magic as regards variable definitions for assemblies
|
|
3913
|
+
// Or if this isn't necessary by the time we figure that part out, we can bid this part farewell
|
|
3479
3914
|
const Assembly = (props) => {
|
|
3915
|
+
if (props.editorMode) {
|
|
3916
|
+
const { node, dragProps, ...editorModeProps } = props;
|
|
3917
|
+
return props.renderDropzone(node, {
|
|
3918
|
+
...editorModeProps,
|
|
3919
|
+
['data-test-id']: 'contentful-assembly',
|
|
3920
|
+
className: props.className,
|
|
3921
|
+
dragProps,
|
|
3922
|
+
});
|
|
3923
|
+
}
|
|
3480
3924
|
// Using a display contents so assembly content/children
|
|
3481
3925
|
// can appear as if they are direct children of the div wrapper's parent
|
|
3482
3926
|
return React.createElement("div", { "data-test-id": "assembly", ...props, style: assemblyStyle });
|
|
@@ -3499,6 +3943,75 @@ const useEntityStore = create((set) => ({
|
|
|
3499
3943
|
},
|
|
3500
3944
|
}));
|
|
3501
3945
|
|
|
3946
|
+
class DragState {
|
|
3947
|
+
constructor() {
|
|
3948
|
+
this.isDragStartedOnParent = false;
|
|
3949
|
+
this.isDraggingItem = false;
|
|
3950
|
+
}
|
|
3951
|
+
get isDragging() {
|
|
3952
|
+
return this.isDraggingItem;
|
|
3953
|
+
}
|
|
3954
|
+
get isDraggingOnParent() {
|
|
3955
|
+
return this.isDragStartedOnParent;
|
|
3956
|
+
}
|
|
3957
|
+
updateIsDragging(isDraggingItem) {
|
|
3958
|
+
this.isDraggingItem = isDraggingItem;
|
|
3959
|
+
}
|
|
3960
|
+
updateIsDragStartedOnParent(isDragStartedOnParent) {
|
|
3961
|
+
this.isDragStartedOnParent = isDragStartedOnParent;
|
|
3962
|
+
}
|
|
3963
|
+
resetState() {
|
|
3964
|
+
this.isDraggingItem = false;
|
|
3965
|
+
this.isDragStartedOnParent = false;
|
|
3966
|
+
}
|
|
3967
|
+
}
|
|
3968
|
+
|
|
3969
|
+
class SimulateDnD extends DragState {
|
|
3970
|
+
constructor() {
|
|
3971
|
+
super();
|
|
3972
|
+
this.draggingElement = null;
|
|
3973
|
+
}
|
|
3974
|
+
setupDrag() {
|
|
3975
|
+
this.updateIsDragStartedOnParent(true);
|
|
3976
|
+
}
|
|
3977
|
+
startDrag(coordX, coordY) {
|
|
3978
|
+
this.draggingElement = document.getElementById(NEW_COMPONENT_ID);
|
|
3979
|
+
this.updateIsDragging(true);
|
|
3980
|
+
this.simulateMouseEvent(coordX, coordY, 'mousedown');
|
|
3981
|
+
}
|
|
3982
|
+
updateDrag(coordX, coordY) {
|
|
3983
|
+
if (!this.draggingElement) {
|
|
3984
|
+
this.draggingElement = document.querySelector(`[${CTFL_DRAGGING_ELEMENT}]`);
|
|
3985
|
+
}
|
|
3986
|
+
this.simulateMouseEvent(coordX, coordY);
|
|
3987
|
+
}
|
|
3988
|
+
endDrag(coordX, coordY) {
|
|
3989
|
+
this.simulateMouseEvent(coordX, coordY, 'mouseup');
|
|
3990
|
+
this.reset();
|
|
3991
|
+
}
|
|
3992
|
+
reset() {
|
|
3993
|
+
this.draggingElement = null;
|
|
3994
|
+
this.resetState();
|
|
3995
|
+
}
|
|
3996
|
+
simulateMouseEvent(coordX, coordY, eventName = 'mousemove') {
|
|
3997
|
+
if (!this.draggingElement) {
|
|
3998
|
+
return;
|
|
3999
|
+
}
|
|
4000
|
+
const options = {
|
|
4001
|
+
bubbles: true,
|
|
4002
|
+
cancelable: true,
|
|
4003
|
+
view: window,
|
|
4004
|
+
pageX: 0,
|
|
4005
|
+
pageY: 0,
|
|
4006
|
+
clientX: coordX,
|
|
4007
|
+
clientY: coordY,
|
|
4008
|
+
};
|
|
4009
|
+
const event = new MouseEvent(eventName, options);
|
|
4010
|
+
this.draggingElement.dispatchEvent(event);
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
var SimulateDnD$1 = new SimulateDnD();
|
|
4014
|
+
|
|
3502
4015
|
function useEditorSubscriber() {
|
|
3503
4016
|
const entityStore = useEntityStore((state) => state.entityStore);
|
|
3504
4017
|
const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
|
|
@@ -3512,7 +4025,14 @@ function useEditorSubscriber() {
|
|
|
3512
4025
|
const setLocale = useEditorStore((state) => state.setLocale);
|
|
3513
4026
|
const setUnboundValues = useEditorStore((state) => state.setUnboundValues);
|
|
3514
4027
|
const setDataSource = useEditorStore((state) => state.setDataSource);
|
|
4028
|
+
const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
|
|
4029
|
+
const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
|
|
3515
4030
|
const resetEntityStore = useEntityStore((state) => state.resetEntityStore);
|
|
4031
|
+
const setComponentId = useDraggedItemStore((state) => state.setComponentId);
|
|
4032
|
+
const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
|
|
4033
|
+
const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
|
|
4034
|
+
const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
|
|
4035
|
+
const setScrollY = useDraggedItemStore((state) => state.setScrollY);
|
|
3516
4036
|
const reloadApp = () => {
|
|
3517
4037
|
sendMessage(OUTGOING_EVENTS.CanvasReload, undefined);
|
|
3518
4038
|
// Wait a moment to ensure that the message was sent
|
|
@@ -3613,12 +4133,12 @@ function useEditorSubscriber() {
|
|
|
3613
4133
|
}
|
|
3614
4134
|
const eventData = tryParseMessage(event);
|
|
3615
4135
|
console.debug(`[experiences-sdk-react::onMessage] Received message [${eventData.eventType}]`, eventData);
|
|
3616
|
-
if (eventData.eventType === PostMessageMethods$
|
|
4136
|
+
if (eventData.eventType === PostMessageMethods$2.REQUESTED_ENTITIES) {
|
|
3617
4137
|
// Expected message: This message is handled in the EntityStore to store fetched entities
|
|
3618
4138
|
return;
|
|
3619
4139
|
}
|
|
3620
4140
|
switch (eventData.eventType) {
|
|
3621
|
-
case INCOMING_EVENTS
|
|
4141
|
+
case INCOMING_EVENTS.ExperienceUpdated: {
|
|
3622
4142
|
const { tree, locale, changedNode, changedValueType, assemblies } = eventData.payload;
|
|
3623
4143
|
// Make sure to first store the assemblies before setting the tree and thus triggering a rerender
|
|
3624
4144
|
if (assemblies) {
|
|
@@ -3662,7 +4182,7 @@ function useEditorSubscriber() {
|
|
|
3662
4182
|
updateTree(tree);
|
|
3663
4183
|
break;
|
|
3664
4184
|
}
|
|
3665
|
-
case INCOMING_EVENTS
|
|
4185
|
+
case INCOMING_EVENTS.AssembliesRegistered: {
|
|
3666
4186
|
const { assemblies } = eventData.payload;
|
|
3667
4187
|
assemblies.forEach((definition) => {
|
|
3668
4188
|
addComponentRegistration({
|
|
@@ -3672,7 +4192,7 @@ function useEditorSubscriber() {
|
|
|
3672
4192
|
});
|
|
3673
4193
|
break;
|
|
3674
4194
|
}
|
|
3675
|
-
case INCOMING_EVENTS
|
|
4195
|
+
case INCOMING_EVENTS.AssembliesAdded: {
|
|
3676
4196
|
const { assembly, assemblyDefinition, } = eventData.payload;
|
|
3677
4197
|
entityStore.updateEntity(assembly);
|
|
3678
4198
|
// Using a Map here to avoid setting state and rerending all existing assemblies when a new assembly is added
|
|
@@ -3689,7 +4209,28 @@ function useEditorSubscriber() {
|
|
|
3689
4209
|
}
|
|
3690
4210
|
break;
|
|
3691
4211
|
}
|
|
3692
|
-
case INCOMING_EVENTS
|
|
4212
|
+
case INCOMING_EVENTS.CanvasResized: {
|
|
4213
|
+
const { selectedNodeId } = eventData.payload;
|
|
4214
|
+
if (selectedNodeId) {
|
|
4215
|
+
sendSelectedComponentCoordinates(selectedNodeId);
|
|
4216
|
+
}
|
|
4217
|
+
break;
|
|
4218
|
+
}
|
|
4219
|
+
case INCOMING_EVENTS.HoverComponent: {
|
|
4220
|
+
const { hoveredNodeId } = eventData.payload;
|
|
4221
|
+
setHoveredComponentId(hoveredNodeId);
|
|
4222
|
+
break;
|
|
4223
|
+
}
|
|
4224
|
+
case INCOMING_EVENTS.ComponentDraggingChanged: {
|
|
4225
|
+
const { isDragging } = eventData.payload;
|
|
4226
|
+
if (!isDragging) {
|
|
4227
|
+
setComponentId('');
|
|
4228
|
+
setDraggingOnCanvas(false);
|
|
4229
|
+
SimulateDnD$1.reset();
|
|
4230
|
+
}
|
|
4231
|
+
break;
|
|
4232
|
+
}
|
|
4233
|
+
case INCOMING_EVENTS.UpdatedEntity: {
|
|
3693
4234
|
const { entity: updatedEntity, shouldRerender } = eventData.payload;
|
|
3694
4235
|
if (updatedEntity) {
|
|
3695
4236
|
const storedEntity = entityStore.entities.find((entity) => entity.sys.id === updatedEntity.sys.id);
|
|
@@ -3702,7 +4243,52 @@ function useEditorSubscriber() {
|
|
|
3702
4243
|
}
|
|
3703
4244
|
break;
|
|
3704
4245
|
}
|
|
3705
|
-
case INCOMING_EVENTS
|
|
4246
|
+
case INCOMING_EVENTS.RequestEditorMode: {
|
|
4247
|
+
break;
|
|
4248
|
+
}
|
|
4249
|
+
case INCOMING_EVENTS.ComponentDragCanceled: {
|
|
4250
|
+
if (SimulateDnD$1.isDragging) {
|
|
4251
|
+
//simulate a mouseup event to cancel the drag
|
|
4252
|
+
SimulateDnD$1.endDrag(0, 0);
|
|
4253
|
+
}
|
|
4254
|
+
break;
|
|
4255
|
+
}
|
|
4256
|
+
case INCOMING_EVENTS.ComponentDragStarted: {
|
|
4257
|
+
const { id, isAssembly } = eventData.payload;
|
|
4258
|
+
SimulateDnD$1.setupDrag();
|
|
4259
|
+
setComponentId(`${id}:${isAssembly}` || '');
|
|
4260
|
+
setDraggingOnCanvas(true);
|
|
4261
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
4262
|
+
nodeId: '',
|
|
4263
|
+
});
|
|
4264
|
+
break;
|
|
4265
|
+
}
|
|
4266
|
+
case INCOMING_EVENTS.ComponentDragEnded: {
|
|
4267
|
+
SimulateDnD$1.reset();
|
|
4268
|
+
setComponentId('');
|
|
4269
|
+
setDraggingOnCanvas(false);
|
|
4270
|
+
break;
|
|
4271
|
+
}
|
|
4272
|
+
case INCOMING_EVENTS.SelectComponent: {
|
|
4273
|
+
const { selectedNodeId: nodeId } = eventData.payload;
|
|
4274
|
+
setSelectedNodeId(nodeId);
|
|
4275
|
+
sendSelectedComponentCoordinates(nodeId);
|
|
4276
|
+
break;
|
|
4277
|
+
}
|
|
4278
|
+
case INCOMING_EVENTS.MouseMove: {
|
|
4279
|
+
const { mouseX, mouseY } = eventData.payload;
|
|
4280
|
+
setMousePosition(mouseX, mouseY);
|
|
4281
|
+
if (SimulateDnD$1.isDraggingOnParent && !SimulateDnD$1.isDragging) {
|
|
4282
|
+
SimulateDnD$1.startDrag(mouseX, mouseY);
|
|
4283
|
+
}
|
|
4284
|
+
else {
|
|
4285
|
+
SimulateDnD$1.updateDrag(mouseX, mouseY);
|
|
4286
|
+
}
|
|
4287
|
+
break;
|
|
4288
|
+
}
|
|
4289
|
+
case INCOMING_EVENTS.ComponentMoveEnded: {
|
|
4290
|
+
const { mouseX, mouseY } = eventData.payload;
|
|
4291
|
+
SimulateDnD$1.endDrag(mouseX, mouseY);
|
|
3706
4292
|
break;
|
|
3707
4293
|
}
|
|
3708
4294
|
default:
|
|
@@ -3715,8 +4301,11 @@ function useEditorSubscriber() {
|
|
|
3715
4301
|
};
|
|
3716
4302
|
}, [
|
|
3717
4303
|
entityStore,
|
|
4304
|
+
setComponentId,
|
|
4305
|
+
setDraggingOnCanvas,
|
|
3718
4306
|
setDataSource,
|
|
3719
4307
|
setLocale,
|
|
4308
|
+
setSelectedNodeId,
|
|
3720
4309
|
dataSource,
|
|
3721
4310
|
areEntitiesFetched,
|
|
3722
4311
|
fetchMissingEntities,
|
|
@@ -3724,92 +4313,328 @@ function useEditorSubscriber() {
|
|
|
3724
4313
|
unboundValues,
|
|
3725
4314
|
updateTree,
|
|
3726
4315
|
updateNodesByUpdatedEntity,
|
|
4316
|
+
setMousePosition,
|
|
3727
4317
|
resetEntityStore,
|
|
4318
|
+
setHoveredComponentId,
|
|
3728
4319
|
]);
|
|
4320
|
+
/*
|
|
4321
|
+
* Handles on scroll business
|
|
4322
|
+
*/
|
|
4323
|
+
useEffect(() => {
|
|
4324
|
+
let timeoutId = 0;
|
|
4325
|
+
let isScrolling = false;
|
|
4326
|
+
const onScroll = () => {
|
|
4327
|
+
setScrollY(window.scrollY);
|
|
4328
|
+
if (isScrolling === false) {
|
|
4329
|
+
sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.Start);
|
|
4330
|
+
}
|
|
4331
|
+
sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.IsScrolling);
|
|
4332
|
+
isScrolling = true;
|
|
4333
|
+
clearTimeout(timeoutId);
|
|
4334
|
+
timeoutId = window.setTimeout(() => {
|
|
4335
|
+
if (isScrolling === false) {
|
|
4336
|
+
return;
|
|
4337
|
+
}
|
|
4338
|
+
isScrolling = false;
|
|
4339
|
+
sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.End);
|
|
4340
|
+
/**
|
|
4341
|
+
* On scroll end, send new co-ordinates of selected node
|
|
4342
|
+
*/
|
|
4343
|
+
if (selectedNodeId) {
|
|
4344
|
+
sendSelectedComponentCoordinates(selectedNodeId);
|
|
4345
|
+
}
|
|
4346
|
+
}, 150);
|
|
4347
|
+
};
|
|
4348
|
+
window.addEventListener('scroll', onScroll, { capture: true, passive: true });
|
|
4349
|
+
return () => {
|
|
4350
|
+
window.removeEventListener('scroll', onScroll, { capture: true });
|
|
4351
|
+
clearTimeout(timeoutId);
|
|
4352
|
+
};
|
|
4353
|
+
}, [selectedNodeId, setScrollY]);
|
|
3729
4354
|
}
|
|
3730
4355
|
|
|
3731
|
-
const
|
|
3732
|
-
|
|
3733
|
-
return (React.createElement("div", { "data-cf-node-id": node.data.id, "data-cf-node-block-id": node.data.blockId, "data-cf-node-block-type": node.type, "data-cf-node-error": "circular-pattern-dependency", style: {
|
|
3734
|
-
border: '1px solid red',
|
|
3735
|
-
background: 'rgba(255, 0, 0, 0.1)',
|
|
3736
|
-
padding: '1rem 1rem 0 1rem',
|
|
3737
|
-
width: '100%',
|
|
3738
|
-
height: '100%',
|
|
3739
|
-
} },
|
|
3740
|
-
"Circular usage of patterns detected:",
|
|
3741
|
-
React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
|
|
3742
|
-
const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
|
|
3743
|
-
const entry = entityStore.getEntityFromLink(entryLink);
|
|
3744
|
-
const entryTitle = entry?.fields?.title;
|
|
3745
|
-
const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
|
|
3746
|
-
return React.createElement("li", { key: patternId }, text);
|
|
3747
|
-
}))));
|
|
4356
|
+
const onComponentMoved = (options) => {
|
|
4357
|
+
sendMessage(OUTGOING_EVENTS.ComponentMoved, options);
|
|
3748
4358
|
};
|
|
3749
4359
|
|
|
3750
|
-
|
|
3751
|
-
constructor(message) {
|
|
3752
|
-
super(message);
|
|
3753
|
-
this.name = 'ImportedComponentError';
|
|
3754
|
-
}
|
|
3755
|
-
}
|
|
3756
|
-
class ExperienceSDKError extends Error {
|
|
3757
|
-
constructor(message) {
|
|
3758
|
-
super(message);
|
|
3759
|
-
this.name = 'ExperienceSDKError';
|
|
3760
|
-
}
|
|
3761
|
-
}
|
|
3762
|
-
class ImportedComponentErrorBoundary extends React.Component {
|
|
3763
|
-
componentDidCatch(error, _errorInfo) {
|
|
3764
|
-
if (error.name === 'ImportedComponentError' || error.name === 'ExperienceSDKError') {
|
|
3765
|
-
// This error was already handled by a nested error boundary and should be passed upwards
|
|
3766
|
-
// We have to do this as we wrap every component on every layer with this error boundary and
|
|
3767
|
-
// thus an error deep in the tree bubbles through many layers of error boundaries.
|
|
3768
|
-
throw error;
|
|
3769
|
-
}
|
|
3770
|
-
// Differentiate between custom and SDK-provided components for error tracking
|
|
3771
|
-
const ErrorClass = isContentfulComponent(this.props.componentId)
|
|
3772
|
-
? ExperienceSDKError
|
|
3773
|
-
: ImportedComponentError;
|
|
3774
|
-
const err = new ErrorClass(error.message);
|
|
3775
|
-
err.stack = error.stack;
|
|
3776
|
-
throw err;
|
|
3777
|
-
}
|
|
3778
|
-
render() {
|
|
3779
|
-
return this.props.children;
|
|
3780
|
-
}
|
|
3781
|
-
}
|
|
4360
|
+
const generateId = (type) => `${type}-${v4()}`;
|
|
3782
4361
|
|
|
3783
|
-
const
|
|
3784
|
-
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
3791
|
-
|
|
4362
|
+
const createTreeNode = ({ blockId, parentId, slotId }) => {
|
|
4363
|
+
const node = {
|
|
4364
|
+
type: 'block',
|
|
4365
|
+
data: {
|
|
4366
|
+
id: generateId(blockId),
|
|
4367
|
+
blockId,
|
|
4368
|
+
slotId,
|
|
4369
|
+
props: {},
|
|
4370
|
+
dataSource: {},
|
|
4371
|
+
breakpoints: [],
|
|
4372
|
+
unboundValues: {},
|
|
4373
|
+
},
|
|
4374
|
+
parentId,
|
|
4375
|
+
children: [],
|
|
4376
|
+
};
|
|
4377
|
+
return node;
|
|
3792
4378
|
};
|
|
3793
4379
|
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
4380
|
+
const onComponentDropped = ({ node, index, parentBlockId, parentType, parentId, }) => {
|
|
4381
|
+
sendMessage(OUTGOING_EVENTS.ComponentDropped, {
|
|
4382
|
+
node,
|
|
4383
|
+
index: index ?? node.children.length,
|
|
4384
|
+
parentNode: {
|
|
4385
|
+
type: parentType,
|
|
4386
|
+
data: {
|
|
4387
|
+
blockId: parentBlockId,
|
|
4388
|
+
id: parentId,
|
|
4389
|
+
},
|
|
4390
|
+
},
|
|
4391
|
+
});
|
|
4392
|
+
};
|
|
3797
4393
|
|
|
3798
|
-
const
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
4394
|
+
const onDrop = ({ destinationIndex, componentType, destinationZoneId, data, slotId, }) => {
|
|
4395
|
+
const parentId = destinationZoneId;
|
|
4396
|
+
const parentNode = getItem({ id: parentId }, data);
|
|
4397
|
+
const parentIsRoot = parentId === ROOT_ID;
|
|
4398
|
+
const emptyComponentData = {
|
|
4399
|
+
type: 'block',
|
|
4400
|
+
parentId,
|
|
4401
|
+
children: [],
|
|
4402
|
+
data: {
|
|
4403
|
+
blockId: componentType,
|
|
4404
|
+
id: generateId(componentType),
|
|
4405
|
+
slotId,
|
|
4406
|
+
breakpoints: [],
|
|
4407
|
+
dataSource: {},
|
|
4408
|
+
props: {},
|
|
4409
|
+
unboundValues: {},
|
|
4410
|
+
},
|
|
4411
|
+
};
|
|
4412
|
+
onComponentDropped({
|
|
4413
|
+
node: emptyComponentData,
|
|
4414
|
+
index: destinationIndex,
|
|
4415
|
+
parentType: parentIsRoot ? 'root' : parentNode?.type,
|
|
4416
|
+
parentBlockId: parentNode?.data.blockId,
|
|
4417
|
+
parentId: parentIsRoot ? 'root' : parentId,
|
|
4418
|
+
});
|
|
4419
|
+
};
|
|
4420
|
+
|
|
4421
|
+
/**
|
|
4422
|
+
* Parses a droppable zone ID into a node ID and slot ID.
|
|
4423
|
+
*
|
|
4424
|
+
* The slot ID is optional and only present if the component implements multiple drop zones.
|
|
4425
|
+
*
|
|
4426
|
+
* @param zoneId - Expected formats are `nodeId` or `nodeId|slotId`.
|
|
4427
|
+
*/
|
|
4428
|
+
const parseZoneId = (zoneId) => {
|
|
4429
|
+
const [nodeId, slotId] = zoneId.includes('|') ? zoneId.split('|') : [zoneId, undefined];
|
|
4430
|
+
return { nodeId, slotId };
|
|
4431
|
+
};
|
|
4432
|
+
|
|
4433
|
+
function useCanvasInteractions() {
|
|
4434
|
+
const tree = useTreeStore((state) => state.tree);
|
|
4435
|
+
const reorderChildren = useTreeStore((state) => state.reorderChildren);
|
|
4436
|
+
const reparentChild = useTreeStore((state) => state.reparentChild);
|
|
4437
|
+
const addChild = useTreeStore((state) => state.addChild);
|
|
4438
|
+
const onAddComponent = (droppedItem) => {
|
|
4439
|
+
const { destination, draggableId } = droppedItem;
|
|
4440
|
+
if (!destination) {
|
|
4441
|
+
return;
|
|
4442
|
+
}
|
|
4443
|
+
/**
|
|
4444
|
+
* We only have the draggableId as information about the new component being dropped.
|
|
4445
|
+
* So we need to split it to get the blockId and the isAssembly flag.
|
|
4446
|
+
*/
|
|
4447
|
+
const [blockId, isAssembly] = draggableId.split(':');
|
|
4448
|
+
const { nodeId: parentId, slotId } = parseZoneId(destination.droppableId);
|
|
4449
|
+
const droppingOnRoot = parentId === ROOT_ID;
|
|
4450
|
+
const isValidRootComponent = blockId === CONTENTFUL_COMPONENTS$1.container.id;
|
|
4451
|
+
let node = createTreeNode({ blockId, parentId, slotId });
|
|
4452
|
+
if (droppingOnRoot && !isValidRootComponent) {
|
|
4453
|
+
const wrappingContainer = createTreeNode({
|
|
4454
|
+
blockId: CONTENTFUL_COMPONENTS$1.container.id,
|
|
4455
|
+
parentId,
|
|
3805
4456
|
});
|
|
4457
|
+
const childNode = createTreeNode({
|
|
4458
|
+
blockId,
|
|
4459
|
+
parentId: wrappingContainer.data.id,
|
|
4460
|
+
});
|
|
4461
|
+
node = wrappingContainer;
|
|
4462
|
+
node.children = [childNode];
|
|
3806
4463
|
}
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
4464
|
+
/**
|
|
4465
|
+
* isAssembly comes from a string ID so we need to check if it's 'true' or 'false'
|
|
4466
|
+
* in string format.
|
|
4467
|
+
*/
|
|
4468
|
+
if (isAssembly === 'false') {
|
|
4469
|
+
addChild(destination.index, parentId, node);
|
|
3810
4470
|
}
|
|
3811
|
-
|
|
3812
|
-
|
|
4471
|
+
onDrop({
|
|
4472
|
+
data: tree,
|
|
4473
|
+
componentType: blockId,
|
|
4474
|
+
destinationIndex: destination.index,
|
|
4475
|
+
destinationZoneId: parentId,
|
|
4476
|
+
slotId,
|
|
4477
|
+
});
|
|
4478
|
+
};
|
|
4479
|
+
const onMoveComponent = (droppedItem) => {
|
|
4480
|
+
const { destination, source, draggableId } = droppedItem;
|
|
4481
|
+
if (!destination || !source) {
|
|
4482
|
+
return;
|
|
4483
|
+
}
|
|
4484
|
+
if (destination.droppableId === source.droppableId) {
|
|
4485
|
+
reorderChildren(destination.index, destination.droppableId, source.index);
|
|
4486
|
+
}
|
|
4487
|
+
if (destination.droppableId !== source.droppableId) {
|
|
4488
|
+
reparentChild(destination.index, destination.droppableId, source.index, source.droppableId);
|
|
4489
|
+
}
|
|
4490
|
+
onComponentMoved({
|
|
4491
|
+
nodeId: draggableId,
|
|
4492
|
+
destinationIndex: destination.index,
|
|
4493
|
+
destinationParentId: destination.droppableId,
|
|
4494
|
+
sourceIndex: source.index,
|
|
4495
|
+
sourceParentId: source.droppableId,
|
|
4496
|
+
});
|
|
4497
|
+
};
|
|
4498
|
+
return { onAddComponent, onMoveComponent };
|
|
4499
|
+
}
|
|
4500
|
+
|
|
4501
|
+
const TestDNDContainer = ({ onDragEnd, onBeforeDragStart, onDragStart, onDragUpdate, children, }) => {
|
|
4502
|
+
const handleDragStart = (event) => {
|
|
4503
|
+
const draggedItem = event.nativeEvent;
|
|
4504
|
+
const start = {
|
|
4505
|
+
mode: draggedItem.mode,
|
|
4506
|
+
draggableId: draggedItem.draggableId,
|
|
4507
|
+
type: draggedItem.type,
|
|
4508
|
+
source: draggedItem.source,
|
|
4509
|
+
};
|
|
4510
|
+
onBeforeDragStart(start);
|
|
4511
|
+
onDragStart(start, {});
|
|
4512
|
+
};
|
|
4513
|
+
const handleDrag = (event) => {
|
|
4514
|
+
const draggedItem = event.nativeEvent;
|
|
4515
|
+
const update = {
|
|
4516
|
+
mode: draggedItem.mode,
|
|
4517
|
+
draggableId: draggedItem.draggableId,
|
|
4518
|
+
type: draggedItem.type,
|
|
4519
|
+
source: draggedItem.source,
|
|
4520
|
+
destination: draggedItem.destination,
|
|
4521
|
+
combine: draggedItem.combine,
|
|
4522
|
+
};
|
|
4523
|
+
onDragUpdate(update, {});
|
|
4524
|
+
};
|
|
4525
|
+
const handleDragEnd = (event) => {
|
|
4526
|
+
const draggedItem = event.nativeEvent;
|
|
4527
|
+
const result = {
|
|
4528
|
+
mode: draggedItem.mode,
|
|
4529
|
+
draggableId: draggedItem.draggableId,
|
|
4530
|
+
type: draggedItem.type,
|
|
4531
|
+
source: draggedItem.source,
|
|
4532
|
+
destination: draggedItem.destination,
|
|
4533
|
+
combine: draggedItem.combine,
|
|
4534
|
+
reason: draggedItem.reason,
|
|
4535
|
+
};
|
|
4536
|
+
onDragEnd(result, {});
|
|
4537
|
+
};
|
|
4538
|
+
return (React.createElement("div", { "data-test-id": "dnd-context-substitute", onDragStart: handleDragStart, onDrag: handleDrag, onDragEnd: handleDragEnd }, children));
|
|
4539
|
+
};
|
|
4540
|
+
|
|
4541
|
+
const DNDProvider = ({ children }) => {
|
|
4542
|
+
const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
|
|
4543
|
+
const draggedItem = useDraggedItemStore((state) => state.draggedItem);
|
|
4544
|
+
const setOnBeforeCaptureId = useDraggedItemStore((state) => state.setOnBeforeCaptureId);
|
|
4545
|
+
const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
|
|
4546
|
+
const updateItem = useDraggedItemStore((state) => state.updateItem);
|
|
4547
|
+
const { onAddComponent, onMoveComponent } = useCanvasInteractions();
|
|
4548
|
+
const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
|
|
4549
|
+
const prevSelectedNodeId = useRef(null);
|
|
4550
|
+
const isTestRun = typeof window !== 'undefined' && Object.prototype.hasOwnProperty.call(window, 'Cypress');
|
|
4551
|
+
const beforeDragStart = ({ source }) => {
|
|
4552
|
+
prevSelectedNodeId.current = selectedNodeId;
|
|
4553
|
+
// Unselect the current node when dragging and remove the outline
|
|
4554
|
+
setSelectedNodeId('');
|
|
4555
|
+
// Set dragging state here to make sure that DnD capture phase has completed
|
|
4556
|
+
setDraggingOnCanvas(true);
|
|
4557
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
4558
|
+
nodeId: '',
|
|
4559
|
+
});
|
|
4560
|
+
if (source.droppableId !== COMPONENT_LIST_ID) {
|
|
4561
|
+
sendMessage(OUTGOING_EVENTS.ComponentMoveStarted, undefined);
|
|
4562
|
+
}
|
|
4563
|
+
};
|
|
4564
|
+
const beforeCapture = ({ draggableId }) => {
|
|
4565
|
+
setOnBeforeCaptureId(draggableId);
|
|
4566
|
+
};
|
|
4567
|
+
const dragStart = (start) => {
|
|
4568
|
+
updateItem(start);
|
|
4569
|
+
};
|
|
4570
|
+
const dragUpdate = (update) => {
|
|
4571
|
+
updateItem(update);
|
|
4572
|
+
};
|
|
4573
|
+
const dragEnd = (dropResult) => {
|
|
4574
|
+
setDraggingOnCanvas(false);
|
|
4575
|
+
setOnBeforeCaptureId('');
|
|
4576
|
+
updateItem();
|
|
4577
|
+
SimulateDnD$1.reset();
|
|
4578
|
+
// If the component is being dropped onto itself, do nothing
|
|
4579
|
+
// This can happen from an apparent race condition where the hovering zone gets set
|
|
4580
|
+
// to the component after its dropped.
|
|
4581
|
+
if (dropResult.destination?.droppableId === dropResult.draggableId) {
|
|
4582
|
+
return;
|
|
4583
|
+
}
|
|
4584
|
+
if (!dropResult.destination) {
|
|
4585
|
+
if (!draggedItem?.destination) {
|
|
4586
|
+
// User cancel drag
|
|
4587
|
+
sendMessage(OUTGOING_EVENTS.ComponentDragCanceled, undefined);
|
|
4588
|
+
//select the previously selected node if drag was canceled
|
|
4589
|
+
if (prevSelectedNodeId.current) {
|
|
4590
|
+
setSelectedNodeId(prevSelectedNodeId.current);
|
|
4591
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
4592
|
+
nodeId: prevSelectedNodeId.current,
|
|
4593
|
+
});
|
|
4594
|
+
prevSelectedNodeId.current = null;
|
|
4595
|
+
}
|
|
4596
|
+
return;
|
|
4597
|
+
}
|
|
4598
|
+
// Use the destination from the draggedItem (when clicking the canvas)
|
|
4599
|
+
dropResult.destination = draggedItem.destination;
|
|
4600
|
+
}
|
|
4601
|
+
// New component added to canvas
|
|
4602
|
+
if (dropResult.source.droppableId.startsWith('component-list')) {
|
|
4603
|
+
onAddComponent(dropResult);
|
|
4604
|
+
}
|
|
4605
|
+
else {
|
|
4606
|
+
onMoveComponent(dropResult);
|
|
4607
|
+
}
|
|
4608
|
+
// If a node was previously selected prior to dragging, re-select it
|
|
4609
|
+
setSelectedNodeId(dropResult.draggableId);
|
|
4610
|
+
sendMessage(OUTGOING_EVENTS.ComponentMoveEnded, undefined);
|
|
4611
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
4612
|
+
nodeId: dropResult.draggableId,
|
|
4613
|
+
});
|
|
4614
|
+
};
|
|
4615
|
+
return (React.createElement(DragDropContext, { onBeforeCapture: beforeCapture, onDragUpdate: dragUpdate, onBeforeDragStart: beforeDragStart, onDragStart: dragStart, onDragEnd: dragEnd }, isTestRun ? (React.createElement(TestDNDContainer, { onDragEnd: dragEnd, onBeforeDragStart: beforeDragStart, onDragStart: dragStart, onDragUpdate: dragUpdate }, children)) : (children)));
|
|
4616
|
+
};
|
|
4617
|
+
|
|
4618
|
+
/**
|
|
4619
|
+
* This hook gets the element co-ordinates of a specified element in the DOM
|
|
4620
|
+
* and sends the DOM Rect to the client app
|
|
4621
|
+
*/
|
|
4622
|
+
const useSelectedInstanceCoordinates = ({ node }) => {
|
|
4623
|
+
const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
|
|
4624
|
+
useEffect(() => {
|
|
4625
|
+
if (selectedNodeId !== node.data.id) {
|
|
4626
|
+
return;
|
|
4627
|
+
}
|
|
4628
|
+
// Allows the drop animation to finish before
|
|
4629
|
+
// calculating the components coordinates
|
|
4630
|
+
setTimeout(() => {
|
|
4631
|
+
sendSelectedComponentCoordinates(node.data.id);
|
|
4632
|
+
}, 10);
|
|
4633
|
+
}, [node, selectedNodeId]);
|
|
4634
|
+
const selectedElement = node.data.id
|
|
4635
|
+
? document.querySelector(`[data-cf-node-id="${selectedNodeId}"]`)
|
|
4636
|
+
: undefined;
|
|
4637
|
+
return selectedElement ? getElementCoordinates(selectedElement) : null;
|
|
3813
4638
|
};
|
|
3814
4639
|
|
|
3815
4640
|
/**
|
|
@@ -3857,13 +4682,15 @@ const getUnboundValues = ({ key, fallback, unboundValues, }) => {
|
|
|
3857
4682
|
return get$1(unboundValues, lodashPath, fallback);
|
|
3858
4683
|
};
|
|
3859
4684
|
|
|
3860
|
-
const useComponentProps = ({ node, resolveDesignValue, definition, options, }) => {
|
|
4685
|
+
const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, renderDropzone, definition, options, userIsDragging, requiresDragWrapper, }) => {
|
|
3861
4686
|
const unboundValues = useEditorStore((state) => state.unboundValues);
|
|
3862
4687
|
const hyperlinkPattern = useEditorStore((state) => state.hyperLinkPattern);
|
|
3863
4688
|
const locale = useEditorStore((state) => state.locale);
|
|
3864
4689
|
const dataSource = useEditorStore((state) => state.dataSource);
|
|
3865
4690
|
const entityStore = useEntityStore((state) => state.entityStore);
|
|
3866
|
-
const
|
|
4691
|
+
const draggingId = useDraggedItemStore((state) => state.onBeforeCaptureId);
|
|
4692
|
+
const nodeRect = useDraggedItemStore((state) => state.domRect);
|
|
4693
|
+
const isEmptyZone = !node.children.length;
|
|
3867
4694
|
const props = useMemo(() => {
|
|
3868
4695
|
const propsBase = {
|
|
3869
4696
|
cfSsrClassName: node.data.props.cfSsrClassName
|
|
@@ -3953,126 +4780,1133 @@ const useComponentProps = ({ node, resolveDesignValue, definition, options, }) =
|
|
|
3953
4780
|
return { ...acc };
|
|
3954
4781
|
}
|
|
3955
4782
|
}, {});
|
|
4783
|
+
const slotProps = {};
|
|
4784
|
+
if (definition.slots) {
|
|
4785
|
+
for (const slotId in definition.slots) {
|
|
4786
|
+
slotProps[slotId] = renderDropzone(node, {
|
|
4787
|
+
zoneId: [node.data.id, slotId].join('|'),
|
|
4788
|
+
});
|
|
4789
|
+
}
|
|
4790
|
+
}
|
|
3956
4791
|
return {
|
|
3957
4792
|
...propsBase,
|
|
3958
4793
|
...extractedProps,
|
|
4794
|
+
...slotProps,
|
|
3959
4795
|
};
|
|
3960
4796
|
}, [
|
|
3961
4797
|
hyperlinkPattern,
|
|
3962
4798
|
node,
|
|
3963
|
-
locale,
|
|
3964
|
-
definition,
|
|
3965
|
-
resolveDesignValue,
|
|
3966
|
-
dataSource,
|
|
4799
|
+
locale,
|
|
4800
|
+
definition,
|
|
4801
|
+
resolveDesignValue,
|
|
4802
|
+
dataSource,
|
|
4803
|
+
areEntitiesFetched,
|
|
4804
|
+
unboundValues,
|
|
4805
|
+
entityStore,
|
|
4806
|
+
renderDropzone,
|
|
4807
|
+
]);
|
|
4808
|
+
const cfStyles = useMemo(() => buildCfStyles(props), [props]);
|
|
4809
|
+
const cfVisibility = props['cfVisibility'];
|
|
4810
|
+
const isAssemblyBlock = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
|
|
4811
|
+
const isSingleColumn = node?.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id;
|
|
4812
|
+
const isStructureComponent = isContentfulStructureComponent(node?.data.blockId);
|
|
4813
|
+
const isPatternNode = node.type === ASSEMBLY_NODE_TYPE;
|
|
4814
|
+
const { overrideStyles, wrapperStyles } = useMemo(() => {
|
|
4815
|
+
// Move size styles to the wrapping div and override the component styles
|
|
4816
|
+
const overrideStyles = {};
|
|
4817
|
+
const wrapperStyles = { width: options?.wrapContainerWidth };
|
|
4818
|
+
if (requiresDragWrapper) {
|
|
4819
|
+
// when element is marked by user as not-visible, on that element the node `display: none !important`
|
|
4820
|
+
// will be set and it will disappear. However, when such a node has a wrapper div, the wrapper
|
|
4821
|
+
// should not have any css properties (at least not ones which force size), as such div should
|
|
4822
|
+
// simply be a zero height wrapper around element with `display: none !important`.
|
|
4823
|
+
// Hence we guard all wrapperStyles with `cfVisibility` check.
|
|
4824
|
+
if (cfVisibility && cfStyles.width)
|
|
4825
|
+
wrapperStyles.width = cfStyles.width;
|
|
4826
|
+
if (cfVisibility && cfStyles.height)
|
|
4827
|
+
wrapperStyles.height = cfStyles.height;
|
|
4828
|
+
if (cfVisibility && cfStyles.maxWidth)
|
|
4829
|
+
wrapperStyles.maxWidth = cfStyles.maxWidth;
|
|
4830
|
+
if (cfVisibility && cfStyles.margin)
|
|
4831
|
+
wrapperStyles.margin = cfStyles.margin;
|
|
4832
|
+
}
|
|
4833
|
+
// Override component styles to fill the wrapper
|
|
4834
|
+
if (wrapperStyles.width)
|
|
4835
|
+
overrideStyles.width = '100%';
|
|
4836
|
+
if (wrapperStyles.height)
|
|
4837
|
+
overrideStyles.height = '100%';
|
|
4838
|
+
if (wrapperStyles.margin)
|
|
4839
|
+
overrideStyles.margin = '0';
|
|
4840
|
+
if (wrapperStyles.maxWidth)
|
|
4841
|
+
overrideStyles.maxWidth = 'none';
|
|
4842
|
+
// Prevent the dragging element from changing sizes when it has a percentage width or height
|
|
4843
|
+
if (draggingId === node.data.id && nodeRect) {
|
|
4844
|
+
if (requiresDragWrapper) {
|
|
4845
|
+
if (isPercentValue(cfStyles.width))
|
|
4846
|
+
wrapperStyles.maxWidth = nodeRect.width;
|
|
4847
|
+
if (isPercentValue(cfStyles.height))
|
|
4848
|
+
wrapperStyles.maxHeight = nodeRect.height;
|
|
4849
|
+
}
|
|
4850
|
+
else {
|
|
4851
|
+
if (isPercentValue(cfStyles.width))
|
|
4852
|
+
overrideStyles.maxWidth = nodeRect.width;
|
|
4853
|
+
if (isPercentValue(cfStyles.height))
|
|
4854
|
+
overrideStyles.maxHeight = nodeRect.height;
|
|
4855
|
+
}
|
|
4856
|
+
}
|
|
4857
|
+
return { overrideStyles, wrapperStyles };
|
|
4858
|
+
}, [
|
|
4859
|
+
cfStyles,
|
|
4860
|
+
options?.wrapContainerWidth,
|
|
4861
|
+
requiresDragWrapper,
|
|
4862
|
+
node.data.id,
|
|
4863
|
+
draggingId,
|
|
4864
|
+
nodeRect,
|
|
4865
|
+
cfVisibility,
|
|
4866
|
+
]);
|
|
4867
|
+
// Styles that will be applied to the component element
|
|
4868
|
+
// This has to be memoized to avoid recreating the styles in useEditorModeClassName on every render
|
|
4869
|
+
const componentStyles = useMemo(() => ({
|
|
4870
|
+
...cfStyles,
|
|
4871
|
+
...overrideStyles,
|
|
4872
|
+
...(isEmptyZone &&
|
|
4873
|
+
isStructureWithRelativeHeight(node?.data.blockId, cfStyles.height) && {
|
|
4874
|
+
minHeight: EMPTY_CONTAINER_HEIGHT,
|
|
4875
|
+
}),
|
|
4876
|
+
...(userIsDragging &&
|
|
4877
|
+
isStructureComponent &&
|
|
4878
|
+
!isSingleColumn &&
|
|
4879
|
+
!isAssemblyBlock && {
|
|
4880
|
+
padding: addExtraDropzonePadding(cfStyles.padding?.toString() || '0 0 0 0'),
|
|
4881
|
+
}),
|
|
4882
|
+
}), [
|
|
4883
|
+
cfStyles,
|
|
4884
|
+
isAssemblyBlock,
|
|
4885
|
+
isEmptyZone,
|
|
4886
|
+
isSingleColumn,
|
|
4887
|
+
isStructureComponent,
|
|
4888
|
+
node?.data.blockId,
|
|
4889
|
+
overrideStyles,
|
|
4890
|
+
userIsDragging,
|
|
4891
|
+
]);
|
|
4892
|
+
const componentClass = useEditorModeClassName({
|
|
4893
|
+
styles: componentStyles,
|
|
4894
|
+
nodeId: node.data.id,
|
|
4895
|
+
});
|
|
4896
|
+
const sharedProps = {
|
|
4897
|
+
'data-cf-node-id': node.data.id,
|
|
4898
|
+
'data-cf-node-block-id': node.data.blockId,
|
|
4899
|
+
'data-cf-node-block-type': node.type,
|
|
4900
|
+
className: props.cfSsrClassName ?? componentClass,
|
|
4901
|
+
...(definition?.children ? { children: renderDropzone(node) } : {}),
|
|
4902
|
+
};
|
|
4903
|
+
const customComponentProps = {
|
|
4904
|
+
...sharedProps,
|
|
4905
|
+
// Allows custom components to render differently in the editor. This needs to be activated
|
|
4906
|
+
// through options as the component has to be aware of this prop to not cause any React warnings.
|
|
4907
|
+
...(options?.enableCustomEditorView ? { isInExpEditorMode: true } : {}),
|
|
4908
|
+
...sanitizeNodeProps(props),
|
|
4909
|
+
};
|
|
4910
|
+
const structuralOrPatternComponentProps = {
|
|
4911
|
+
...sharedProps,
|
|
4912
|
+
editorMode: true,
|
|
4913
|
+
node,
|
|
4914
|
+
renderDropzone,
|
|
4915
|
+
};
|
|
4916
|
+
return {
|
|
4917
|
+
componentProps: isStructureComponent || isPatternNode
|
|
4918
|
+
? structuralOrPatternComponentProps
|
|
4919
|
+
: customComponentProps,
|
|
4920
|
+
componentStyles,
|
|
4921
|
+
wrapperStyles,
|
|
4922
|
+
};
|
|
4923
|
+
};
|
|
4924
|
+
const addExtraDropzonePadding = (padding) => padding
|
|
4925
|
+
.split(' ')
|
|
4926
|
+
.map((value) => parseFloat(value) === 0 ? `${DRAG_PADDING}px` : `calc(${value} + ${DRAG_PADDING}px)`)
|
|
4927
|
+
.join(' ');
|
|
4928
|
+
const isPercentValue = (value) => typeof value === 'string' && value.endsWith('%');
|
|
4929
|
+
|
|
4930
|
+
class ImportedComponentError extends Error {
|
|
4931
|
+
constructor(message) {
|
|
4932
|
+
super(message);
|
|
4933
|
+
this.name = 'ImportedComponentError';
|
|
4934
|
+
}
|
|
4935
|
+
}
|
|
4936
|
+
class ExperienceSDKError extends Error {
|
|
4937
|
+
constructor(message) {
|
|
4938
|
+
super(message);
|
|
4939
|
+
this.name = 'ExperienceSDKError';
|
|
4940
|
+
}
|
|
4941
|
+
}
|
|
4942
|
+
class ImportedComponentErrorBoundary extends React.Component {
|
|
4943
|
+
componentDidCatch(error, _errorInfo) {
|
|
4944
|
+
if (error.name === 'ImportedComponentError' || error.name === 'ExperienceSDKError') {
|
|
4945
|
+
// This error was already handled by a nested error boundary and should be passed upwards
|
|
4946
|
+
// We have to do this as we wrap every component on every layer with this error boundary and
|
|
4947
|
+
// thus an error deep in the tree bubbles through many layers of error boundaries.
|
|
4948
|
+
throw error;
|
|
4949
|
+
}
|
|
4950
|
+
// Differentiate between custom and SDK-provided components for error tracking
|
|
4951
|
+
const ErrorClass = isContentfulComponent(this.props.componentId)
|
|
4952
|
+
? ExperienceSDKError
|
|
4953
|
+
: ImportedComponentError;
|
|
4954
|
+
const err = new ErrorClass(error.message);
|
|
4955
|
+
err.stack = error.stack;
|
|
4956
|
+
throw err;
|
|
4957
|
+
}
|
|
4958
|
+
render() {
|
|
4959
|
+
return this.props.children;
|
|
4960
|
+
}
|
|
4961
|
+
}
|
|
4962
|
+
|
|
4963
|
+
const MissingComponentPlaceholder = ({ blockId }) => {
|
|
4964
|
+
return (React.createElement("div", { style: {
|
|
4965
|
+
border: '1px solid red',
|
|
4966
|
+
width: '100%',
|
|
4967
|
+
height: '100%',
|
|
4968
|
+
} },
|
|
4969
|
+
"Missing component '",
|
|
4970
|
+
blockId,
|
|
4971
|
+
"'"));
|
|
4972
|
+
};
|
|
4973
|
+
|
|
4974
|
+
const CircularDependencyErrorPlaceholder = forwardRef(({ wrappingPatternIds, ...props }, ref) => {
|
|
4975
|
+
const entityStore = useEntityStore((state) => state.entityStore);
|
|
4976
|
+
return (React.createElement("div", { ...props,
|
|
4977
|
+
// Pass through ref to avoid DND errors being logged
|
|
4978
|
+
ref: ref, "data-cf-node-error": "circular-pattern-dependency", style: {
|
|
4979
|
+
border: '1px solid red',
|
|
4980
|
+
background: 'rgba(255, 0, 0, 0.1)',
|
|
4981
|
+
padding: '1rem 1rem 0 1rem',
|
|
4982
|
+
width: '100%',
|
|
4983
|
+
height: '100%',
|
|
4984
|
+
} },
|
|
4985
|
+
"Circular usage of patterns detected:",
|
|
4986
|
+
React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
|
|
4987
|
+
const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
|
|
4988
|
+
const entry = entityStore.getEntityFromLink(entryLink);
|
|
4989
|
+
const entryTitle = entry?.fields?.title;
|
|
4990
|
+
const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
|
|
4991
|
+
return React.createElement("li", { key: patternId }, text);
|
|
4992
|
+
}))));
|
|
4993
|
+
});
|
|
4994
|
+
CircularDependencyErrorPlaceholder.displayName = 'CircularDependencyErrorPlaceholder';
|
|
4995
|
+
|
|
4996
|
+
const useComponent = ({ node, resolveDesignValue, renderDropzone, userIsDragging, wrappingPatternIds, }) => {
|
|
4997
|
+
const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
|
|
4998
|
+
const tree = useTreeStore((state) => state.tree);
|
|
4999
|
+
const componentRegistration = useMemo(() => {
|
|
5000
|
+
let registration = componentRegistry.get(node.data.blockId);
|
|
5001
|
+
if (node.type === ASSEMBLY_NODE_TYPE && !registration) {
|
|
5002
|
+
registration = createAssemblyRegistration({
|
|
5003
|
+
definitionId: node.data.blockId,
|
|
5004
|
+
component: Assembly,
|
|
5005
|
+
});
|
|
5006
|
+
}
|
|
5007
|
+
if (!registration) {
|
|
5008
|
+
console.warn(`Component registration not found for component with id: "${node.data.blockId}". The registered component might have been removed from the code. To proceed, remove the component manually from the layers tab.`);
|
|
5009
|
+
return undefined;
|
|
5010
|
+
}
|
|
5011
|
+
return registration;
|
|
5012
|
+
}, [node]);
|
|
5013
|
+
const componentId = node.data.id;
|
|
5014
|
+
const isPatternNode = node.type === ASSEMBLY_NODE_TYPE;
|
|
5015
|
+
const isPatternComponent = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
|
|
5016
|
+
const parentComponentNode = getItem({ id: node.parentId }, tree);
|
|
5017
|
+
const isNestedPattern = isPatternNode &&
|
|
5018
|
+
[ASSEMBLY_BLOCK_NODE_TYPE, ASSEMBLY_NODE_TYPE].includes(parentComponentNode?.type ?? '');
|
|
5019
|
+
const isStructureComponent = isContentfulStructureComponent(node.data.blockId);
|
|
5020
|
+
const requiresDragWrapper = !isPatternNode && !isStructureComponent && !componentRegistration?.options?.wrapComponent;
|
|
5021
|
+
const { componentProps, wrapperStyles } = useComponentProps({
|
|
5022
|
+
node,
|
|
3967
5023
|
areEntitiesFetched,
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
...cfStyles,
|
|
3975
|
-
...(!node.children.length &&
|
|
3976
|
-
isStructureWithRelativeHeight(node.data.blockId, cfStyles.height) && {
|
|
3977
|
-
minHeight: EMPTY_CONTAINER_HEIGHT$1,
|
|
3978
|
-
}),
|
|
3979
|
-
}), [cfStyles, node.children.length, node.data.blockId]);
|
|
3980
|
-
const cfCsrClassName = useEditorModeClassName({
|
|
3981
|
-
styles: componentStyles,
|
|
3982
|
-
nodeId: node.data.id,
|
|
5024
|
+
resolveDesignValue,
|
|
5025
|
+
renderDropzone,
|
|
5026
|
+
definition: componentRegistration?.definition,
|
|
5027
|
+
options: componentRegistration?.options,
|
|
5028
|
+
userIsDragging,
|
|
5029
|
+
requiresDragWrapper,
|
|
3983
5030
|
});
|
|
3984
|
-
const
|
|
3985
|
-
const
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
5031
|
+
const elementToRender = (props) => {
|
|
5032
|
+
const { dragProps = {} } = props || {};
|
|
5033
|
+
const { children, innerRef, Tag = 'div', ToolTipAndPlaceholder, style, ...rest } = dragProps;
|
|
5034
|
+
const { 'data-cf-node-block-id': dataCfNodeBlockId, 'data-cf-node-block-type': dataCfNodeBlockType, 'data-cf-node-id': dataCfNodeId, } = componentProps;
|
|
5035
|
+
const refCallback = (refNode) => {
|
|
5036
|
+
if (innerRef && refNode)
|
|
5037
|
+
innerRef(refNode);
|
|
3990
5038
|
};
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
editorMode: true,
|
|
3997
|
-
node,
|
|
3998
|
-
};
|
|
5039
|
+
if (!componentRegistration) {
|
|
5040
|
+
return React.createElement(MissingComponentPlaceholder, { blockId: node.data.blockId });
|
|
5041
|
+
}
|
|
5042
|
+
if (node.data.blockId && wrappingPatternIds.has(node.data.blockId)) {
|
|
5043
|
+
return (React.createElement(CircularDependencyErrorPlaceholder, { ref: refCallback, "data-cf-node-id": dataCfNodeId, "data-cf-node-block-id": dataCfNodeBlockId, "data-cf-node-block-type": dataCfNodeBlockType, wrappingPatternIds: wrappingPatternIds }));
|
|
3999
5044
|
}
|
|
5045
|
+
const element = React.createElement(ImportedComponentErrorBoundary, { componentId: node.data.blockId }, React.createElement(componentRegistration.component, {
|
|
5046
|
+
...componentProps,
|
|
5047
|
+
dragProps,
|
|
5048
|
+
}));
|
|
5049
|
+
if (!requiresDragWrapper) {
|
|
5050
|
+
return element;
|
|
5051
|
+
}
|
|
5052
|
+
return (React.createElement(Tag, { ...rest, style: { ...style, ...wrapperStyles }, ref: refCallback, "data-cf-node-id": dataCfNodeId, "data-cf-node-block-id": dataCfNodeBlockId, "data-cf-node-block-type": dataCfNodeBlockType },
|
|
5053
|
+
ToolTipAndPlaceholder,
|
|
5054
|
+
element));
|
|
5055
|
+
};
|
|
5056
|
+
return {
|
|
5057
|
+
node,
|
|
5058
|
+
parentComponentNode,
|
|
5059
|
+
isAssembly: isPatternNode,
|
|
5060
|
+
isPatternNode,
|
|
5061
|
+
isPatternComponent,
|
|
5062
|
+
isNestedPattern,
|
|
5063
|
+
componentId,
|
|
5064
|
+
elementToRender,
|
|
5065
|
+
definition: componentRegistration?.definition,
|
|
5066
|
+
};
|
|
5067
|
+
};
|
|
5068
|
+
|
|
5069
|
+
const calcOffsetLeft = (parentElement, placeholderWidth, nodeWidth) => {
|
|
5070
|
+
if (!parentElement) {
|
|
5071
|
+
return 0;
|
|
5072
|
+
}
|
|
5073
|
+
const alignItems = window.getComputedStyle(parentElement).alignItems;
|
|
5074
|
+
if (alignItems === 'center') {
|
|
5075
|
+
return -(placeholderWidth - nodeWidth) / 2;
|
|
5076
|
+
}
|
|
5077
|
+
if (alignItems === 'end') {
|
|
5078
|
+
return -placeholderWidth + nodeWidth + 2;
|
|
5079
|
+
}
|
|
5080
|
+
return 0;
|
|
5081
|
+
};
|
|
5082
|
+
const calcOffsetTop = (parentElement, placeholderHeight, nodeHeight) => {
|
|
5083
|
+
if (!parentElement) {
|
|
5084
|
+
return 0;
|
|
5085
|
+
}
|
|
5086
|
+
const alignItems = window.getComputedStyle(parentElement).alignItems;
|
|
5087
|
+
if (alignItems === 'center') {
|
|
5088
|
+
return -(placeholderHeight - nodeHeight) / 2;
|
|
5089
|
+
}
|
|
5090
|
+
if (alignItems === 'end') {
|
|
5091
|
+
return -placeholderHeight + nodeHeight + 2;
|
|
5092
|
+
}
|
|
5093
|
+
return 0;
|
|
5094
|
+
};
|
|
5095
|
+
const getPaddingOffset = (element) => {
|
|
5096
|
+
const paddingLeft = parseFloat(window.getComputedStyle(element).paddingLeft);
|
|
5097
|
+
const paddingRight = parseFloat(window.getComputedStyle(element).paddingRight);
|
|
5098
|
+
const paddingTop = parseFloat(window.getComputedStyle(element).paddingTop);
|
|
5099
|
+
const paddingBottom = parseFloat(window.getComputedStyle(element).paddingBottom);
|
|
5100
|
+
const horizontalOffset = paddingLeft + paddingRight;
|
|
5101
|
+
const verticalOffset = paddingTop + paddingBottom;
|
|
5102
|
+
return [horizontalOffset, verticalOffset];
|
|
5103
|
+
};
|
|
5104
|
+
/**
|
|
5105
|
+
* Calculate the size and position of the dropzone indicator
|
|
5106
|
+
* when dragging a new component onto the canvas
|
|
5107
|
+
*/
|
|
5108
|
+
const calcNewComponentStyles = (params) => {
|
|
5109
|
+
const { destinationIndex, elementIndex, dropzoneElementId, id, direction, totalIndexes } = params;
|
|
5110
|
+
const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
|
|
5111
|
+
const isHorizontal = direction === 'horizontal';
|
|
5112
|
+
const isRightAlign = isHorizontal && isEnd;
|
|
5113
|
+
const isBottomAlign = !isHorizontal && isEnd;
|
|
5114
|
+
const dropzone = document.querySelector(`[data-rfd-droppable-id="${dropzoneElementId}"]`);
|
|
5115
|
+
const element = document.querySelector(`[data-ctfl-draggable-id="${id}"]`);
|
|
5116
|
+
if (!dropzone || !element) {
|
|
5117
|
+
return emptyStyles;
|
|
5118
|
+
}
|
|
5119
|
+
const elementSizes = element.getBoundingClientRect();
|
|
5120
|
+
const dropzoneSizes = dropzone.getBoundingClientRect();
|
|
5121
|
+
const [horizontalPadding, verticalPadding] = getPaddingOffset(dropzone);
|
|
5122
|
+
const width = isHorizontal ? DRAGGABLE_WIDTH : dropzoneSizes.width - horizontalPadding;
|
|
5123
|
+
const height = isHorizontal ? dropzoneSizes.height - verticalPadding : DRAGGABLE_HEIGHT;
|
|
5124
|
+
const top = isHorizontal
|
|
5125
|
+
? calcOffsetTop(element.parentElement, height, elementSizes.height)
|
|
5126
|
+
: -height;
|
|
5127
|
+
const left = isHorizontal
|
|
5128
|
+
? -width
|
|
5129
|
+
: calcOffsetLeft(element.parentElement, width, elementSizes.width);
|
|
5130
|
+
return {
|
|
5131
|
+
width,
|
|
5132
|
+
height,
|
|
5133
|
+
top: !isBottomAlign ? top : 'unset',
|
|
5134
|
+
right: isRightAlign ? -width : 'unset',
|
|
5135
|
+
bottom: isBottomAlign ? -height : 'unset',
|
|
5136
|
+
left: !isRightAlign ? left : 'unset',
|
|
5137
|
+
};
|
|
5138
|
+
};
|
|
5139
|
+
/**
|
|
5140
|
+
* Calculate the size and position of the dropzone indicator
|
|
5141
|
+
* when moving an existing component on the canvas
|
|
5142
|
+
*/
|
|
5143
|
+
const calcMovementStyles = (params) => {
|
|
5144
|
+
const { destinationIndex, sourceIndex, destinationId, sourceId, elementIndex, dropzoneElementId, id, direction, totalIndexes, draggableId, } = params;
|
|
5145
|
+
const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
|
|
5146
|
+
const isHorizontal = direction === 'horizontal';
|
|
5147
|
+
const isSameZone = destinationId === sourceId;
|
|
5148
|
+
const isBelowSourceIndex = destinationIndex > sourceIndex;
|
|
5149
|
+
const isRightAlign = isHorizontal && (isEnd || (isSameZone && isBelowSourceIndex));
|
|
5150
|
+
const isBottomAlign = !isHorizontal && (isEnd || (isSameZone && isBelowSourceIndex));
|
|
5151
|
+
const dropzone = document.querySelector(`[data-rfd-droppable-id="${dropzoneElementId}"]`);
|
|
5152
|
+
const draggable = document.querySelector(`[data-rfd-draggable-id="${draggableId}"]`);
|
|
5153
|
+
const element = document.querySelector(`[data-ctfl-draggable-id="${id}"]`);
|
|
5154
|
+
if (!dropzone || !element || !draggable) {
|
|
5155
|
+
return emptyStyles;
|
|
5156
|
+
}
|
|
5157
|
+
const elementSizes = element.getBoundingClientRect();
|
|
5158
|
+
const dropzoneSizes = dropzone.getBoundingClientRect();
|
|
5159
|
+
const draggableSizes = draggable.getBoundingClientRect();
|
|
5160
|
+
const [horizontalPadding, verticalPadding] = getPaddingOffset(dropzone);
|
|
5161
|
+
const width = isHorizontal ? draggableSizes.width : dropzoneSizes.width - horizontalPadding;
|
|
5162
|
+
const height = isHorizontal ? dropzoneSizes.height - verticalPadding : draggableSizes.height;
|
|
5163
|
+
const top = isHorizontal
|
|
5164
|
+
? calcOffsetTop(element.parentElement, height, elementSizes.height)
|
|
5165
|
+
: -height;
|
|
5166
|
+
const left = isHorizontal
|
|
5167
|
+
? -width
|
|
5168
|
+
: calcOffsetLeft(element.parentElement, width, elementSizes.width);
|
|
5169
|
+
return {
|
|
5170
|
+
width,
|
|
5171
|
+
height,
|
|
5172
|
+
top: !isBottomAlign ? top : 'unset',
|
|
5173
|
+
right: isRightAlign ? -width : 'unset',
|
|
5174
|
+
bottom: isBottomAlign ? -height : 'unset',
|
|
5175
|
+
left: !isRightAlign ? left : 'unset',
|
|
5176
|
+
};
|
|
5177
|
+
};
|
|
5178
|
+
const emptyStyles = { width: 0, height: 0 };
|
|
5179
|
+
const calcPlaceholderStyles = (params) => {
|
|
5180
|
+
const { isDraggingOver, sourceId } = params;
|
|
5181
|
+
if (!isDraggingOver) {
|
|
5182
|
+
return emptyStyles;
|
|
5183
|
+
}
|
|
5184
|
+
if (sourceId === COMPONENT_LIST_ID) {
|
|
5185
|
+
return calcNewComponentStyles(params);
|
|
5186
|
+
}
|
|
5187
|
+
return calcMovementStyles(params);
|
|
5188
|
+
};
|
|
5189
|
+
const Placeholder = (props) => {
|
|
5190
|
+
const sourceIndex = useDraggedItemStore((state) => state.draggedItem?.source.index) ?? -1;
|
|
5191
|
+
const draggableId = useDraggedItemStore((state) => state.draggedItem?.draggableId) ?? '';
|
|
5192
|
+
const sourceId = useDraggedItemStore((state) => state.draggedItem?.source.droppableId) ?? '';
|
|
5193
|
+
const destinationIndex = useDraggedItemStore((state) => state.draggedItem?.destination?.index) ?? -1;
|
|
5194
|
+
const destinationId = useDraggedItemStore((state) => state.draggedItem?.destination?.droppableId) ?? '';
|
|
5195
|
+
const { elementIndex, totalIndexes, isDraggingOver } = props;
|
|
5196
|
+
const isActive = destinationIndex === elementIndex;
|
|
5197
|
+
const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
|
|
5198
|
+
const isVisible = isEnd || isActive;
|
|
5199
|
+
const isComponentList = destinationId === COMPONENT_LIST_ID;
|
|
5200
|
+
return (!isComponentList &&
|
|
5201
|
+
isDraggingOver &&
|
|
5202
|
+
isVisible && (React.createElement("div", { style: {
|
|
5203
|
+
...calcPlaceholderStyles({
|
|
5204
|
+
...props,
|
|
5205
|
+
sourceId,
|
|
5206
|
+
sourceIndex,
|
|
5207
|
+
destinationId,
|
|
5208
|
+
destinationIndex,
|
|
5209
|
+
draggableId,
|
|
5210
|
+
}),
|
|
5211
|
+
backgroundColor: 'rgba(var(--exp-builder-blue300-rgb), 0.5)',
|
|
5212
|
+
position: 'absolute',
|
|
5213
|
+
pointerEvents: 'none',
|
|
5214
|
+
} })));
|
|
5215
|
+
};
|
|
5216
|
+
|
|
5217
|
+
var css_248z$2 = ".styles-module_hitbox__i3wKV {\n position: fixed;\n pointer-events: all;\n}\n";
|
|
5218
|
+
var styles$2 = {"hitbox":"styles-module_hitbox__i3wKV"};
|
|
5219
|
+
styleInject(css_248z$2);
|
|
5220
|
+
|
|
5221
|
+
const useZoneStore = create()((set) => ({
|
|
5222
|
+
zones: {},
|
|
5223
|
+
hoveringZone: '',
|
|
5224
|
+
setHoveringZone(zoneId) {
|
|
5225
|
+
set({
|
|
5226
|
+
hoveringZone: zoneId,
|
|
5227
|
+
});
|
|
5228
|
+
},
|
|
5229
|
+
upsertZone(id, data) {
|
|
5230
|
+
set(produce((state) => {
|
|
5231
|
+
state.zones[id] = { ...(state.zones[id] || {}), ...data };
|
|
5232
|
+
}));
|
|
5233
|
+
},
|
|
5234
|
+
}));
|
|
5235
|
+
|
|
5236
|
+
const { WIDTH, HEIGHT, INITIAL_OFFSET, OFFSET_INCREMENT, MIN_HEIGHT, MIN_DEPTH_HEIGHT, DEEP_ZONE } = HITBOX;
|
|
5237
|
+
const calcOffsetDepth = (depth) => {
|
|
5238
|
+
return INITIAL_OFFSET - OFFSET_INCREMENT * depth;
|
|
5239
|
+
};
|
|
5240
|
+
const getHitboxStyles = ({ direction, zoneDepth, domRect, scrollY, offsetRect, }) => {
|
|
5241
|
+
if (!domRect) {
|
|
4000
5242
|
return {
|
|
4001
|
-
|
|
4002
|
-
// Allows custom components to render differently in the editor. This needs to be activated
|
|
4003
|
-
// through options as the component has to be aware of this prop to not cause any React warnings.
|
|
4004
|
-
...(options?.enableCustomEditorView ? { isInExpEditorMode: true } : {}),
|
|
4005
|
-
...sanitizeNodeProps(props),
|
|
5243
|
+
display: 'none',
|
|
4006
5244
|
};
|
|
4007
|
-
}
|
|
4008
|
-
|
|
5245
|
+
}
|
|
5246
|
+
const { width, height, top, left, bottom, right } = domRect;
|
|
5247
|
+
const { height: offsetHeight, width: offsetWidth } = offsetRect || { height: 0, width: 0 };
|
|
5248
|
+
const MAX_SELF_HEIGHT = DRAGGABLE_HEIGHT * 2;
|
|
5249
|
+
const isDeepZone = zoneDepth > DEEP_ZONE;
|
|
5250
|
+
const isAboveMaxHeight = height > MAX_SELF_HEIGHT;
|
|
5251
|
+
switch (direction) {
|
|
5252
|
+
case HitboxDirection.TOP:
|
|
5253
|
+
return {
|
|
5254
|
+
width,
|
|
5255
|
+
height: HEIGHT,
|
|
5256
|
+
top: top + offsetHeight - calcOffsetDepth(zoneDepth) - scrollY,
|
|
5257
|
+
left,
|
|
5258
|
+
zIndex: 100 + zoneDepth,
|
|
5259
|
+
};
|
|
5260
|
+
case HitboxDirection.BOTTOM:
|
|
5261
|
+
return {
|
|
5262
|
+
width,
|
|
5263
|
+
height: HEIGHT,
|
|
5264
|
+
top: bottom + offsetHeight + calcOffsetDepth(zoneDepth) - scrollY,
|
|
5265
|
+
left,
|
|
5266
|
+
zIndex: 100 + zoneDepth,
|
|
5267
|
+
};
|
|
5268
|
+
case HitboxDirection.LEFT:
|
|
5269
|
+
return {
|
|
5270
|
+
width: WIDTH,
|
|
5271
|
+
height: height - HEIGHT,
|
|
5272
|
+
left: left + offsetWidth - calcOffsetDepth(zoneDepth) - WIDTH / 2,
|
|
5273
|
+
top: top + HEIGHT / 2 - scrollY,
|
|
5274
|
+
zIndex: 100 + zoneDepth,
|
|
5275
|
+
};
|
|
5276
|
+
case HitboxDirection.RIGHT:
|
|
5277
|
+
return {
|
|
5278
|
+
width: WIDTH,
|
|
5279
|
+
height: height - HEIGHT,
|
|
5280
|
+
left: right + offsetWidth + calcOffsetDepth(zoneDepth) - WIDTH / 2,
|
|
5281
|
+
top: top + HEIGHT / 2 - scrollY,
|
|
5282
|
+
zIndex: 100 + zoneDepth,
|
|
5283
|
+
};
|
|
5284
|
+
case HitboxDirection.SELF_VERTICAL: {
|
|
5285
|
+
if (isAboveMaxHeight && !isDeepZone) {
|
|
5286
|
+
return { display: 'none' };
|
|
5287
|
+
}
|
|
5288
|
+
const selfHeight = isDeepZone ? MIN_DEPTH_HEIGHT : MIN_HEIGHT;
|
|
5289
|
+
return {
|
|
5290
|
+
width,
|
|
5291
|
+
height: selfHeight,
|
|
5292
|
+
left,
|
|
5293
|
+
top: top + height / 2 - selfHeight / 2 - scrollY,
|
|
5294
|
+
zIndex: 1000 + zoneDepth,
|
|
5295
|
+
};
|
|
5296
|
+
}
|
|
5297
|
+
case HitboxDirection.SELF_HORIZONTAL: {
|
|
5298
|
+
if (width > DRAGGABLE_WIDTH) {
|
|
5299
|
+
return { display: 'none' };
|
|
5300
|
+
}
|
|
5301
|
+
return {
|
|
5302
|
+
width: width - DRAGGABLE_WIDTH * 2,
|
|
5303
|
+
height,
|
|
5304
|
+
left: left + DRAGGABLE_WIDTH,
|
|
5305
|
+
top: top - scrollY,
|
|
5306
|
+
zIndex: 1000 + zoneDepth,
|
|
5307
|
+
};
|
|
5308
|
+
}
|
|
5309
|
+
default:
|
|
5310
|
+
return {};
|
|
5311
|
+
}
|
|
4009
5312
|
};
|
|
4010
5313
|
|
|
4011
|
-
|
|
4012
|
-
const
|
|
4013
|
-
const
|
|
4014
|
-
|
|
4015
|
-
|
|
5314
|
+
const Hitboxes = ({ zoneId, parentZoneId, isEmptyZone }) => {
|
|
5315
|
+
const tree = useTreeStore((state) => state.tree);
|
|
5316
|
+
const isDraggingOnCanvas = useDraggedItemStore((state) => state.isDraggingOnCanvas);
|
|
5317
|
+
const scrollY = useDraggedItemStore((state) => state.scrollY);
|
|
5318
|
+
const zoneDepth = useMemo(() => getItemDepthFromNode({ id: parentZoneId }, tree.root), [tree, parentZoneId]);
|
|
5319
|
+
const zones = useZoneStore((state) => state.zones);
|
|
5320
|
+
const hoveringZone = useZoneStore((state) => state.hoveringZone);
|
|
5321
|
+
const isHoveringZone = hoveringZone === zoneId;
|
|
5322
|
+
const hitboxContainer = useMemo(() => {
|
|
5323
|
+
return document.querySelector('[data-ctfl-hitboxes]');
|
|
5324
|
+
}, []);
|
|
5325
|
+
const domRect = useMemo(() => {
|
|
5326
|
+
if (!isDraggingOnCanvas)
|
|
5327
|
+
return;
|
|
5328
|
+
return document.querySelector(`[${CTFL_ZONE_ID}="${zoneId}"]`)?.getBoundingClientRect();
|
|
5329
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5330
|
+
}, [zoneId, isDraggingOnCanvas]);
|
|
5331
|
+
// Use the size of the cloned dragging element to offset the position of the hitboxes
|
|
5332
|
+
// So that when dragging causes a dropzone to expand, the hitboxes will be in the correct position
|
|
5333
|
+
const offsetRect = useMemo(() => {
|
|
5334
|
+
if (!isDraggingOnCanvas || isEmptyZone || !isHoveringZone)
|
|
5335
|
+
return;
|
|
5336
|
+
return document.querySelector(`[${CTFL_DRAGGING_ELEMENT}]`)?.getBoundingClientRect();
|
|
5337
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5338
|
+
}, [isEmptyZone, isHoveringZone, isDraggingOnCanvas]);
|
|
5339
|
+
const zoneDirection = zones[parentZoneId]?.direction || 'vertical';
|
|
5340
|
+
const isVertical = zoneDirection === 'vertical';
|
|
5341
|
+
const isRoot = parentZoneId === ROOT_ID;
|
|
5342
|
+
const { slotId: parentSlotId } = parseZoneId(parentZoneId);
|
|
5343
|
+
const getStyles = useCallback((direction) => getHitboxStyles({
|
|
5344
|
+
direction,
|
|
5345
|
+
zoneDepth,
|
|
5346
|
+
domRect,
|
|
5347
|
+
scrollY,
|
|
5348
|
+
offsetRect,
|
|
5349
|
+
}), [zoneDepth, domRect, scrollY, offsetRect]);
|
|
5350
|
+
const renderFinalRootHitbox = () => {
|
|
5351
|
+
if (!isRoot)
|
|
5352
|
+
return null;
|
|
5353
|
+
return (React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(HitboxDirection.BOTTOM) }));
|
|
5354
|
+
};
|
|
5355
|
+
const renderSurroundingHitboxes = () => {
|
|
5356
|
+
if (isRoot || parentSlotId)
|
|
5357
|
+
return null;
|
|
5358
|
+
return (React.createElement(React.Fragment, null,
|
|
5359
|
+
React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.TOP : HitboxDirection.LEFT) }),
|
|
5360
|
+
React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.BOTTOM : HitboxDirection.RIGHT) })));
|
|
5361
|
+
};
|
|
5362
|
+
const ActiveHitboxes = (React.createElement(React.Fragment, null,
|
|
5363
|
+
React.createElement("div", { "data-ctfl-zone-id": zoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.SELF_VERTICAL : HitboxDirection.SELF_HORIZONTAL) }),
|
|
5364
|
+
renderSurroundingHitboxes(),
|
|
5365
|
+
renderFinalRootHitbox()));
|
|
5366
|
+
if (!hitboxContainer) {
|
|
5367
|
+
return null;
|
|
5368
|
+
}
|
|
5369
|
+
return createPortal(ActiveHitboxes, hitboxContainer);
|
|
5370
|
+
};
|
|
5371
|
+
|
|
5372
|
+
const isRelativePreviewSize = (width) => {
|
|
5373
|
+
// For now, we solely allow 100% as relative value
|
|
5374
|
+
return width === '100%';
|
|
5375
|
+
};
|
|
5376
|
+
const getTooltipPositions = ({ previewSize, tooltipRect, coordinates, }) => {
|
|
5377
|
+
if (!coordinates || !tooltipRect) {
|
|
5378
|
+
return { display: 'none' };
|
|
5379
|
+
}
|
|
5380
|
+
/**
|
|
5381
|
+
* By default, the tooltip floats to the left of the element
|
|
5382
|
+
*/
|
|
5383
|
+
const newTooltipStyles = { display: 'flex' };
|
|
5384
|
+
// If the preview size is relative, we don't change the floating direction
|
|
5385
|
+
if (!isRelativePreviewSize(previewSize)) {
|
|
5386
|
+
const previewSizeMatch = previewSize.match(/(\d{1,})px/);
|
|
5387
|
+
if (!previewSizeMatch) {
|
|
5388
|
+
return { display: 'none' };
|
|
4016
5389
|
}
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
5390
|
+
const previewSizePx = parseInt(previewSizeMatch[1]);
|
|
5391
|
+
/**
|
|
5392
|
+
* If the element is at the right edge of the canvas, and the element isn't wide enough to fit the tooltip width,
|
|
5393
|
+
* we float the tooltip to the right of the element.
|
|
5394
|
+
*/
|
|
5395
|
+
if (tooltipRect.width > previewSizePx - coordinates.right &&
|
|
5396
|
+
tooltipRect.width > coordinates.width) {
|
|
5397
|
+
newTooltipStyles['float'] = 'right';
|
|
5398
|
+
}
|
|
5399
|
+
}
|
|
5400
|
+
const tooltipHeight = tooltipRect.height === 0 ? 32 : tooltipRect.height;
|
|
5401
|
+
/**
|
|
5402
|
+
* For elements with small heights, we don't want the tooltip covering the content in the element,
|
|
5403
|
+
* so we show the tooltip at the top or bottom.
|
|
5404
|
+
*/
|
|
5405
|
+
if (tooltipHeight * 2 > coordinates.height) {
|
|
5406
|
+
/**
|
|
5407
|
+
* If there's enough space for the tooltip at the top of the element, we show the tooltip at the top of the element,
|
|
5408
|
+
* else we show the tooltip at the bottom.
|
|
5409
|
+
*/
|
|
5410
|
+
if (tooltipHeight < coordinates.top) {
|
|
5411
|
+
newTooltipStyles['bottom'] = coordinates.height;
|
|
5412
|
+
}
|
|
5413
|
+
else {
|
|
5414
|
+
newTooltipStyles['top'] = coordinates.height;
|
|
5415
|
+
}
|
|
5416
|
+
}
|
|
5417
|
+
/**
|
|
5418
|
+
* If the component draws outside of the borders of the canvas to the left we move the tooltip to the right
|
|
5419
|
+
* so that it is fully visible.
|
|
5420
|
+
*/
|
|
5421
|
+
if (coordinates.left < 0) {
|
|
5422
|
+
newTooltipStyles['left'] = -coordinates.left;
|
|
5423
|
+
}
|
|
5424
|
+
/**
|
|
5425
|
+
* If for any reason, the element's top is negative, we show the tooltip at the bottom
|
|
5426
|
+
*/
|
|
5427
|
+
if (coordinates.top < 0) {
|
|
5428
|
+
newTooltipStyles['top'] = coordinates.height;
|
|
5429
|
+
}
|
|
5430
|
+
return newTooltipStyles;
|
|
5431
|
+
};
|
|
5432
|
+
|
|
5433
|
+
var css_248z$1 = ".styles-module_DraggableComponent__oyE7Q,\n.styles-module_Dropzone__3R-sm:not(.styles-module_isSlot__HI9yO) {\n position: relative;\n transition: background-color 0.2s;\n pointer-events: all;\n box-sizing: border-box;\n cursor: grab;\n}\n\n.styles-module_DraggableComponent__oyE7Q:before,\n.styles-module_Dropzone__3R-sm:not(.styles-module_isSlot__HI9yO):before {\n content: '';\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n outline-offset: -2px;\n outline: 2px solid transparent;\n z-index: 1;\n transition: outline 0.2s;\n pointer-events: none;\n}\n\n.styles-module_DraggableComponent__oyE7Q.styles-module_isDragging__hldL4.styles-module_Dropzone__3R-sm:before {\n outline-offset: -1px;\n}\n\n.styles-module_DraggableComponent__oyE7Q.styles-module_isDragging__hldL4.styles-module_Dropzone__3R-sm {\n pointer-events: all;\n}\n\n.styles-module_Dropzone__3R-sm.styles-module_fullHeight__afMfT {\n height: 100%;\n}\n\n.styles-module_Dropzone__3R-sm.styles-module_fullWidth__Od117 {\n width: 100%;\n}\n\n.styles-module_isRoot__c-c-x,\n.styles-module_isEmptyCanvas__Mm6Al {\n flex: 1;\n}\n\n.styles-module_isEmptyZone__XZ1Ej {\n min-height: 80px;\n min-width: 80px;\n}\n\n.styles-module_isDragging__hldL4:not(.styles-module_isRoot__c-c-x):not(.styles-module_DraggableClone__CdKIH):before {\n outline: 2px dashed var(--exp-builder-gray300);\n}\n\n.styles-module_Dropzone__3R-sm.styles-module_isDestination__sE70P:not(.styles-module_isRoot__c-c-x):before {\n transition:\n outline 0.2s,\n background-color 0.2s;\n outline: 2px dashed var(--exp-builder-blue400);\n background-color: rgba(var(--exp-builder-blue100-rgb), 0.5);\n z-index: 2;\n}\n\n.styles-module_DraggableClone__CdKIH:before {\n outline: 2px solid var(--exp-builder-blue500);\n}\n\n.styles-module_DropzoneClone__xiT8j,\n.styles-module_DraggableClone__CdKIH,\n.styles-module_DropzoneClone__xiT8j *,\n.styles-module_DraggableClone__CdKIH * {\n pointer-events: none !important;\n}\n\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isDragging__hldL4) :not(.styles-module_DraggableComponent__oyE7Q) {\n pointer-events: none;\n}\n\n.styles-module_isDraggingThisComponent__yCZTp {\n overflow: hidden;\n}\n\n.styles-module_isSelected__c2QEJ:before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_tooltipWrapper__kqvmR {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n z-index: 10;\n pointer-events: none;\n}\n\n.styles-module_DraggableComponent__oyE7Q.styles-module_isDragging__hldL4 .styles-module_tooltipWrapper__kqvmR {\n display: none;\n}\n\n.styles-module_overlay__knwhE {\n position: absolute;\n display: flex;\n align-items: center;\n min-width: max-content;\n height: 24px;\n z-index: 2;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 14px;\n font-weight: 500;\n background-color: var(--exp-builder-gray500);\n color: var(--exp-builder-color-white);\n padding: 4px 12px 4px 12px;\n transition: opacity 0.1s;\n opacity: 0;\n text-wrap: nowrap;\n}\n\n.styles-module_overlayContainer__lUsiC {\n opacity: 0;\n}\n\n.styles-module_overlayAssembly__3BKl4 {\n background-color: var(--exp-builder-purple600);\n}\n\n.styles-module_isDragging__hldL4 > .styles-module_overlay__knwhE,\n.styles-module_isDragging__hldL4 > .styles-module_overlayContainer__lUsiC {\n opacity: 0 !important;\n}\n\n.styles-module_isDragging__hldL4:not(.styles-module_Dropzone__3R-sm):before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_isHoveringComponent__f7G5m > div > .styles-module_overlay__knwhE,\n.styles-module_DraggableComponent__oyE7Q:hover:not(:has(.styles-module_DraggableComponent__oyE7Q:hover)) > div > .styles-module_overlay__knwhE {\n opacity: 1;\n}\n\n/* hovering related component in layers tab */\n\n.styles-module_DraggableComponent__oyE7Q:has(.styles-module_isHoveringComponent__f7G5m):not(.styles-module_isAssemblyBlock__goT9z):before,\n.styles-module_DraggableComponent__oyE7Q:has(.styles-module_isHoveringComponent__f7G5m):not(.styles-module_isAssemblyBlock__goT9z) .styles-module_DraggableComponent__oyE7Q:not(.styles-module_isHoveringComponent__f7G5m):not(.styles-module_isAssemblyBlock__goT9z):before,\n.styles-module_isHoveringComponent__f7G5m:not(.styles-module_isAssemblyBlock__goT9z) .styles-module_DraggableComponent__oyE7Q:not(.styles-module_isAssemblyBlock__goT9z):before,\n\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isAssemblyBlock__goT9z):not(.styles-module_isDragging__hldL4):hover:before,\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isDragging__hldL4):hover .styles-module_DraggableComponent__oyE7Q:before {\n outline: 2px dashed var(--exp-builder-gray500);\n}\n\n/* hovering component in layers tab */\n\n.styles-module_isHoveringComponent__f7G5m:not(.styles-module_isAssemblyBlock__goT9z):before,\n\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isAssemblyBlock__goT9z):not(.styles-module_isDragging__hldL4):hover:not(:has(.styles-module_DraggableComponent__oyE7Q:hover)):before {\n outline: 2px solid var(--exp-builder-gray500);\n}\n\n/* hovering related pattern in layers tab */\n\n.styles-module_isAssemblyBlock__goT9z:has(.styles-module_isHoveringComponent__f7G5m):before,\n.styles-module_isAssemblyBlock__goT9z:has(.styles-module_isHoveringComponent__f7G5m) .styles-module_isAssemblyBlock__goT9z:not(.styles-module_isHoveringComponent__f7G5m):before,\n.styles-module_isHoveringComponent__f7G5m .styles-module_isAssemblyBlock__goT9z:before,\n\n.styles-module_isAssemblyBlock__goT9z:hover:before,\n.styles-module_isAssemblyBlock__goT9z:hover .styles-module_DraggableComponent__oyE7Q:before,\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isDragging__hldL4):hover .styles-module_isAssemblyBlock__goT9z:before {\n outline: 2px dashed var(--exp-builder-purple600);\n}\n\n/* hovering pattern in layers tab */\n\n.styles-module_isAssemblyBlock__goT9z.styles-module_isHoveringComponent__f7G5m:before,\n\n.styles-module_isAssemblyBlock__goT9z:hover:not(:has(.styles-module_DraggableComponent__oyE7Q:hover)):before {\n outline: 2px solid var(--exp-builder-purple600);\n}\n";
|
|
5434
|
+
var styles$1 = {"DraggableComponent":"styles-module_DraggableComponent__oyE7Q","Dropzone":"styles-module_Dropzone__3R-sm","isSlot":"styles-module_isSlot__HI9yO","isDragging":"styles-module_isDragging__hldL4","fullHeight":"styles-module_fullHeight__afMfT","fullWidth":"styles-module_fullWidth__Od117","isRoot":"styles-module_isRoot__c-c-x","isEmptyCanvas":"styles-module_isEmptyCanvas__Mm6Al","isEmptyZone":"styles-module_isEmptyZone__XZ1Ej","DraggableClone":"styles-module_DraggableClone__CdKIH","isDestination":"styles-module_isDestination__sE70P","DropzoneClone":"styles-module_DropzoneClone__xiT8j","isDraggingThisComponent":"styles-module_isDraggingThisComponent__yCZTp","isSelected":"styles-module_isSelected__c2QEJ","tooltipWrapper":"styles-module_tooltipWrapper__kqvmR","overlay":"styles-module_overlay__knwhE","overlayContainer":"styles-module_overlayContainer__lUsiC","overlayAssembly":"styles-module_overlayAssembly__3BKl4","isHoveringComponent":"styles-module_isHoveringComponent__f7G5m","isAssemblyBlock":"styles-module_isAssemblyBlock__goT9z"};
|
|
5435
|
+
styleInject(css_248z$1);
|
|
5436
|
+
|
|
5437
|
+
const Tooltip = ({ coordinates, id, label, isAssemblyBlock, isContainer, isSelected, }) => {
|
|
5438
|
+
const tooltipRef = useRef(null);
|
|
5439
|
+
const previewSize = '100%'; // This should be based on breakpoints and added to usememo dependency array
|
|
5440
|
+
const tooltipStyles = useMemo(() => {
|
|
5441
|
+
const tooltipRect = tooltipRef.current?.getBoundingClientRect();
|
|
5442
|
+
const draggableRect = document
|
|
5443
|
+
.querySelector(`[data-ctfl-draggable-id="${id}"]`)
|
|
5444
|
+
?.getBoundingClientRect();
|
|
5445
|
+
const newTooltipStyles = getTooltipPositions({
|
|
5446
|
+
previewSize,
|
|
5447
|
+
tooltipRect,
|
|
5448
|
+
coordinates: draggableRect,
|
|
5449
|
+
});
|
|
5450
|
+
return newTooltipStyles;
|
|
5451
|
+
// Ignore eslint because we intentionally want to trigger this whenever a user clicks on a container/component which is tracked by these coordinates of the component being clicked being changed
|
|
5452
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5453
|
+
}, [coordinates, id, tooltipRef.current]);
|
|
5454
|
+
if (isSelected) {
|
|
5455
|
+
return null;
|
|
5456
|
+
}
|
|
5457
|
+
return (React.createElement("div", { "data-tooltip": true, className: styles$1.tooltipWrapper },
|
|
5458
|
+
React.createElement("div", { "data-tooltip": true, ref: tooltipRef, style: tooltipStyles, className: classNames(styles$1.overlay, {
|
|
5459
|
+
[styles$1.overlayContainer]: isContainer,
|
|
5460
|
+
[styles$1.overlayAssembly]: isAssemblyBlock,
|
|
5461
|
+
}) }, label)));
|
|
5462
|
+
};
|
|
5463
|
+
|
|
5464
|
+
function useSingleColumn(node, resolveDesignValue) {
|
|
5465
|
+
const tree = useTreeStore((store) => store.tree);
|
|
5466
|
+
const isSingleColumn = node.data.blockId === CONTENTFUL_COMPONENTS$1.singleColumn.id;
|
|
5467
|
+
const isWrapped = useMemo(() => {
|
|
5468
|
+
if (!node.parentId || !isSingleColumn) {
|
|
5469
|
+
return false;
|
|
5470
|
+
}
|
|
5471
|
+
const parentNode = getItem({ id: node.parentId }, tree);
|
|
5472
|
+
if (!parentNode || parentNode.data.blockId !== CONTENTFUL_COMPONENTS$1.columns.id) {
|
|
5473
|
+
return false;
|
|
5474
|
+
}
|
|
5475
|
+
const { cfWrapColumns } = parentNode.data.props;
|
|
5476
|
+
if (cfWrapColumns.type !== 'DesignValue') {
|
|
5477
|
+
return false;
|
|
5478
|
+
}
|
|
5479
|
+
return resolveDesignValue(cfWrapColumns.valuesByBreakpoint);
|
|
5480
|
+
}, [tree, node, isSingleColumn, resolveDesignValue]);
|
|
5481
|
+
return {
|
|
5482
|
+
isSingleColumn,
|
|
5483
|
+
isWrapped,
|
|
5484
|
+
};
|
|
4038
5485
|
}
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
5486
|
+
|
|
5487
|
+
function getStyle$1(style, snapshot) {
|
|
5488
|
+
if (!snapshot.isDropAnimating) {
|
|
5489
|
+
return style;
|
|
5490
|
+
}
|
|
5491
|
+
return {
|
|
5492
|
+
...style,
|
|
5493
|
+
// cannot be 0, but make it super tiny
|
|
5494
|
+
transitionDuration: `0.001s`,
|
|
5495
|
+
};
|
|
5496
|
+
}
|
|
5497
|
+
const EditorBlock = ({ node: rawNode, resolveDesignValue, renderDropzone, index, zoneId, userIsDragging, placeholder, wrappingPatternIds, }) => {
|
|
5498
|
+
const { slotId } = parseZoneId(zoneId);
|
|
5499
|
+
const ref = useRef(null);
|
|
5500
|
+
const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
|
|
5501
|
+
const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
|
|
5502
|
+
const { node, componentId, elementToRender, definition, isPatternNode, isPatternComponent, isNestedPattern, } = useComponent({
|
|
5503
|
+
node: rawNode,
|
|
4042
5504
|
resolveDesignValue,
|
|
4043
|
-
|
|
4044
|
-
|
|
5505
|
+
renderDropzone,
|
|
5506
|
+
userIsDragging,
|
|
5507
|
+
wrappingPatternIds,
|
|
4045
5508
|
});
|
|
4046
|
-
|
|
5509
|
+
const { isSingleColumn, isWrapped } = useSingleColumn(node, resolveDesignValue);
|
|
5510
|
+
const setDomRect = useDraggedItemStore((state) => state.setDomRect);
|
|
5511
|
+
const isHoveredComponent = useDraggedItemStore((state) => state.hoveredComponentId === componentId);
|
|
5512
|
+
const coordinates = useSelectedInstanceCoordinates({ node });
|
|
5513
|
+
const displayName = node.data.displayName || rawNode.data.displayName || definition?.name;
|
|
5514
|
+
const testId = `draggable-${node.data.blockId ?? 'node'}`;
|
|
5515
|
+
const isSelected = node.data.id === selectedNodeId;
|
|
5516
|
+
const isContainer = node.data.blockId === CONTENTFUL_COMPONENTS$1.container.id;
|
|
5517
|
+
const isSlotComponent = Boolean(node.data.slotId);
|
|
5518
|
+
const isDragDisabled = isNestedPattern || isPatternComponent || (isSingleColumn && isWrapped) || isSlotComponent;
|
|
5519
|
+
const isEmptyZone = useMemo(() => {
|
|
5520
|
+
return !node.children.filter((node) => node.data.slotId === slotId).length;
|
|
5521
|
+
}, [node.children, slotId]);
|
|
5522
|
+
useDraggablePosition({
|
|
5523
|
+
draggableId: componentId,
|
|
5524
|
+
draggableRef: ref,
|
|
5525
|
+
position: DraggablePosition.MOUSE_POSITION,
|
|
5526
|
+
});
|
|
5527
|
+
const onClick = (e) => {
|
|
5528
|
+
e.stopPropagation();
|
|
5529
|
+
if (!userIsDragging) {
|
|
5530
|
+
setSelectedNodeId(node.data.id);
|
|
5531
|
+
// if it is the assembly directly we just want to select it as a normal component
|
|
5532
|
+
if (isPatternNode) {
|
|
5533
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
5534
|
+
nodeId: node.data.id,
|
|
5535
|
+
});
|
|
5536
|
+
return;
|
|
5537
|
+
}
|
|
5538
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
5539
|
+
assembly: node.data.assembly,
|
|
5540
|
+
nodeId: node.data.id,
|
|
5541
|
+
});
|
|
5542
|
+
}
|
|
5543
|
+
};
|
|
5544
|
+
const onMouseOver = (e) => {
|
|
5545
|
+
e.stopPropagation();
|
|
5546
|
+
if (userIsDragging)
|
|
5547
|
+
return;
|
|
5548
|
+
sendMessage(OUTGOING_EVENTS.NewHoveredElement, {
|
|
5549
|
+
nodeId: componentId,
|
|
5550
|
+
});
|
|
5551
|
+
};
|
|
5552
|
+
const onMouseDown = (e) => {
|
|
5553
|
+
if (isDragDisabled) {
|
|
5554
|
+
return;
|
|
5555
|
+
}
|
|
5556
|
+
e.stopPropagation();
|
|
5557
|
+
setDomRect(e.currentTarget.getBoundingClientRect());
|
|
5558
|
+
};
|
|
5559
|
+
const ToolTipAndPlaceholder = (React.createElement(React.Fragment, null,
|
|
5560
|
+
React.createElement(Tooltip, { id: componentId, coordinates: coordinates, isAssemblyBlock: isPatternNode || isPatternComponent, isContainer: isContainer, isSelected: isSelected, label: displayName || 'No label specified' }),
|
|
5561
|
+
React.createElement(Placeholder, { ...placeholder, id: componentId }),
|
|
5562
|
+
userIsDragging && !isPatternComponent && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId, isEmptyZone: isEmptyZone }))));
|
|
5563
|
+
return (React.createElement(Draggable, { key: componentId, draggableId: componentId, index: index, isDragDisabled: isDragDisabled, disableInteractiveElementBlocking: true }, (provided, snapshot) => elementToRender({
|
|
5564
|
+
dragProps: {
|
|
5565
|
+
...provided.draggableProps,
|
|
5566
|
+
...provided.dragHandleProps,
|
|
5567
|
+
'data-ctfl-draggable-id': componentId,
|
|
5568
|
+
'data-test-id': testId,
|
|
5569
|
+
innerRef: (refNode) => {
|
|
5570
|
+
provided?.innerRef(refNode);
|
|
5571
|
+
ref.current = refNode;
|
|
5572
|
+
},
|
|
5573
|
+
className: classNames(styles$1.DraggableComponent, {
|
|
5574
|
+
[styles$1.isAssemblyBlock]: isPatternComponent || isPatternNode,
|
|
5575
|
+
[styles$1.isDragging]: snapshot?.isDragging || userIsDragging,
|
|
5576
|
+
[styles$1.isSelected]: isSelected,
|
|
5577
|
+
[styles$1.isHoveringComponent]: isHoveredComponent,
|
|
5578
|
+
}),
|
|
5579
|
+
style: getStyle$1(provided.draggableProps.style, snapshot),
|
|
5580
|
+
onMouseDown,
|
|
5581
|
+
onMouseOver,
|
|
5582
|
+
onClick,
|
|
5583
|
+
ToolTipAndPlaceholder,
|
|
5584
|
+
},
|
|
5585
|
+
})));
|
|
4047
5586
|
};
|
|
4048
5587
|
|
|
4049
|
-
var css_248z = ".
|
|
4050
|
-
var styles = {"
|
|
5588
|
+
var css_248z = ".EmptyContainer-module_container__XPH5b {\n height: 200px;\n display: flex;\n width: 100%;\n position: absolute;\n align-items: center;\n justify-content: center;\n flex-direction: row;\n transition: all 0.2s;\n color: var(--exp-builder-gray400);\n font-size: var(--exp-builder-font-size-l);\n font-family: var(--exp-builder-font-stack-primary);\n outline: 2px dashed var(--exp-builder-gray400);\n outline-offset: -2px;\n}\n\n.EmptyContainer-module_highlight__lcICy:hover {\n outline: 2px dashed var(--exp-builder-blue500);\n background-color: rgba(var(--exp-builder-blue100-rgb), 0.5);\n cursor: grabbing;\n}\n\n.EmptyContainer-module_icon__82-2O rect {\n fill: var(--exp-builder-gray400);\n}\n\n.EmptyContainer-module_label__4TxRa {\n margin-left: var(--exp-builder-spacing-s);\n}\n";
|
|
5589
|
+
var styles = {"container":"EmptyContainer-module_container__XPH5b","highlight":"EmptyContainer-module_highlight__lcICy","icon":"EmptyContainer-module_icon__82-2O","label":"EmptyContainer-module_label__4TxRa"};
|
|
4051
5590
|
styleInject(css_248z);
|
|
4052
5591
|
|
|
4053
|
-
const
|
|
4054
|
-
return (React.createElement("div", { className: styles
|
|
4055
|
-
|
|
5592
|
+
const EmptyContainer = ({ isDragging }) => {
|
|
5593
|
+
return (React.createElement("div", { className: classNames(styles.container, {
|
|
5594
|
+
[styles.highlight]: isDragging,
|
|
5595
|
+
}), "data-type": "empty-container" },
|
|
5596
|
+
React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "37", height: "36", fill: "none", className: styles.icon },
|
|
4056
5597
|
React.createElement("rect", { width: "11.676", height: "11.676", x: "18.512", y: ".153", rx: "1.621", transform: "rotate(45 18.512 .153)" }),
|
|
4057
5598
|
React.createElement("rect", { width: "11.676", height: "11.676", x: "9.254", y: "9.139", rx: "1.621", transform: "rotate(45 9.254 9.139)" }),
|
|
4058
5599
|
React.createElement("rect", { width: "11.676", height: "11.676", x: "18.011", y: "18.625", rx: "1.621", transform: "rotate(45 18.01 18.625)" }),
|
|
4059
5600
|
React.createElement("rect", { width: "11.676", height: "11.676", x: "30.557", y: "10.131", rx: "1.621", transform: "rotate(60 30.557 10.13)" }),
|
|
4060
5601
|
React.createElement("path", { fill: "#fff", stroke: "#fff", strokeWidth: ".243", d: "M31.113 17.038a.463.463 0 0 0-.683-.517l-1.763 1.032-1.033-1.763a.464.464 0 0 0-.8.469l1.034 1.763-1.763 1.033a.463.463 0 1 0 .468.8l1.763-1.033 1.033 1.763a.463.463 0 1 0 .8-.469l-1.033-1.763 1.763-1.033a.463.463 0 0 0 .214-.282Z" })),
|
|
4061
|
-
React.createElement("span", { className: styles
|
|
5602
|
+
React.createElement("span", { className: styles.label }, "Add components to begin")));
|
|
5603
|
+
};
|
|
5604
|
+
|
|
5605
|
+
const useDropzoneDirection = ({ resolveDesignValue, node, zoneId }) => {
|
|
5606
|
+
const zone = useZoneStore((state) => state.zones);
|
|
5607
|
+
const upsertZone = useZoneStore((state) => state.upsertZone);
|
|
5608
|
+
useEffect(() => {
|
|
5609
|
+
function getDirection() {
|
|
5610
|
+
if (!node || !node.data.blockId) {
|
|
5611
|
+
return 'vertical';
|
|
5612
|
+
}
|
|
5613
|
+
if (!isContentfulStructureComponent(node.data.blockId)) {
|
|
5614
|
+
return 'vertical';
|
|
5615
|
+
}
|
|
5616
|
+
if (node.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id) {
|
|
5617
|
+
return 'horizontal';
|
|
5618
|
+
}
|
|
5619
|
+
const designValues = node.data.props['cfFlexDirection'];
|
|
5620
|
+
if (!designValues || !resolveDesignValue || designValues.type !== 'DesignValue') {
|
|
5621
|
+
return 'vertical';
|
|
5622
|
+
}
|
|
5623
|
+
const direction = resolveDesignValue(designValues.valuesByBreakpoint);
|
|
5624
|
+
if (direction === 'row') {
|
|
5625
|
+
return 'horizontal';
|
|
5626
|
+
}
|
|
5627
|
+
return 'vertical';
|
|
5628
|
+
}
|
|
5629
|
+
upsertZone(zoneId, { direction: getDirection() });
|
|
5630
|
+
}, [node, resolveDesignValue, zoneId, upsertZone]);
|
|
5631
|
+
return zone[zoneId]?.direction || 'vertical';
|
|
5632
|
+
};
|
|
5633
|
+
|
|
5634
|
+
function getStyle(style = {}, snapshot) {
|
|
5635
|
+
if (!snapshot?.isDropAnimating) {
|
|
5636
|
+
return style;
|
|
5637
|
+
}
|
|
5638
|
+
return {
|
|
5639
|
+
...style,
|
|
5640
|
+
// cannot be 0, but make it super tiny
|
|
5641
|
+
transitionDuration: `0.001s`,
|
|
5642
|
+
};
|
|
5643
|
+
}
|
|
5644
|
+
const EditorBlockClone = ({ node: rawNode, resolveDesignValue, snapshot, provided, renderDropzone, wrappingPatternIds, }) => {
|
|
5645
|
+
const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
|
|
5646
|
+
const { node, elementToRender } = useComponent({
|
|
5647
|
+
node: rawNode,
|
|
5648
|
+
resolveDesignValue,
|
|
5649
|
+
renderDropzone,
|
|
5650
|
+
userIsDragging,
|
|
5651
|
+
wrappingPatternIds,
|
|
5652
|
+
});
|
|
5653
|
+
const isAssemblyBlock = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
|
|
5654
|
+
return elementToRender({
|
|
5655
|
+
dragProps: {
|
|
5656
|
+
...provided?.draggableProps,
|
|
5657
|
+
...provided?.dragHandleProps,
|
|
5658
|
+
'data-ctfl-dragging-element': 'true',
|
|
5659
|
+
innerRef: provided?.innerRef,
|
|
5660
|
+
className: classNames(styles$1.DraggableComponent, styles$1.DraggableClone, {
|
|
5661
|
+
[styles$1.isAssemblyBlock]: isAssemblyBlock,
|
|
5662
|
+
[styles$1.isDragging]: snapshot?.isDragging,
|
|
5663
|
+
}),
|
|
5664
|
+
style: getStyle(provided?.draggableProps.style, snapshot),
|
|
5665
|
+
},
|
|
5666
|
+
});
|
|
4062
5667
|
};
|
|
4063
5668
|
|
|
4064
|
-
const
|
|
5669
|
+
const getHtmlDragProps = (dragProps) => {
|
|
5670
|
+
if (dragProps) {
|
|
5671
|
+
const { ToolTipAndPlaceholder, Tag, innerRef, wrapComponent, ...htmlDragProps } = dragProps;
|
|
5672
|
+
return htmlDragProps;
|
|
5673
|
+
}
|
|
5674
|
+
return {};
|
|
5675
|
+
};
|
|
5676
|
+
const getHtmlComponentProps = (props) => {
|
|
5677
|
+
if (props) {
|
|
5678
|
+
const { editorMode, renderDropzone, node, ...htmlProps } = props;
|
|
5679
|
+
return htmlProps;
|
|
5680
|
+
}
|
|
5681
|
+
return {};
|
|
5682
|
+
};
|
|
5683
|
+
|
|
5684
|
+
function DropzoneClone({ node, zoneId, resolveDesignValue, WrapperComponent = 'div', renderDropzone, dragProps, wrappingPatternIds, ...rest }) {
|
|
5685
|
+
const tree = useTreeStore((state) => state.tree);
|
|
5686
|
+
const content = node?.children || tree.root?.children || [];
|
|
5687
|
+
const { slotId } = parseZoneId(zoneId);
|
|
5688
|
+
const htmlDraggableProps = getHtmlDragProps(dragProps);
|
|
5689
|
+
const htmlProps = getHtmlComponentProps(rest);
|
|
5690
|
+
const isRootZone = zoneId === ROOT_ID;
|
|
5691
|
+
if (!resolveDesignValue) {
|
|
5692
|
+
return null;
|
|
5693
|
+
}
|
|
5694
|
+
return (React.createElement(WrapperComponent, { ...htmlDraggableProps, ...htmlProps, className: classNames(dragProps?.className, styles$1.Dropzone, styles$1.DropzoneClone, rest.className, {
|
|
5695
|
+
[styles$1.isRoot]: isRootZone,
|
|
5696
|
+
[styles$1.isEmptyZone]: !content.length,
|
|
5697
|
+
}), "data-ctfl-slot-id": slotId, ref: (refNode) => {
|
|
5698
|
+
if (dragProps?.innerRef) {
|
|
5699
|
+
dragProps.innerRef(refNode);
|
|
5700
|
+
}
|
|
5701
|
+
} }, content
|
|
5702
|
+
.filter((node) => node.data.slotId === slotId)
|
|
5703
|
+
.map((item) => {
|
|
5704
|
+
const componentId = item.data.id;
|
|
5705
|
+
return (React.createElement(EditorBlockClone, { key: componentId, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone, wrappingPatternIds: wrappingPatternIds }));
|
|
5706
|
+
})));
|
|
5707
|
+
}
|
|
5708
|
+
|
|
5709
|
+
function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponent = 'div', dragProps, wrappingPatternIds: parentWrappingPatternIds = new Set(), ...rest }) {
|
|
5710
|
+
const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
|
|
5711
|
+
const draggedItem = useDraggedItemStore((state) => state.draggedItem);
|
|
5712
|
+
const isDraggingNewComponent = useDraggedItemStore((state) => Boolean(state.componentId));
|
|
5713
|
+
const isHoveringZone = useZoneStore((state) => state.hoveringZone === zoneId);
|
|
5714
|
+
const tree = useTreeStore((state) => state.tree);
|
|
5715
|
+
const content = node?.children || tree.root?.children || [];
|
|
5716
|
+
const { slotId } = parseZoneId(zoneId);
|
|
5717
|
+
const direction = useDropzoneDirection({ resolveDesignValue, node, zoneId });
|
|
5718
|
+
const draggedDestinationId = draggedItem && draggedItem.destination?.droppableId;
|
|
5719
|
+
const draggedNode = useMemo(() => {
|
|
5720
|
+
if (!draggedItem)
|
|
5721
|
+
return;
|
|
5722
|
+
return getItem({ id: draggedItem.draggableId }, tree);
|
|
5723
|
+
}, [draggedItem, tree]);
|
|
5724
|
+
const isRootZone = zoneId === ROOT_ID;
|
|
5725
|
+
const isDestination = draggedDestinationId === zoneId;
|
|
5726
|
+
const isEmptyCanvas = isRootZone && !content.length;
|
|
5727
|
+
const isAssembly = ASSEMBLY_NODE_TYPES.includes(node?.type || '');
|
|
5728
|
+
const isRootAssembly = node?.type === ASSEMBLY_NODE_TYPE;
|
|
5729
|
+
const htmlDraggableProps = getHtmlDragProps(dragProps);
|
|
5730
|
+
const htmlProps = getHtmlComponentProps(rest);
|
|
5731
|
+
const wrappingPatternIds = useMemo(() => {
|
|
5732
|
+
// On the top level, the node is not defined. If the root blockId is not the default string,
|
|
5733
|
+
// we assume that it is the entry ID of the experience/ pattern to properly detect circular dependencies
|
|
5734
|
+
if (!node && tree.root.data.blockId && tree.root.data.blockId !== ROOT_ID) {
|
|
5735
|
+
return new Set([tree.root.data.blockId, ...parentWrappingPatternIds]);
|
|
5736
|
+
}
|
|
5737
|
+
if (isRootAssembly && node?.data.blockId) {
|
|
5738
|
+
return new Set([node.data.blockId, ...parentWrappingPatternIds]);
|
|
5739
|
+
}
|
|
5740
|
+
return parentWrappingPatternIds;
|
|
5741
|
+
}, [isRootAssembly, node, parentWrappingPatternIds, tree.root.data.blockId]);
|
|
5742
|
+
// To avoid a circular dependency, we create the recursive rendering function here and trickle it down
|
|
5743
|
+
const renderDropzone = useCallback((node, props) => {
|
|
5744
|
+
return (React.createElement(Dropzone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds, ...props }));
|
|
5745
|
+
}, [wrappingPatternIds, resolveDesignValue]);
|
|
5746
|
+
const renderClonedDropzone = useCallback((node, props) => {
|
|
5747
|
+
return (React.createElement(DropzoneClone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, renderDropzone: renderClonedDropzone, wrappingPatternIds: wrappingPatternIds, ...props }));
|
|
5748
|
+
}, [resolveDesignValue, wrappingPatternIds]);
|
|
5749
|
+
const isDropzoneEnabled = useMemo(() => {
|
|
5750
|
+
const isColumns = node?.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id;
|
|
5751
|
+
const isDraggingSingleColumn = draggedNode?.data.blockId === CONTENTFUL_COMPONENTS$1.singleColumn.id;
|
|
5752
|
+
const isParentOfDraggedNode = node?.data.id === draggedNode?.parentId;
|
|
5753
|
+
// If dragging a single column, only enable the dropzone of the parent
|
|
5754
|
+
// columns component
|
|
5755
|
+
if (isDraggingSingleColumn && isColumns && isParentOfDraggedNode) {
|
|
5756
|
+
return true;
|
|
5757
|
+
}
|
|
5758
|
+
// If dragging a single column, disable dropzones for any component besides
|
|
5759
|
+
// the parent of the dragged single column
|
|
5760
|
+
if (isDraggingSingleColumn && !isParentOfDraggedNode) {
|
|
5761
|
+
return false;
|
|
5762
|
+
}
|
|
5763
|
+
// Disable dropzone for Columns component
|
|
5764
|
+
if (isColumns) {
|
|
5765
|
+
return false;
|
|
5766
|
+
}
|
|
5767
|
+
// Disable dropzone for Assembly
|
|
5768
|
+
if (isAssembly) {
|
|
5769
|
+
return false;
|
|
5770
|
+
}
|
|
5771
|
+
// Enable dropzone for the non-root hovered zones if component is not allowed on root
|
|
5772
|
+
if (!isDraggingNewComponent &&
|
|
5773
|
+
!isComponentAllowedOnRoot({ type: draggedNode?.type, componentId: draggedNode?.data.blockId })) {
|
|
5774
|
+
return isHoveringZone && !isRootZone;
|
|
5775
|
+
}
|
|
5776
|
+
// Enable dropzone for the hovered zone only
|
|
5777
|
+
return isHoveringZone;
|
|
5778
|
+
}, [isAssembly, isHoveringZone, isRootZone, isDraggingNewComponent, draggedNode, node]);
|
|
5779
|
+
if (!resolveDesignValue) {
|
|
5780
|
+
return null;
|
|
5781
|
+
}
|
|
5782
|
+
const isPatternWrapperComponentFullHeight = isRootAssembly
|
|
5783
|
+
? node.children.length === 1 &&
|
|
5784
|
+
resolveDesignValue(node?.children[0]?.data.props.cfHeight?.valuesByBreakpoint ?? {}, 'cfHeight') === '100%'
|
|
5785
|
+
: false;
|
|
5786
|
+
const isPatternWrapperComponentFullWidth = isRootAssembly
|
|
5787
|
+
? node.children.length === 1 &&
|
|
5788
|
+
resolveDesignValue(node?.children[0]?.data.props.cfWidth?.valuesByBreakpoint ?? {}, 'cfWidth') === '100%'
|
|
5789
|
+
: false;
|
|
5790
|
+
return (React.createElement(Droppable, { droppableId: zoneId, direction: direction, isDropDisabled: !isDropzoneEnabled, renderClone: (provided, snapshot, rubic) => (React.createElement(EditorBlockClone, { node: content[rubic.source.index], resolveDesignValue: resolveDesignValue, provided: provided, snapshot: snapshot, renderDropzone: renderClonedDropzone, wrappingPatternIds: wrappingPatternIds })) }, (provided, snapshot) => {
|
|
5791
|
+
return (React.createElement(WrapperComponent, { ...(provided || { droppableProps: {} }).droppableProps, ...htmlDraggableProps, ...htmlProps, ref: (refNode) => {
|
|
5792
|
+
if (dragProps?.innerRef) {
|
|
5793
|
+
dragProps.innerRef(refNode);
|
|
5794
|
+
}
|
|
5795
|
+
provided?.innerRef(refNode);
|
|
5796
|
+
}, id: zoneId, "data-ctfl-zone-id": zoneId, "data-ctfl-slot-id": slotId, className: classNames(dragProps?.className, styles$1.Dropzone, className, {
|
|
5797
|
+
[styles$1.isEmptyCanvas]: isEmptyCanvas,
|
|
5798
|
+
[styles$1.isDragging]: userIsDragging,
|
|
5799
|
+
[styles$1.isDestination]: isDestination && !isAssembly,
|
|
5800
|
+
[styles$1.isRoot]: isRootZone,
|
|
5801
|
+
[styles$1.isEmptyZone]: !content.length,
|
|
5802
|
+
[styles$1.isSlot]: Boolean(slotId),
|
|
5803
|
+
[styles$1.fullHeight]: isPatternWrapperComponentFullHeight,
|
|
5804
|
+
[styles$1.fullWidth]: isPatternWrapperComponentFullWidth,
|
|
5805
|
+
}) },
|
|
5806
|
+
isEmptyCanvas ? (React.createElement(EmptyContainer, { isDragging: isRootZone && userIsDragging })) : (content
|
|
5807
|
+
.filter((node) => node.data.slotId === slotId)
|
|
5808
|
+
.map((item, i) => (React.createElement(EditorBlock, { placeholder: {
|
|
5809
|
+
isDraggingOver: snapshot?.isDraggingOver,
|
|
5810
|
+
totalIndexes: content.length,
|
|
5811
|
+
elementIndex: i,
|
|
5812
|
+
dropzoneElementId: zoneId,
|
|
5813
|
+
direction,
|
|
5814
|
+
}, index: i, zoneId: zoneId, key: item.data.id, userIsDragging: userIsDragging, draggingNewComponent: isDraggingNewComponent, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone, wrappingPatternIds: wrappingPatternIds })))),
|
|
5815
|
+
provided?.placeholder,
|
|
5816
|
+
dragProps?.ToolTipAndPlaceholder));
|
|
5817
|
+
}));
|
|
5818
|
+
}
|
|
5819
|
+
|
|
5820
|
+
const RootRenderer = ({ onChange }) => {
|
|
4065
5821
|
useEditorSubscriber();
|
|
5822
|
+
const dragItem = useDraggedItemStore((state) => state.componentId);
|
|
5823
|
+
const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
|
|
5824
|
+
const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
|
|
4066
5825
|
const breakpoints = useTreeStore((state) => state.breakpoints);
|
|
5826
|
+
const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
|
|
4067
5827
|
const containerRef = useRef(null);
|
|
4068
5828
|
const { resolveDesignValue } = useBreakpoints(breakpoints);
|
|
5829
|
+
const [containerStyles, setContainerStyles] = useState({});
|
|
4069
5830
|
const tree = useTreeStore((state) => state.tree);
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
5831
|
+
const handleMouseOver = useCallback(() => {
|
|
5832
|
+
// Remove hover state set by UI when mouse is over canvas
|
|
5833
|
+
setHoveredComponentId();
|
|
5834
|
+
// Remove hover styling from components in the layers tab
|
|
5835
|
+
sendMessage(OUTGOING_EVENTS.NewHoveredElement, {});
|
|
5836
|
+
}, [setHoveredComponentId]);
|
|
5837
|
+
const handleClickOutside = useCallback((e) => {
|
|
5838
|
+
const element = e.target;
|
|
5839
|
+
const isRoot = element.getAttribute('data-ctfl-zone-id') === ROOT_ID;
|
|
5840
|
+
const clickedOnCanvas = element.closest(`[data-ctfl-root]`);
|
|
5841
|
+
if (clickedOnCanvas && !isRoot) {
|
|
5842
|
+
return;
|
|
5843
|
+
}
|
|
5844
|
+
sendMessage(OUTGOING_EVENTS.OutsideCanvasClick, {
|
|
5845
|
+
outsideCanvasClick: true,
|
|
5846
|
+
});
|
|
5847
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
5848
|
+
nodeId: '',
|
|
5849
|
+
});
|
|
5850
|
+
setSelectedNodeId('');
|
|
5851
|
+
}, [setSelectedNodeId]);
|
|
5852
|
+
const handleResizeCanvas = useCallback(() => {
|
|
5853
|
+
const parentElement = containerRef.current?.parentElement;
|
|
5854
|
+
if (!parentElement) {
|
|
5855
|
+
return;
|
|
5856
|
+
}
|
|
5857
|
+
let siblingHeight = 0;
|
|
5858
|
+
for (const child of parentElement.children) {
|
|
5859
|
+
if (!child.hasAttribute('data-ctfl-root')) {
|
|
5860
|
+
siblingHeight += child.getBoundingClientRect().height;
|
|
5861
|
+
}
|
|
5862
|
+
}
|
|
5863
|
+
if (!siblingHeight) {
|
|
5864
|
+
/**
|
|
5865
|
+
* DRAGGABLE_HEIGHT is subtracted here due to an uninteded scrolling effect
|
|
5866
|
+
* when dragging a new component onto the canvas
|
|
5867
|
+
*
|
|
5868
|
+
* The DRAGGABLE_HEIGHT is then added as margin bottom to offset this value
|
|
5869
|
+
* so that visually there is no difference to the user.
|
|
5870
|
+
*/
|
|
5871
|
+
setContainerStyles({
|
|
5872
|
+
minHeight: `${window.innerHeight - DRAGGABLE_HEIGHT}px`,
|
|
5873
|
+
});
|
|
5874
|
+
return;
|
|
5875
|
+
}
|
|
5876
|
+
setContainerStyles({
|
|
5877
|
+
minHeight: `${window.innerHeight - siblingHeight}px`,
|
|
5878
|
+
});
|
|
5879
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5880
|
+
}, [containerRef.current]);
|
|
5881
|
+
useEffect(() => {
|
|
5882
|
+
if (onChange)
|
|
5883
|
+
onChange(tree);
|
|
5884
|
+
}, [tree, onChange]);
|
|
5885
|
+
useEffect(() => {
|
|
5886
|
+
window.addEventListener('mouseover', handleMouseOver);
|
|
5887
|
+
return () => {
|
|
5888
|
+
window.removeEventListener('mouseover', handleMouseOver);
|
|
5889
|
+
};
|
|
5890
|
+
}, [handleMouseOver]);
|
|
5891
|
+
useEffect(() => {
|
|
5892
|
+
document.addEventListener('click', handleClickOutside);
|
|
5893
|
+
return () => {
|
|
5894
|
+
document.removeEventListener('click', handleClickOutside);
|
|
5895
|
+
};
|
|
5896
|
+
}, [handleClickOutside]);
|
|
5897
|
+
useEffect(() => {
|
|
5898
|
+
handleResizeCanvas();
|
|
5899
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5900
|
+
}, [containerRef.current]);
|
|
5901
|
+
return (React.createElement(DNDProvider, null,
|
|
5902
|
+
dragItem && React.createElement(DraggableContainer, { id: dragItem }),
|
|
5903
|
+
React.createElement("div", { "data-ctfl-root": true, className: styles$3.container, ref: containerRef, style: containerStyles },
|
|
5904
|
+
userIsDragging && React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitbox }),
|
|
5905
|
+
React.createElement(Dropzone, { zoneId: ROOT_ID, resolveDesignValue: resolveDesignValue }),
|
|
5906
|
+
userIsDragging && (React.createElement(React.Fragment, null,
|
|
5907
|
+
React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitboxLower }),
|
|
5908
|
+
React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.canvasBottomSpacer })))),
|
|
5909
|
+
React.createElement("div", { "data-ctfl-hitboxes": true })));
|
|
4076
5910
|
};
|
|
4077
5911
|
|
|
4078
5912
|
const useInitializeEditor = () => {
|
|
@@ -4116,11 +5950,38 @@ const useInitializeEditor = () => {
|
|
|
4116
5950
|
const VisualEditorRoot = ({ experience }) => {
|
|
4117
5951
|
const initialized = useInitializeEditor();
|
|
4118
5952
|
const setHyperLinkPattern = useEditorStore((state) => state.setHyperLinkPattern);
|
|
5953
|
+
const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
|
|
5954
|
+
const setHoveringZone = useZoneStore((state) => state.setHoveringZone);
|
|
4119
5955
|
useEffect(() => {
|
|
4120
5956
|
if (experience?.hyperlinkPattern) {
|
|
4121
5957
|
setHyperLinkPattern(experience.hyperlinkPattern);
|
|
4122
5958
|
}
|
|
4123
5959
|
}, [experience?.hyperlinkPattern, setHyperLinkPattern]);
|
|
5960
|
+
useEffect(() => {
|
|
5961
|
+
const onMouseMove = (e) => {
|
|
5962
|
+
setMousePosition(e.clientX, e.clientY);
|
|
5963
|
+
const target = e.target;
|
|
5964
|
+
const zoneId = target.closest(`[${CTFL_ZONE_ID}]`)?.getAttribute(CTFL_ZONE_ID);
|
|
5965
|
+
if (zoneId) {
|
|
5966
|
+
setHoveringZone(zoneId);
|
|
5967
|
+
}
|
|
5968
|
+
if (!SimulateDnD$1.isDragging) {
|
|
5969
|
+
return;
|
|
5970
|
+
}
|
|
5971
|
+
if (target.id === NEW_COMPONENT_ID) {
|
|
5972
|
+
return;
|
|
5973
|
+
}
|
|
5974
|
+
SimulateDnD$1.updateDrag(e.clientX, e.clientY);
|
|
5975
|
+
sendMessage(OUTGOING_EVENTS.MouseMove, {
|
|
5976
|
+
clientX: e.pageX - window.scrollX,
|
|
5977
|
+
clientY: e.pageY - window.scrollY,
|
|
5978
|
+
});
|
|
5979
|
+
};
|
|
5980
|
+
document.addEventListener('mousemove', onMouseMove);
|
|
5981
|
+
return () => {
|
|
5982
|
+
document.removeEventListener('mousemove', onMouseMove);
|
|
5983
|
+
};
|
|
5984
|
+
}, [setHoveringZone, setMousePosition]);
|
|
4124
5985
|
if (!initialized)
|
|
4125
5986
|
return null;
|
|
4126
5987
|
return React.createElement(RootRenderer, null);
|