@contentful/experiences-visual-editor-react 1.40.0-alpha-20250604T0813-1f6e699.0 → 1.40.0-dev-20250604T0933-fc115de.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 +2284 -622
- package/dist/index.js.map +1 -1
- package/dist/renderApp.js +24034 -12699
- package/dist/renderApp.js.map +1 -1
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -1,269 +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, debounce } 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
|
-
CanvasGeometryUpdated: 'canvasGeometryUpdated',
|
|
232
|
-
};
|
|
233
18
|
const INCOMING_EVENTS$1 = {
|
|
234
19
|
RequestEditorMode: 'requestEditorMode',
|
|
235
20
|
RequestReadOnlyMode: 'requestReadOnlyMode',
|
|
236
21
|
ExperienceUpdated: 'componentTreeUpdated',
|
|
237
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
238
22
|
ComponentDraggingChanged: 'componentDraggingChanged',
|
|
239
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
240
23
|
ComponentDragCanceled: 'componentDragCanceled',
|
|
241
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
242
24
|
ComponentDragStarted: 'componentDragStarted',
|
|
243
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
244
25
|
ComponentDragEnded: 'componentDragEnded',
|
|
245
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
246
26
|
ComponentMoveEnded: 'componentMoveEnded',
|
|
247
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
248
27
|
CanvasResized: 'canvasResized',
|
|
249
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
250
28
|
SelectComponent: 'selectComponent',
|
|
251
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
252
29
|
HoverComponent: 'hoverComponent',
|
|
253
30
|
UpdatedEntity: 'updatedEntity',
|
|
254
31
|
AssembliesAdded: 'assembliesAdded',
|
|
255
32
|
AssembliesRegistered: 'assembliesRegistered',
|
|
256
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
257
33
|
MouseMove: 'mouseMove',
|
|
258
34
|
RequestedEntities: 'REQUESTED_ENTITIES',
|
|
259
35
|
};
|
|
260
|
-
const INTERNAL_EVENTS = {
|
|
261
|
-
ComponentsRegistered: 'cfComponentsRegistered',
|
|
262
|
-
VisualEditorInitialize: 'cfVisualEditorInitialize',
|
|
263
|
-
};
|
|
264
|
-
const VISUAL_EDITOR_EVENTS = {
|
|
265
|
-
Ready: 'cfVisualEditorReady',
|
|
266
|
-
};
|
|
267
36
|
/**
|
|
268
37
|
* These modes are ONLY intended to be internally used within the context of
|
|
269
38
|
* editing an experience inside of Contentful Studio. i.e. these modes
|
|
@@ -275,56 +44,7 @@ var StudioCanvasMode$3;
|
|
|
275
44
|
StudioCanvasMode["EDITOR"] = "editorMode";
|
|
276
45
|
StudioCanvasMode["NONE"] = "none";
|
|
277
46
|
})(StudioCanvasMode$3 || (StudioCanvasMode$3 = {}));
|
|
278
|
-
const
|
|
279
|
-
const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
|
|
280
|
-
const EMPTY_CONTAINER_HEIGHT$1 = '80px';
|
|
281
|
-
const HYPERLINK_DEFAULT_PATTERN = `/{locale}/{entry.fields.slug}/`;
|
|
282
|
-
var PostMessageMethods$3;
|
|
283
|
-
(function (PostMessageMethods) {
|
|
284
|
-
PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
|
|
285
|
-
PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
|
|
286
|
-
})(PostMessageMethods$3 || (PostMessageMethods$3 = {}));
|
|
287
|
-
|
|
288
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
289
|
-
const INCOMING_EVENTS = {
|
|
290
|
-
RequestEditorMode: 'requestEditorMode',
|
|
291
|
-
RequestReadOnlyMode: 'requestReadOnlyMode',
|
|
292
|
-
ExperienceUpdated: 'componentTreeUpdated',
|
|
293
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
294
|
-
ComponentDraggingChanged: 'componentDraggingChanged',
|
|
295
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
296
|
-
ComponentDragCanceled: 'componentDragCanceled',
|
|
297
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
298
|
-
ComponentDragStarted: 'componentDragStarted',
|
|
299
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
300
|
-
ComponentDragEnded: 'componentDragEnded',
|
|
301
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
302
|
-
ComponentMoveEnded: 'componentMoveEnded',
|
|
303
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
304
|
-
CanvasResized: 'canvasResized',
|
|
305
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
306
|
-
SelectComponent: 'selectComponent',
|
|
307
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
308
|
-
HoverComponent: 'hoverComponent',
|
|
309
|
-
UpdatedEntity: 'updatedEntity',
|
|
310
|
-
AssembliesAdded: 'assembliesAdded',
|
|
311
|
-
AssembliesRegistered: 'assembliesRegistered',
|
|
312
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
313
|
-
MouseMove: 'mouseMove',
|
|
314
|
-
RequestedEntities: 'REQUESTED_ENTITIES',
|
|
315
|
-
};
|
|
316
|
-
/**
|
|
317
|
-
* These modes are ONLY intended to be internally used within the context of
|
|
318
|
-
* editing an experience inside of Contentful Studio. i.e. these modes
|
|
319
|
-
* intentionally do not include preview/delivery modes.
|
|
320
|
-
*/
|
|
321
|
-
var StudioCanvasMode$2;
|
|
322
|
-
(function (StudioCanvasMode) {
|
|
323
|
-
StudioCanvasMode["READ_ONLY"] = "readOnlyMode";
|
|
324
|
-
StudioCanvasMode["EDITOR"] = "editorMode";
|
|
325
|
-
StudioCanvasMode["NONE"] = "none";
|
|
326
|
-
})(StudioCanvasMode$2 || (StudioCanvasMode$2 = {}));
|
|
327
|
-
const CONTENTFUL_COMPONENTS$1 = {
|
|
47
|
+
const CONTENTFUL_COMPONENTS$2 = {
|
|
328
48
|
section: {
|
|
329
49
|
id: 'contentful-section',
|
|
330
50
|
name: 'Section',
|
|
@@ -370,6 +90,9 @@ const CONTENTFUL_COMPONENTS$1 = {
|
|
|
370
90
|
name: 'Carousel',
|
|
371
91
|
},
|
|
372
92
|
};
|
|
93
|
+
const ASSEMBLY_NODE_TYPE$1 = 'assembly';
|
|
94
|
+
const ASSEMBLY_DEFAULT_CATEGORY$1 = 'Assemblies';
|
|
95
|
+
const ASSEMBLY_BLOCK_NODE_TYPE$1 = 'assemblyBlock';
|
|
373
96
|
const CF_STYLE_ATTRIBUTES = [
|
|
374
97
|
'cfVisibility',
|
|
375
98
|
'cfHorizontalAlignment',
|
|
@@ -412,24 +135,30 @@ const CF_STYLE_ATTRIBUTES = [
|
|
|
412
135
|
'cfBackgroundImageAlignmentVertical',
|
|
413
136
|
'cfBackgroundImageAlignmentHorizontal',
|
|
414
137
|
];
|
|
415
|
-
const EMPTY_CONTAINER_HEIGHT = '80px';
|
|
138
|
+
const EMPTY_CONTAINER_HEIGHT$1 = '80px';
|
|
416
139
|
const DEFAULT_IMAGE_WIDTH = '500px';
|
|
417
|
-
var PostMessageMethods$
|
|
140
|
+
var PostMessageMethods$3;
|
|
418
141
|
(function (PostMessageMethods) {
|
|
419
142
|
PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
|
|
420
143
|
PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
|
|
421
|
-
})(PostMessageMethods$
|
|
144
|
+
})(PostMessageMethods$3 || (PostMessageMethods$3 = {}));
|
|
422
145
|
const SUPPORTED_IMAGE_FORMATS = ['jpg', 'png', 'webp', 'gif', 'avif'];
|
|
423
146
|
|
|
424
147
|
const structureComponentIds = new Set([
|
|
425
|
-
CONTENTFUL_COMPONENTS$
|
|
426
|
-
CONTENTFUL_COMPONENTS$
|
|
427
|
-
CONTENTFUL_COMPONENTS$
|
|
428
|
-
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,
|
|
429
152
|
]);
|
|
430
|
-
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 ?? '');
|
|
431
156
|
const isContentfulStructureComponent = (componentId) => structureComponentIds.has((componentId ?? ''));
|
|
432
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;
|
|
433
162
|
const isStructureWithRelativeHeight = (componentId, height) => {
|
|
434
163
|
return isContentfulStructureComponent(componentId) && !height?.toString().endsWith('px');
|
|
435
164
|
};
|
|
@@ -451,6 +180,10 @@ const builtInStyles = {
|
|
|
451
180
|
value: 'end',
|
|
452
181
|
displayName: 'Align right',
|
|
453
182
|
},
|
|
183
|
+
{
|
|
184
|
+
value: 'stretch',
|
|
185
|
+
displayName: 'Stretch',
|
|
186
|
+
},
|
|
454
187
|
],
|
|
455
188
|
},
|
|
456
189
|
type: 'Text',
|
|
@@ -474,6 +207,10 @@ const builtInStyles = {
|
|
|
474
207
|
value: 'end',
|
|
475
208
|
displayName: 'Align bottom',
|
|
476
209
|
},
|
|
210
|
+
{
|
|
211
|
+
value: 'stretch',
|
|
212
|
+
displayName: 'Stretch',
|
|
213
|
+
},
|
|
477
214
|
],
|
|
478
215
|
},
|
|
479
216
|
type: 'Text',
|
|
@@ -1881,7 +1618,7 @@ const calculateNodeDefaultHeight = ({ blockId, children, value, }) => {
|
|
|
1881
1618
|
if (children.length) {
|
|
1882
1619
|
return '100%';
|
|
1883
1620
|
}
|
|
1884
|
-
return EMPTY_CONTAINER_HEIGHT;
|
|
1621
|
+
return EMPTY_CONTAINER_HEIGHT$1;
|
|
1885
1622
|
};
|
|
1886
1623
|
|
|
1887
1624
|
function getOptimizedImageUrl(url, width, quality, format) {
|
|
@@ -2312,10 +2049,10 @@ const tryParseMessage = (event) => {
|
|
|
2312
2049
|
throw new ParseError(`Field eventData.source must be equal to 'composability-app', instead of '${eventData.source}'`);
|
|
2313
2050
|
}
|
|
2314
2051
|
// check eventData.eventType
|
|
2315
|
-
const supportedEventTypes = Object.values(INCOMING_EVENTS);
|
|
2052
|
+
const supportedEventTypes = Object.values(INCOMING_EVENTS$1);
|
|
2316
2053
|
if (!supportedEventTypes.includes(eventData.eventType)) {
|
|
2317
2054
|
// Expected message: This message is handled in the EntityStore to store fetched entities
|
|
2318
|
-
if (eventData.eventType !== PostMessageMethods$
|
|
2055
|
+
if (eventData.eventType !== PostMessageMethods$3.REQUESTED_ENTITIES) {
|
|
2319
2056
|
throw new ParseError(`Field eventData.eventType must be one of the supported values: [${supportedEventTypes.join(', ')}]`);
|
|
2320
2057
|
}
|
|
2321
2058
|
}
|
|
@@ -2580,7 +2317,7 @@ class EditorEntityStore extends EntityStoreBase {
|
|
|
2580
2317
|
}
|
|
2581
2318
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
2582
2319
|
const newPromise = new Promise((resolve, reject) => {
|
|
2583
|
-
const unsubscribe = this.subscribe(PostMessageMethods$
|
|
2320
|
+
const unsubscribe = this.subscribe(PostMessageMethods$3.REQUESTED_ENTITIES, (message) => {
|
|
2584
2321
|
const messageIds = [
|
|
2585
2322
|
...message.entities.map((entity) => entity.sys.id),
|
|
2586
2323
|
...(message.missingEntityIds ?? []),
|
|
@@ -2602,7 +2339,7 @@ class EditorEntityStore extends EntityStoreBase {
|
|
|
2602
2339
|
ids.forEach((id) => this.cleanupPromise(id));
|
|
2603
2340
|
unsubscribe();
|
|
2604
2341
|
}, this.timeoutDuration);
|
|
2605
|
-
this.sendMessage(PostMessageMethods$
|
|
2342
|
+
this.sendMessage(PostMessageMethods$3.REQUEST_ENTITIES, {
|
|
2606
2343
|
entityIds: missing,
|
|
2607
2344
|
entityType: type,
|
|
2608
2345
|
locale: this.locale,
|
|
@@ -2704,74 +2441,567 @@ class EditorModeEntityStore extends EditorEntityStore {
|
|
|
2704
2441
|
const { missing: missingAssetIds } = this.getEntitiesFromMap('Asset', uniqueAssetIds);
|
|
2705
2442
|
return { missingEntryIds, missingAssetIds };
|
|
2706
2443
|
}
|
|
2707
|
-
getValue(entityLinkOrEntity, path) {
|
|
2708
|
-
const entity = this.getEntryOrAsset(entityLinkOrEntity, path.join('/'));
|
|
2709
|
-
if (!entity) {
|
|
2710
|
-
return;
|
|
2711
|
-
}
|
|
2712
|
-
const fieldValue = get(entity, path);
|
|
2713
|
-
return transformAssetFileToUrl(fieldValue);
|
|
2444
|
+
getValue(entityLinkOrEntity, path) {
|
|
2445
|
+
const entity = this.getEntryOrAsset(entityLinkOrEntity, path.join('/'));
|
|
2446
|
+
if (!entity) {
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2449
|
+
const fieldValue = get(entity, path);
|
|
2450
|
+
return transformAssetFileToUrl(fieldValue);
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
var VisualEditorMode$1;
|
|
2455
|
+
(function (VisualEditorMode) {
|
|
2456
|
+
VisualEditorMode["LazyLoad"] = "lazyLoad";
|
|
2457
|
+
VisualEditorMode["InjectScript"] = "injectScript";
|
|
2458
|
+
})(VisualEditorMode$1 || (VisualEditorMode$1 = {}));
|
|
2459
|
+
|
|
2460
|
+
class DeepReference {
|
|
2461
|
+
constructor({ path, dataSource }) {
|
|
2462
|
+
const { key, field, referentField } = parseDataSourcePathWithL1DeepBindings(path);
|
|
2463
|
+
this.originalPath = path;
|
|
2464
|
+
this.entityId = dataSource[key].sys.id;
|
|
2465
|
+
this.entityLink = dataSource[key];
|
|
2466
|
+
this.field = field;
|
|
2467
|
+
this.referentField = referentField;
|
|
2468
|
+
}
|
|
2469
|
+
get headEntityId() {
|
|
2470
|
+
return this.entityId;
|
|
2471
|
+
}
|
|
2472
|
+
/**
|
|
2473
|
+
* Extracts referent from the path, using EntityStore as source of
|
|
2474
|
+
* entities during the resolution path.
|
|
2475
|
+
*/
|
|
2476
|
+
extractReferent(entityStore) {
|
|
2477
|
+
const headEntity = entityStore.getEntityFromLink(this.entityLink);
|
|
2478
|
+
const maybeReferentLink = headEntity?.fields[this.field];
|
|
2479
|
+
if (undefined === maybeReferentLink) {
|
|
2480
|
+
// field references nothing (or even field doesn't exist)
|
|
2481
|
+
return undefined;
|
|
2482
|
+
}
|
|
2483
|
+
if (!isLink(maybeReferentLink)) {
|
|
2484
|
+
// Scenario of "impostor referent", where one of the deepPath's segments is not a Link but some other type
|
|
2485
|
+
// Under normal circumstance we expect field to be a Link, but it could be an "impostor"
|
|
2486
|
+
// eg. `Text` or `Number` or anything like that; could be due to CT changes or manual path creation via CMA
|
|
2487
|
+
return undefined;
|
|
2488
|
+
}
|
|
2489
|
+
return maybeReferentLink;
|
|
2490
|
+
}
|
|
2491
|
+
static from(opt) {
|
|
2492
|
+
return new DeepReference(opt);
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2495
|
+
function gatherDeepReferencesFromTree(startingNode, dataSource) {
|
|
2496
|
+
const deepReferences = [];
|
|
2497
|
+
treeVisit(startingNode, (node) => {
|
|
2498
|
+
if (!node.data.props)
|
|
2499
|
+
return;
|
|
2500
|
+
for (const [, variableMapping] of Object.entries(node.data.props)) {
|
|
2501
|
+
if (variableMapping.type !== 'BoundValue')
|
|
2502
|
+
continue;
|
|
2503
|
+
if (!isDeepPath(variableMapping.path))
|
|
2504
|
+
continue;
|
|
2505
|
+
deepReferences.push(DeepReference.from({
|
|
2506
|
+
path: variableMapping.path,
|
|
2507
|
+
dataSource,
|
|
2508
|
+
}));
|
|
2509
|
+
}
|
|
2510
|
+
});
|
|
2511
|
+
return deepReferences;
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2514
|
+
const useDraggedItemStore = create((set) => ({
|
|
2515
|
+
draggedItem: undefined,
|
|
2516
|
+
hoveredComponentId: undefined,
|
|
2517
|
+
domRect: undefined,
|
|
2518
|
+
componentId: '',
|
|
2519
|
+
isDraggingOnCanvas: false,
|
|
2520
|
+
onBeforeCaptureId: '',
|
|
2521
|
+
mouseX: 0,
|
|
2522
|
+
mouseY: 0,
|
|
2523
|
+
scrollY: 0,
|
|
2524
|
+
setComponentId(id) {
|
|
2525
|
+
set({ componentId: id });
|
|
2526
|
+
},
|
|
2527
|
+
setHoveredComponentId(id) {
|
|
2528
|
+
set({ hoveredComponentId: id });
|
|
2529
|
+
},
|
|
2530
|
+
updateItem: (item) => {
|
|
2531
|
+
set({ draggedItem: item });
|
|
2532
|
+
},
|
|
2533
|
+
setDraggingOnCanvas: (isDraggingOnCanvas) => {
|
|
2534
|
+
set({ isDraggingOnCanvas });
|
|
2535
|
+
},
|
|
2536
|
+
setOnBeforeCaptureId: (onBeforeCaptureId) => {
|
|
2537
|
+
set({ onBeforeCaptureId });
|
|
2538
|
+
},
|
|
2539
|
+
setMousePosition(x, y) {
|
|
2540
|
+
set({ mouseX: x, mouseY: y });
|
|
2541
|
+
},
|
|
2542
|
+
setDomRect(domRect) {
|
|
2543
|
+
set({ domRect });
|
|
2544
|
+
},
|
|
2545
|
+
setScrollY(y) {
|
|
2546
|
+
set({ scrollY: y });
|
|
2547
|
+
},
|
|
2548
|
+
}));
|
|
2549
|
+
|
|
2550
|
+
const SCROLL_STATES = {
|
|
2551
|
+
Start: 'scrollStart',
|
|
2552
|
+
IsScrolling: 'isScrolling',
|
|
2553
|
+
End: 'scrollEnd',
|
|
2554
|
+
};
|
|
2555
|
+
const OUTGOING_EVENTS = {
|
|
2556
|
+
Connected: 'connected',
|
|
2557
|
+
DesignTokens: 'registerDesignTokens',
|
|
2558
|
+
RegisteredBreakpoints: 'registeredBreakpoints',
|
|
2559
|
+
MouseMove: 'mouseMove',
|
|
2560
|
+
NewHoveredElement: 'newHoveredElement',
|
|
2561
|
+
ComponentSelected: 'componentSelected',
|
|
2562
|
+
RegisteredComponents: 'registeredComponents',
|
|
2563
|
+
RequestComponentTreeUpdate: 'requestComponentTreeUpdate',
|
|
2564
|
+
ComponentDragCanceled: 'componentDragCanceled',
|
|
2565
|
+
ComponentDropped: 'componentDropped',
|
|
2566
|
+
ComponentMoved: 'componentMoved',
|
|
2567
|
+
CanvasReload: 'canvasReload',
|
|
2568
|
+
UpdateSelectedComponentCoordinates: 'updateSelectedComponentCoordinates',
|
|
2569
|
+
CanvasScroll: 'canvasScrolling',
|
|
2570
|
+
CanvasError: 'canvasError',
|
|
2571
|
+
ComponentMoveStarted: 'componentMoveStarted',
|
|
2572
|
+
ComponentMoveEnded: 'componentMoveEnded',
|
|
2573
|
+
OutsideCanvasClick: 'outsideCanvasClick',
|
|
2574
|
+
SDKFeatures: 'sdkFeatures',
|
|
2575
|
+
RequestEntities: 'REQUEST_ENTITIES',
|
|
2576
|
+
};
|
|
2577
|
+
const INCOMING_EVENTS = {
|
|
2578
|
+
RequestEditorMode: 'requestEditorMode',
|
|
2579
|
+
RequestReadOnlyMode: 'requestReadOnlyMode',
|
|
2580
|
+
ExperienceUpdated: 'componentTreeUpdated',
|
|
2581
|
+
ComponentDraggingChanged: 'componentDraggingChanged',
|
|
2582
|
+
ComponentDragCanceled: 'componentDragCanceled',
|
|
2583
|
+
ComponentDragStarted: 'componentDragStarted',
|
|
2584
|
+
ComponentDragEnded: 'componentDragEnded',
|
|
2585
|
+
ComponentMoveEnded: 'componentMoveEnded',
|
|
2586
|
+
CanvasResized: 'canvasResized',
|
|
2587
|
+
SelectComponent: 'selectComponent',
|
|
2588
|
+
HoverComponent: 'hoverComponent',
|
|
2589
|
+
UpdatedEntity: 'updatedEntity',
|
|
2590
|
+
AssembliesAdded: 'assembliesAdded',
|
|
2591
|
+
AssembliesRegistered: 'assembliesRegistered',
|
|
2592
|
+
MouseMove: 'mouseMove',
|
|
2593
|
+
RequestedEntities: 'REQUESTED_ENTITIES',
|
|
2594
|
+
};
|
|
2595
|
+
const INTERNAL_EVENTS = {
|
|
2596
|
+
ComponentsRegistered: 'cfComponentsRegistered',
|
|
2597
|
+
VisualEditorInitialize: 'cfVisualEditorInitialize',
|
|
2598
|
+
};
|
|
2599
|
+
const VISUAL_EDITOR_EVENTS = {
|
|
2600
|
+
Ready: 'cfVisualEditorReady',
|
|
2601
|
+
};
|
|
2602
|
+
/**
|
|
2603
|
+
* These modes are ONLY intended to be internally used within the context of
|
|
2604
|
+
* editing an experience inside of Contentful Studio. i.e. these modes
|
|
2605
|
+
* intentionally do not include preview/delivery modes.
|
|
2606
|
+
*/
|
|
2607
|
+
var StudioCanvasMode$2;
|
|
2608
|
+
(function (StudioCanvasMode) {
|
|
2609
|
+
StudioCanvasMode["READ_ONLY"] = "readOnlyMode";
|
|
2610
|
+
StudioCanvasMode["EDITOR"] = "editorMode";
|
|
2611
|
+
StudioCanvasMode["NONE"] = "none";
|
|
2612
|
+
})(StudioCanvasMode$2 || (StudioCanvasMode$2 = {}));
|
|
2613
|
+
const CONTENTFUL_COMPONENTS$1 = {
|
|
2614
|
+
section: {
|
|
2615
|
+
id: 'contentful-section',
|
|
2616
|
+
name: 'Section',
|
|
2617
|
+
},
|
|
2618
|
+
container: {
|
|
2619
|
+
id: 'contentful-container',
|
|
2620
|
+
name: 'Container',
|
|
2621
|
+
},
|
|
2622
|
+
columns: {
|
|
2623
|
+
id: 'contentful-columns',
|
|
2624
|
+
name: 'Columns',
|
|
2625
|
+
},
|
|
2626
|
+
singleColumn: {
|
|
2627
|
+
id: 'contentful-single-column',
|
|
2628
|
+
name: 'Column',
|
|
2629
|
+
},
|
|
2630
|
+
button: {
|
|
2631
|
+
id: 'contentful-button',
|
|
2632
|
+
name: 'Button',
|
|
2633
|
+
},
|
|
2634
|
+
heading: {
|
|
2635
|
+
id: 'contentful-heading',
|
|
2636
|
+
name: 'Heading',
|
|
2637
|
+
},
|
|
2638
|
+
image: {
|
|
2639
|
+
id: 'contentful-image',
|
|
2640
|
+
name: 'Image',
|
|
2641
|
+
},
|
|
2642
|
+
richText: {
|
|
2643
|
+
id: 'contentful-richText',
|
|
2644
|
+
name: 'Rich Text',
|
|
2645
|
+
},
|
|
2646
|
+
text: {
|
|
2647
|
+
id: 'contentful-text',
|
|
2648
|
+
name: 'Text',
|
|
2649
|
+
},
|
|
2650
|
+
divider: {
|
|
2651
|
+
id: 'contentful-divider',
|
|
2652
|
+
name: 'Divider',
|
|
2653
|
+
},
|
|
2654
|
+
carousel: {
|
|
2655
|
+
id: 'contentful-carousel',
|
|
2656
|
+
name: 'Carousel',
|
|
2657
|
+
},
|
|
2658
|
+
};
|
|
2659
|
+
const ASSEMBLY_NODE_TYPE = 'assembly';
|
|
2660
|
+
const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
|
|
2661
|
+
const ASSEMBLY_BLOCK_NODE_TYPE = 'assemblyBlock';
|
|
2662
|
+
const ASSEMBLY_NODE_TYPES = [ASSEMBLY_NODE_TYPE, ASSEMBLY_BLOCK_NODE_TYPE];
|
|
2663
|
+
const EMPTY_CONTAINER_HEIGHT = '80px';
|
|
2664
|
+
const HYPERLINK_DEFAULT_PATTERN = `/{locale}/{entry.fields.slug}/`;
|
|
2665
|
+
var PostMessageMethods$2;
|
|
2666
|
+
(function (PostMessageMethods) {
|
|
2667
|
+
PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
|
|
2668
|
+
PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
|
|
2669
|
+
})(PostMessageMethods$2 || (PostMessageMethods$2 = {}));
|
|
2670
|
+
|
|
2671
|
+
const DRAGGABLE_HEIGHT = 30;
|
|
2672
|
+
const DRAGGABLE_WIDTH = 50;
|
|
2673
|
+
const DRAG_PADDING = 4;
|
|
2674
|
+
const ROOT_ID = 'root';
|
|
2675
|
+
const COMPONENT_LIST_ID = 'component-list';
|
|
2676
|
+
const NEW_COMPONENT_ID = 'ctfl-new-draggable';
|
|
2677
|
+
const CTFL_ZONE_ID = 'data-ctfl-zone-id';
|
|
2678
|
+
const CTFL_DRAGGING_ELEMENT = 'data-ctfl-dragging-element';
|
|
2679
|
+
const HITBOX = {
|
|
2680
|
+
WIDTH: 70,
|
|
2681
|
+
HEIGHT: 20,
|
|
2682
|
+
INITIAL_OFFSET: 10,
|
|
2683
|
+
OFFSET_INCREMENT: 8,
|
|
2684
|
+
MIN_HEIGHT: 45,
|
|
2685
|
+
MIN_DEPTH_HEIGHT: 20,
|
|
2686
|
+
DEEP_ZONE: 5,
|
|
2687
|
+
};
|
|
2688
|
+
var TreeAction;
|
|
2689
|
+
(function (TreeAction) {
|
|
2690
|
+
TreeAction[TreeAction["REMOVE_NODE"] = 0] = "REMOVE_NODE";
|
|
2691
|
+
TreeAction[TreeAction["ADD_NODE"] = 1] = "ADD_NODE";
|
|
2692
|
+
TreeAction[TreeAction["MOVE_NODE"] = 2] = "MOVE_NODE";
|
|
2693
|
+
TreeAction[TreeAction["UPDATE_NODE"] = 3] = "UPDATE_NODE";
|
|
2694
|
+
TreeAction[TreeAction["REORDER_NODE"] = 4] = "REORDER_NODE";
|
|
2695
|
+
TreeAction[TreeAction["REPLACE_NODE"] = 5] = "REPLACE_NODE";
|
|
2696
|
+
})(TreeAction || (TreeAction = {}));
|
|
2697
|
+
var HitboxDirection;
|
|
2698
|
+
(function (HitboxDirection) {
|
|
2699
|
+
HitboxDirection[HitboxDirection["TOP"] = 0] = "TOP";
|
|
2700
|
+
HitboxDirection[HitboxDirection["LEFT"] = 1] = "LEFT";
|
|
2701
|
+
HitboxDirection[HitboxDirection["RIGHT"] = 2] = "RIGHT";
|
|
2702
|
+
HitboxDirection[HitboxDirection["BOTTOM"] = 3] = "BOTTOM";
|
|
2703
|
+
HitboxDirection[HitboxDirection["SELF_VERTICAL"] = 4] = "SELF_VERTICAL";
|
|
2704
|
+
HitboxDirection[HitboxDirection["SELF_HORIZONTAL"] = 5] = "SELF_HORIZONTAL";
|
|
2705
|
+
})(HitboxDirection || (HitboxDirection = {}));
|
|
2706
|
+
var DraggablePosition;
|
|
2707
|
+
(function (DraggablePosition) {
|
|
2708
|
+
DraggablePosition[DraggablePosition["CENTERED"] = 0] = "CENTERED";
|
|
2709
|
+
DraggablePosition[DraggablePosition["MOUSE_POSITION"] = 1] = "MOUSE_POSITION";
|
|
2710
|
+
})(DraggablePosition || (DraggablePosition = {}));
|
|
2711
|
+
|
|
2712
|
+
function useDraggablePosition({ draggableId, draggableRef, position }) {
|
|
2713
|
+
const isDraggingOnCanvas = useDraggedItemStore((state) => state.isDraggingOnCanvas);
|
|
2714
|
+
const draggingId = useDraggedItemStore((state) => state.onBeforeCaptureId);
|
|
2715
|
+
const preDragDomRect = useDraggedItemStore((state) => state.domRect);
|
|
2716
|
+
useEffect(() => {
|
|
2717
|
+
const el = draggableRef?.current ??
|
|
2718
|
+
document.querySelector(`[${CTFL_DRAGGING_ELEMENT}][data-cf-node-id="${draggableId}"]`);
|
|
2719
|
+
if (!isDraggingOnCanvas || draggingId !== draggableId || !el) {
|
|
2720
|
+
return;
|
|
2721
|
+
}
|
|
2722
|
+
const isCentered = position === DraggablePosition.CENTERED || !preDragDomRect;
|
|
2723
|
+
const domRect = isCentered ? el.getBoundingClientRect() : preDragDomRect;
|
|
2724
|
+
const { mouseX, mouseY } = useDraggedItemStore.getState();
|
|
2725
|
+
const top = isCentered ? mouseY - domRect.height / 2 : domRect.top;
|
|
2726
|
+
const left = isCentered ? mouseX - domRect.width / 2 : domRect.left;
|
|
2727
|
+
el.style.position = 'fixed';
|
|
2728
|
+
el.style.left = `${left}px`;
|
|
2729
|
+
el.style.top = `${top}px`;
|
|
2730
|
+
el.style.width = `${domRect.width}px`;
|
|
2731
|
+
el.style.height = `${domRect.height}px`;
|
|
2732
|
+
}, [draggableRef, draggableId, isDraggingOnCanvas, draggingId, position, preDragDomRect]);
|
|
2733
|
+
}
|
|
2734
|
+
|
|
2735
|
+
function getStyle$2(style, snapshot) {
|
|
2736
|
+
if (!snapshot.isDropAnimating) {
|
|
2737
|
+
return style;
|
|
2738
|
+
}
|
|
2739
|
+
return {
|
|
2740
|
+
...style,
|
|
2741
|
+
// cannot be 0, but make it super tiny
|
|
2742
|
+
transitionDuration: `0.001s`,
|
|
2743
|
+
};
|
|
2744
|
+
}
|
|
2745
|
+
const DraggableContainer = ({ id }) => {
|
|
2746
|
+
const ref = useRef(null);
|
|
2747
|
+
useDraggablePosition({
|
|
2748
|
+
draggableId: id,
|
|
2749
|
+
draggableRef: ref,
|
|
2750
|
+
position: DraggablePosition.CENTERED,
|
|
2751
|
+
});
|
|
2752
|
+
return (React.createElement("div", { id: COMPONENT_LIST_ID, style: {
|
|
2753
|
+
position: 'absolute',
|
|
2754
|
+
top: 0,
|
|
2755
|
+
left: 0,
|
|
2756
|
+
pointerEvents: 'none',
|
|
2757
|
+
zIndex: -1,
|
|
2758
|
+
} },
|
|
2759
|
+
React.createElement(Droppable, { droppableId: COMPONENT_LIST_ID, isDropDisabled: true }, (provided) => (React.createElement("div", { ...provided.droppableProps, ref: provided.innerRef },
|
|
2760
|
+
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) => {
|
|
2761
|
+
provided.innerRef(node);
|
|
2762
|
+
ref.current = node;
|
|
2763
|
+
}, ...provided.draggableProps, ...provided.dragHandleProps, style: {
|
|
2764
|
+
...getStyle$2(provided.draggableProps.style, snapshot),
|
|
2765
|
+
width: DRAGGABLE_WIDTH,
|
|
2766
|
+
height: DRAGGABLE_HEIGHT,
|
|
2767
|
+
pointerEvents: 'none',
|
|
2768
|
+
} }))),
|
|
2769
|
+
provided.placeholder)))));
|
|
2770
|
+
};
|
|
2771
|
+
|
|
2772
|
+
function getItemFromTree(id, node) {
|
|
2773
|
+
// Check if the current node's id matches the search id
|
|
2774
|
+
if (node.data.id === id) {
|
|
2775
|
+
return node;
|
|
2776
|
+
}
|
|
2777
|
+
// Recursively search through each child
|
|
2778
|
+
for (const child of node.children) {
|
|
2779
|
+
const foundNode = getItemFromTree(id, child);
|
|
2780
|
+
if (foundNode) {
|
|
2781
|
+
// Node found in children
|
|
2782
|
+
return foundNode;
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
// If the node is not found in this branch of the tree, return undefined
|
|
2786
|
+
return undefined;
|
|
2787
|
+
}
|
|
2788
|
+
function findDepthById(node, id, currentDepth = 1) {
|
|
2789
|
+
if (node.data.id === id) {
|
|
2790
|
+
return currentDepth;
|
|
2791
|
+
}
|
|
2792
|
+
// If the node has children, check each one
|
|
2793
|
+
for (const child of node.children) {
|
|
2794
|
+
const childDepth = findDepthById(child, id, currentDepth + 1);
|
|
2795
|
+
if (childDepth !== -1) {
|
|
2796
|
+
return childDepth; // Found the node in a child
|
|
2797
|
+
}
|
|
2798
|
+
}
|
|
2799
|
+
return -1; // Node not found in this branch
|
|
2800
|
+
}
|
|
2801
|
+
const getChildFromTree = (parentId, index, node) => {
|
|
2802
|
+
// Check if the current node's id matches the search id
|
|
2803
|
+
if (node.data.id === parentId) {
|
|
2804
|
+
return node.children[index];
|
|
2805
|
+
}
|
|
2806
|
+
// Recursively search through each child
|
|
2807
|
+
for (const child of node.children) {
|
|
2808
|
+
const foundNode = getChildFromTree(parentId, index, child);
|
|
2809
|
+
if (foundNode) {
|
|
2810
|
+
// Node found in children
|
|
2811
|
+
return foundNode;
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
// If the node is not found in this branch of the tree, return undefined
|
|
2815
|
+
return undefined;
|
|
2816
|
+
};
|
|
2817
|
+
const getItem = (selector, tree) => {
|
|
2818
|
+
return getItemFromTree(selector.id, {
|
|
2819
|
+
type: 'block',
|
|
2820
|
+
data: {
|
|
2821
|
+
id: ROOT_ID,
|
|
2822
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2823
|
+
},
|
|
2824
|
+
children: tree.root.children,
|
|
2825
|
+
});
|
|
2826
|
+
};
|
|
2827
|
+
const getItemDepthFromNode = (selector, node) => {
|
|
2828
|
+
return findDepthById(node, selector.id);
|
|
2829
|
+
};
|
|
2830
|
+
|
|
2831
|
+
function updateNode(nodeId, updatedNode, node) {
|
|
2832
|
+
if (node.data.id === nodeId) {
|
|
2833
|
+
node.data = updatedNode.data;
|
|
2834
|
+
return;
|
|
2835
|
+
}
|
|
2836
|
+
node.children.forEach((childNode) => updateNode(nodeId, updatedNode, childNode));
|
|
2837
|
+
}
|
|
2838
|
+
function replaceNode(indexToReplace, updatedNode, node) {
|
|
2839
|
+
if (node.data.id === updatedNode.parentId) {
|
|
2840
|
+
node.children = [
|
|
2841
|
+
...node.children.slice(0, indexToReplace),
|
|
2842
|
+
updatedNode,
|
|
2843
|
+
...node.children.slice(indexToReplace + 1),
|
|
2844
|
+
];
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
node.children.forEach((childNode) => replaceNode(indexToReplace, updatedNode, childNode));
|
|
2848
|
+
}
|
|
2849
|
+
function removeChildNode(indexToRemove, nodeId, parentNodeId, node) {
|
|
2850
|
+
if (node.data.id === parentNodeId) {
|
|
2851
|
+
const childIndex = node.children.findIndex((child) => child.data.id === nodeId);
|
|
2852
|
+
node.children.splice(childIndex === -1 ? indexToRemove : childIndex, 1);
|
|
2853
|
+
return;
|
|
2854
|
+
}
|
|
2855
|
+
node.children.forEach((childNode) => removeChildNode(indexToRemove, nodeId, parentNodeId, childNode));
|
|
2856
|
+
}
|
|
2857
|
+
function addChildNode(indexToAdd, parentNodeId, nodeToAdd, node) {
|
|
2858
|
+
if (node.data.id === parentNodeId) {
|
|
2859
|
+
node.children = [
|
|
2860
|
+
...node.children.slice(0, indexToAdd),
|
|
2861
|
+
nodeToAdd,
|
|
2862
|
+
...node.children.slice(indexToAdd),
|
|
2863
|
+
];
|
|
2864
|
+
return;
|
|
2865
|
+
}
|
|
2866
|
+
node.children.forEach((childNode) => addChildNode(indexToAdd, parentNodeId, nodeToAdd, childNode));
|
|
2867
|
+
}
|
|
2868
|
+
function reorderChildNode(oldIndex, newIndex, parentNodeId, node) {
|
|
2869
|
+
if (node.data.id === parentNodeId) {
|
|
2870
|
+
// Remove the child from the old position
|
|
2871
|
+
const [childToMove] = node.children.splice(oldIndex, 1);
|
|
2872
|
+
// Insert the child at the new position
|
|
2873
|
+
node.children.splice(newIndex, 0, childToMove);
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
node.children.forEach((childNode) => reorderChildNode(oldIndex, newIndex, parentNodeId, childNode));
|
|
2877
|
+
}
|
|
2878
|
+
function reparentChildNode(oldIndex, newIndex, sourceNodeId, destinationNodeId, node) {
|
|
2879
|
+
const nodeToMove = getChildFromTree(sourceNodeId, oldIndex, node);
|
|
2880
|
+
if (!nodeToMove) {
|
|
2881
|
+
return;
|
|
2714
2882
|
}
|
|
2883
|
+
removeChildNode(oldIndex, nodeToMove.data.id, sourceNodeId, node);
|
|
2884
|
+
addChildNode(newIndex, destinationNodeId, nodeToMove, node);
|
|
2715
2885
|
}
|
|
2716
2886
|
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
VisualEditorMode["InjectScript"] = "injectScript";
|
|
2721
|
-
})(VisualEditorMode$1 || (VisualEditorMode$1 = {}));
|
|
2722
|
-
|
|
2723
|
-
class DeepReference {
|
|
2724
|
-
constructor({ path, dataSource }) {
|
|
2725
|
-
const { key, field, referentField } = parseDataSourcePathWithL1DeepBindings(path);
|
|
2726
|
-
this.originalPath = path;
|
|
2727
|
-
this.entityId = dataSource[key].sys.id;
|
|
2728
|
-
this.entityLink = dataSource[key];
|
|
2729
|
-
this.field = field;
|
|
2730
|
-
this.referentField = referentField;
|
|
2731
|
-
}
|
|
2732
|
-
get headEntityId() {
|
|
2733
|
-
return this.entityId;
|
|
2887
|
+
function missingNodeAction({ index, nodeAdded, child, tree, parentNodeId, currentNode, }) {
|
|
2888
|
+
if (nodeAdded) {
|
|
2889
|
+
return { type: TreeAction.ADD_NODE, indexToAdd: index, nodeToAdd: child, parentNodeId };
|
|
2734
2890
|
}
|
|
2735
|
-
|
|
2736
|
-
|
|
2737
|
-
|
|
2738
|
-
|
|
2739
|
-
|
|
2740
|
-
const headEntity = entityStore.getEntityFromLink(this.entityLink);
|
|
2741
|
-
const maybeReferentLink = headEntity?.fields[this.field];
|
|
2742
|
-
if (undefined === maybeReferentLink) {
|
|
2743
|
-
// field references nothing (or even field doesn't exist)
|
|
2744
|
-
return undefined;
|
|
2745
|
-
}
|
|
2746
|
-
if (!isLink(maybeReferentLink)) {
|
|
2747
|
-
// Scenario of "impostor referent", where one of the deepPath's segments is not a Link but some other type
|
|
2748
|
-
// Under normal circumstance we expect field to be a Link, but it could be an "impostor"
|
|
2749
|
-
// eg. `Text` or `Number` or anything like that; could be due to CT changes or manual path creation via CMA
|
|
2750
|
-
return undefined;
|
|
2891
|
+
const item = getItem({ id: child.data.id }, tree);
|
|
2892
|
+
if (item) {
|
|
2893
|
+
const parentNode = getItem({ id: item.parentId }, tree);
|
|
2894
|
+
if (!parentNode) {
|
|
2895
|
+
return null;
|
|
2751
2896
|
}
|
|
2752
|
-
|
|
2897
|
+
const sourceIndex = parentNode.children.findIndex((c) => c.data.id === child.data.id);
|
|
2898
|
+
return { type: TreeAction.MOVE_NODE, sourceIndex, destinationIndex: index, parentNodeId };
|
|
2753
2899
|
}
|
|
2754
|
-
|
|
2755
|
-
|
|
2900
|
+
return {
|
|
2901
|
+
type: TreeAction.REPLACE_NODE,
|
|
2902
|
+
originalId: currentNode.children[index].data.id,
|
|
2903
|
+
indexToReplace: index,
|
|
2904
|
+
node: child,
|
|
2905
|
+
};
|
|
2906
|
+
}
|
|
2907
|
+
function matchingNodeAction({ index, originalIndex, nodeRemoved, nodeAdded, parentNodeId, }) {
|
|
2908
|
+
if (index !== originalIndex && !nodeRemoved && !nodeAdded) {
|
|
2909
|
+
return {
|
|
2910
|
+
type: TreeAction.REORDER_NODE,
|
|
2911
|
+
sourceIndex: originalIndex,
|
|
2912
|
+
destinationIndex: index,
|
|
2913
|
+
parentNodeId,
|
|
2914
|
+
};
|
|
2756
2915
|
}
|
|
2916
|
+
return null;
|
|
2757
2917
|
}
|
|
2758
|
-
function
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2918
|
+
function compareNodes({ currentNode, updatedNode, originalTree, differences = [], }) {
|
|
2919
|
+
// In the end, this map contains the list of nodes that are not present
|
|
2920
|
+
// in the updated tree and must be removed
|
|
2921
|
+
const map = new Map();
|
|
2922
|
+
if (!currentNode || !updatedNode) {
|
|
2923
|
+
return differences;
|
|
2924
|
+
}
|
|
2925
|
+
// On each tree level, consider only the children of the current node to differentiate between added, removed, or replaced case
|
|
2926
|
+
const currentNodeCount = currentNode.children.length;
|
|
2927
|
+
const updatedNodeCount = updatedNode.children.length;
|
|
2928
|
+
const nodeRemoved = currentNodeCount > updatedNodeCount;
|
|
2929
|
+
const nodeAdded = currentNodeCount < updatedNodeCount;
|
|
2930
|
+
const parentNodeId = updatedNode.data.id;
|
|
2931
|
+
/**
|
|
2932
|
+
* The data of the current node has changed, we need to update
|
|
2933
|
+
* this node to reflect the data change. (design, content, unbound values)
|
|
2934
|
+
*/
|
|
2935
|
+
if (!isEqual(currentNode.data, updatedNode.data)) {
|
|
2936
|
+
differences.push({
|
|
2937
|
+
type: TreeAction.UPDATE_NODE,
|
|
2938
|
+
nodeId: currentNode.data.id,
|
|
2939
|
+
node: updatedNode,
|
|
2940
|
+
});
|
|
2941
|
+
}
|
|
2942
|
+
// Map children of the first tree by their ID
|
|
2943
|
+
currentNode.children.forEach((child, index) => map.set(child.data.id, index));
|
|
2944
|
+
// Compare with the second tree
|
|
2945
|
+
updatedNode.children.forEach((child, index) => {
|
|
2946
|
+
const childId = child.data.id;
|
|
2947
|
+
// The original tree does not have this node in the updated tree.
|
|
2948
|
+
if (!map.has(childId)) {
|
|
2949
|
+
const diff = missingNodeAction({
|
|
2950
|
+
index,
|
|
2951
|
+
child,
|
|
2952
|
+
nodeAdded,
|
|
2953
|
+
parentNodeId,
|
|
2954
|
+
tree: originalTree,
|
|
2955
|
+
currentNode,
|
|
2956
|
+
});
|
|
2957
|
+
if (diff?.type === TreeAction.REPLACE_NODE) {
|
|
2958
|
+
// Remove it from the deletion map to avoid adding another REMOVE_NODE action
|
|
2959
|
+
map.delete(diff.originalId);
|
|
2960
|
+
}
|
|
2961
|
+
return differences.push(diff);
|
|
2962
|
+
}
|
|
2963
|
+
const originalIndex = map.get(childId);
|
|
2964
|
+
const diff = matchingNodeAction({
|
|
2965
|
+
index,
|
|
2966
|
+
originalIndex,
|
|
2967
|
+
nodeAdded,
|
|
2968
|
+
nodeRemoved,
|
|
2969
|
+
parentNodeId,
|
|
2970
|
+
});
|
|
2971
|
+
differences.push(diff);
|
|
2972
|
+
map.delete(childId);
|
|
2973
|
+
compareNodes({
|
|
2974
|
+
currentNode: currentNode.children[originalIndex],
|
|
2975
|
+
updatedNode: child,
|
|
2976
|
+
originalTree,
|
|
2977
|
+
differences,
|
|
2978
|
+
});
|
|
2979
|
+
});
|
|
2980
|
+
map.forEach((index, key) => {
|
|
2981
|
+
// If the node count of the entire tree doesn't signify
|
|
2982
|
+
// a node was removed, don't add that as a diff
|
|
2983
|
+
if (!nodeRemoved) {
|
|
2762
2984
|
return;
|
|
2763
|
-
for (const [, variableMapping] of Object.entries(node.data.props)) {
|
|
2764
|
-
if (variableMapping.type !== 'BoundValue')
|
|
2765
|
-
continue;
|
|
2766
|
-
if (!isDeepPath(variableMapping.path))
|
|
2767
|
-
continue;
|
|
2768
|
-
deepReferences.push(DeepReference.from({
|
|
2769
|
-
path: variableMapping.path,
|
|
2770
|
-
dataSource,
|
|
2771
|
-
}));
|
|
2772
2985
|
}
|
|
2986
|
+
// Remaining nodes in the map are removed in the second tree
|
|
2987
|
+
differences.push({
|
|
2988
|
+
type: TreeAction.REMOVE_NODE,
|
|
2989
|
+
indexToRemove: index,
|
|
2990
|
+
parentNodeId,
|
|
2991
|
+
idToRemove: key,
|
|
2992
|
+
});
|
|
2773
2993
|
});
|
|
2774
|
-
return
|
|
2994
|
+
return differences;
|
|
2995
|
+
}
|
|
2996
|
+
function getTreeDiffs(tree1, tree2, originalTree) {
|
|
2997
|
+
const differences = [];
|
|
2998
|
+
compareNodes({
|
|
2999
|
+
currentNode: tree1,
|
|
3000
|
+
updatedNode: tree2,
|
|
3001
|
+
originalTree,
|
|
3002
|
+
differences,
|
|
3003
|
+
});
|
|
3004
|
+
return differences.filter((diff) => diff);
|
|
2775
3005
|
}
|
|
2776
3006
|
|
|
2777
3007
|
const useTreeStore = create((set, get) => ({
|
|
@@ -2805,6 +3035,20 @@ const useTreeStore = create((set, get) => ({
|
|
|
2805
3035
|
});
|
|
2806
3036
|
}));
|
|
2807
3037
|
},
|
|
3038
|
+
/**
|
|
3039
|
+
* NOTE: this is for debugging purposes only as it causes ugly canvas flash.
|
|
3040
|
+
*
|
|
3041
|
+
* Force updates entire tree. Usually shouldn't be used as updateTree()
|
|
3042
|
+
* uses smart update algorithm based on diffs. But for troubleshooting
|
|
3043
|
+
* you may want to force update the tree so leaving this in.
|
|
3044
|
+
*/
|
|
3045
|
+
updateTreeForced: (tree) => {
|
|
3046
|
+
set({
|
|
3047
|
+
tree,
|
|
3048
|
+
// Breakpoints must be updated, as we receive completely new tree with possibly new breakpoints
|
|
3049
|
+
breakpoints: tree?.root?.data?.breakpoints || [],
|
|
3050
|
+
});
|
|
3051
|
+
},
|
|
2808
3052
|
updateTree: (tree) => {
|
|
2809
3053
|
const currentTree = get().tree;
|
|
2810
3054
|
/**
|
|
@@ -2850,6 +3094,21 @@ const useTreeStore = create((set, get) => ({
|
|
|
2850
3094
|
state.breakpoints = tree?.root?.data?.breakpoints || [];
|
|
2851
3095
|
}));
|
|
2852
3096
|
},
|
|
3097
|
+
addChild: (index, parentId, node) => {
|
|
3098
|
+
set(produce((state) => {
|
|
3099
|
+
addChildNode(index, parentId, node, state.tree.root);
|
|
3100
|
+
}));
|
|
3101
|
+
},
|
|
3102
|
+
reorderChildren: (destinationIndex, destinationParentId, sourceIndex) => {
|
|
3103
|
+
set(produce((state) => {
|
|
3104
|
+
reorderChildNode(sourceIndex, destinationIndex, destinationParentId, state.tree.root);
|
|
3105
|
+
}));
|
|
3106
|
+
},
|
|
3107
|
+
reparentChild: (destinationIndex, destinationParentId, sourceIndex, sourceParentId) => {
|
|
3108
|
+
set(produce((state) => {
|
|
3109
|
+
reparentChildNode(sourceIndex, destinationIndex, sourceParentId, destinationParentId, state.tree.root);
|
|
3110
|
+
}));
|
|
3111
|
+
},
|
|
2853
3112
|
}));
|
|
2854
3113
|
const hasBreakpointDiffs = (currentTree, newTree) => {
|
|
2855
3114
|
const currentBreakpoints = currentTree?.root?.data?.breakpoints ?? [];
|
|
@@ -2867,8 +3126,8 @@ const cloneDeepAsPOJO = (obj) => {
|
|
|
2867
3126
|
return JSON.parse(JSON.stringify(obj));
|
|
2868
3127
|
};
|
|
2869
3128
|
|
|
2870
|
-
var css_248z$a = ".
|
|
2871
|
-
var styles$
|
|
3129
|
+
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";
|
|
3130
|
+
var styles$3 = {"hitbox":"render-module_hitbox__l4ysJ","hitboxLower":"render-module_hitboxLower__tgsA1","canvasBottomSpacer":"render-module_canvasBottomSpacer__JuxVh","container":"render-module_container__-C3d7"};
|
|
2872
3131
|
styleInject(css_248z$a);
|
|
2873
3132
|
|
|
2874
3133
|
// TODO: In order to support integrations without React, we should extract this heavy logic into simple
|
|
@@ -2907,6 +3166,66 @@ const useBreakpoints = (breakpoints) => {
|
|
|
2907
3166
|
return { resolveDesignValue };
|
|
2908
3167
|
};
|
|
2909
3168
|
|
|
3169
|
+
/**
|
|
3170
|
+
* This function gets the element co-ordinates of a specified component in the DOM and its parent
|
|
3171
|
+
* and sends the DOM Rect to the client app
|
|
3172
|
+
*/
|
|
3173
|
+
const sendSelectedComponentCoordinates = (instanceId) => {
|
|
3174
|
+
const selection = getSelectionNodes(instanceId);
|
|
3175
|
+
if (selection?.target) {
|
|
3176
|
+
const sendUpdateSelectedComponentCoordinates = () => {
|
|
3177
|
+
sendMessage(OUTGOING_EVENTS.UpdateSelectedComponentCoordinates, {
|
|
3178
|
+
selectedNodeCoordinates: getElementCoordinates(selection.target),
|
|
3179
|
+
selectedAssemblyChildCoordinates: selection.patternChild
|
|
3180
|
+
? getElementCoordinates(selection.patternChild)
|
|
3181
|
+
: undefined,
|
|
3182
|
+
parentCoordinates: selection.parent ? getElementCoordinates(selection.parent) : undefined,
|
|
3183
|
+
});
|
|
3184
|
+
};
|
|
3185
|
+
// If the target contains an image, wait for this image to be loaded before sending the coordinates
|
|
3186
|
+
const childImage = selection.target.querySelector('img');
|
|
3187
|
+
if (childImage) {
|
|
3188
|
+
const handleImageLoad = () => {
|
|
3189
|
+
sendUpdateSelectedComponentCoordinates();
|
|
3190
|
+
childImage.removeEventListener('load', handleImageLoad);
|
|
3191
|
+
};
|
|
3192
|
+
childImage.addEventListener('load', handleImageLoad);
|
|
3193
|
+
}
|
|
3194
|
+
sendUpdateSelectedComponentCoordinates();
|
|
3195
|
+
}
|
|
3196
|
+
};
|
|
3197
|
+
const getSelectionNodes = (instanceId) => {
|
|
3198
|
+
if (!instanceId)
|
|
3199
|
+
return;
|
|
3200
|
+
let selectedNode = document.querySelector(`[data-cf-node-id="${instanceId}"]`);
|
|
3201
|
+
let selectedPatternChild = null;
|
|
3202
|
+
let selectedParent = null;
|
|
3203
|
+
// Use RegEx instead of split to match the last occurrence of '---' in the instanceId instead of the first one
|
|
3204
|
+
const idMatch = instanceId.match(/(.*)---(.*)/);
|
|
3205
|
+
const rootNodeId = idMatch?.[1] ?? instanceId;
|
|
3206
|
+
const nodeLocation = idMatch?.[2];
|
|
3207
|
+
const isNestedPattern = nodeLocation && selectedNode?.dataset?.cfNodeBlockType === ASSEMBLY_NODE_TYPE;
|
|
3208
|
+
const isPatternChild = !isNestedPattern && nodeLocation;
|
|
3209
|
+
if (isPatternChild) {
|
|
3210
|
+
// For pattern child nodes, render the pattern itself as selected component
|
|
3211
|
+
selectedPatternChild = selectedNode;
|
|
3212
|
+
selectedNode = document.querySelector(`[data-cf-node-id="${rootNodeId}"]`);
|
|
3213
|
+
}
|
|
3214
|
+
else if (isNestedPattern) {
|
|
3215
|
+
// For nested patterns, return the upper pattern as parent
|
|
3216
|
+
selectedParent = document.querySelector(`[data-cf-node-id="${rootNodeId}"]`);
|
|
3217
|
+
}
|
|
3218
|
+
else {
|
|
3219
|
+
// Find the next valid parent of the selected element
|
|
3220
|
+
selectedParent = selectedNode?.parentElement ?? null;
|
|
3221
|
+
// Ensure that the selection parent is a VisualEditorBlock
|
|
3222
|
+
while (selectedParent && !selectedParent.dataset?.cfNodeId) {
|
|
3223
|
+
selectedParent = selectedParent?.parentElement;
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
return { target: selectedNode, patternChild: selectedPatternChild, parent: selectedParent };
|
|
3227
|
+
};
|
|
3228
|
+
|
|
2910
3229
|
// Note: During development, the hot reloading might empty this and it
|
|
2911
3230
|
// stays empty leading to not rendering assemblies. Ideally, this is
|
|
2912
3231
|
// integrated into the state machine to keep track of its state.
|
|
@@ -2940,10 +3259,14 @@ const useEditorStore = create((set, get) => ({
|
|
|
2940
3259
|
dataSource: {},
|
|
2941
3260
|
hyperLinkPattern: undefined,
|
|
2942
3261
|
unboundValues: {},
|
|
3262
|
+
selectedNodeId: null,
|
|
2943
3263
|
locale: null,
|
|
2944
3264
|
setHyperLinkPattern: (pattern) => {
|
|
2945
3265
|
set({ hyperLinkPattern: pattern });
|
|
2946
3266
|
},
|
|
3267
|
+
setSelectedNodeId: (id) => {
|
|
3268
|
+
set({ selectedNodeId: id });
|
|
3269
|
+
},
|
|
2947
3270
|
setDataSource(data) {
|
|
2948
3271
|
const dataSource = get().dataSource;
|
|
2949
3272
|
const newDataSource = { ...dataSource, ...data };
|
|
@@ -2975,7 +3298,6 @@ const useEditorStore = create((set, get) => ({
|
|
|
2975
3298
|
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";
|
|
2976
3299
|
styleInject(css_248z$8);
|
|
2977
3300
|
|
|
2978
|
-
/** @deprecated will be removed when dropping backward compatibility for old DND */
|
|
2979
3301
|
/**
|
|
2980
3302
|
* These modes are ONLY intended to be internally used within the context of
|
|
2981
3303
|
* editing an experience inside of Contentful Studio. i.e. these modes
|
|
@@ -3554,8 +3876,8 @@ var VisualEditorMode;
|
|
|
3554
3876
|
VisualEditorMode["InjectScript"] = "injectScript";
|
|
3555
3877
|
})(VisualEditorMode || (VisualEditorMode = {}));
|
|
3556
3878
|
|
|
3557
|
-
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";
|
|
3558
|
-
styleInject(css_248z$2);
|
|
3879
|
+
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";
|
|
3880
|
+
styleInject(css_248z$2$1);
|
|
3559
3881
|
|
|
3560
3882
|
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) => {
|
|
3561
3883
|
return (React.createElement("div", { id: id, ref: ref, style: {
|
|
@@ -3579,14 +3901,34 @@ const Flex = forwardRef(({ id, children, onMouseEnter, onMouseUp, onMouseLeave,
|
|
|
3579
3901
|
});
|
|
3580
3902
|
Flex.displayName = 'Flex';
|
|
3581
3903
|
|
|
3582
|
-
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";
|
|
3904
|
+
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";
|
|
3583
3905
|
styleInject(css_248z$1$1);
|
|
3584
3906
|
|
|
3585
|
-
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";
|
|
3907
|
+
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";
|
|
3586
3908
|
styleInject(css_248z$9);
|
|
3587
3909
|
|
|
3910
|
+
const ColumnWrapper = forwardRef((props, ref) => {
|
|
3911
|
+
return (React.createElement("div", { ref: ref, ...props, style: {
|
|
3912
|
+
...(props.style || {}),
|
|
3913
|
+
display: 'grid',
|
|
3914
|
+
gridTemplateColumns: 'repeat(12, [col-start] 1fr)',
|
|
3915
|
+
} }, props.children));
|
|
3916
|
+
});
|
|
3917
|
+
ColumnWrapper.displayName = 'ColumnWrapper';
|
|
3918
|
+
|
|
3588
3919
|
const assemblyStyle = { display: 'contents' };
|
|
3920
|
+
// Feel free to do any magic as regards variable definitions for assemblies
|
|
3921
|
+
// Or if this isn't necessary by the time we figure that part out, we can bid this part farewell
|
|
3589
3922
|
const Assembly = (props) => {
|
|
3923
|
+
if (props.editorMode) {
|
|
3924
|
+
const { node, dragProps, ...editorModeProps } = props;
|
|
3925
|
+
return props.renderDropzone(node, {
|
|
3926
|
+
...editorModeProps,
|
|
3927
|
+
['data-test-id']: 'contentful-assembly',
|
|
3928
|
+
className: props.className,
|
|
3929
|
+
dragProps,
|
|
3930
|
+
});
|
|
3931
|
+
}
|
|
3590
3932
|
// Using a display contents so assembly content/children
|
|
3591
3933
|
// can appear as if they are direct children of the div wrapper's parent
|
|
3592
3934
|
return React.createElement("div", { "data-test-id": "assembly", ...props, style: assemblyStyle });
|
|
@@ -3609,6 +3951,75 @@ const useEntityStore = create((set) => ({
|
|
|
3609
3951
|
},
|
|
3610
3952
|
}));
|
|
3611
3953
|
|
|
3954
|
+
class DragState {
|
|
3955
|
+
constructor() {
|
|
3956
|
+
this.isDragStartedOnParent = false;
|
|
3957
|
+
this.isDraggingItem = false;
|
|
3958
|
+
}
|
|
3959
|
+
get isDragging() {
|
|
3960
|
+
return this.isDraggingItem;
|
|
3961
|
+
}
|
|
3962
|
+
get isDraggingOnParent() {
|
|
3963
|
+
return this.isDragStartedOnParent;
|
|
3964
|
+
}
|
|
3965
|
+
updateIsDragging(isDraggingItem) {
|
|
3966
|
+
this.isDraggingItem = isDraggingItem;
|
|
3967
|
+
}
|
|
3968
|
+
updateIsDragStartedOnParent(isDragStartedOnParent) {
|
|
3969
|
+
this.isDragStartedOnParent = isDragStartedOnParent;
|
|
3970
|
+
}
|
|
3971
|
+
resetState() {
|
|
3972
|
+
this.isDraggingItem = false;
|
|
3973
|
+
this.isDragStartedOnParent = false;
|
|
3974
|
+
}
|
|
3975
|
+
}
|
|
3976
|
+
|
|
3977
|
+
class SimulateDnD extends DragState {
|
|
3978
|
+
constructor() {
|
|
3979
|
+
super();
|
|
3980
|
+
this.draggingElement = null;
|
|
3981
|
+
}
|
|
3982
|
+
setupDrag() {
|
|
3983
|
+
this.updateIsDragStartedOnParent(true);
|
|
3984
|
+
}
|
|
3985
|
+
startDrag(coordX, coordY) {
|
|
3986
|
+
this.draggingElement = document.getElementById(NEW_COMPONENT_ID);
|
|
3987
|
+
this.updateIsDragging(true);
|
|
3988
|
+
this.simulateMouseEvent(coordX, coordY, 'mousedown');
|
|
3989
|
+
}
|
|
3990
|
+
updateDrag(coordX, coordY) {
|
|
3991
|
+
if (!this.draggingElement) {
|
|
3992
|
+
this.draggingElement = document.querySelector(`[${CTFL_DRAGGING_ELEMENT}]`);
|
|
3993
|
+
}
|
|
3994
|
+
this.simulateMouseEvent(coordX, coordY);
|
|
3995
|
+
}
|
|
3996
|
+
endDrag(coordX, coordY) {
|
|
3997
|
+
this.simulateMouseEvent(coordX, coordY, 'mouseup');
|
|
3998
|
+
this.reset();
|
|
3999
|
+
}
|
|
4000
|
+
reset() {
|
|
4001
|
+
this.draggingElement = null;
|
|
4002
|
+
this.resetState();
|
|
4003
|
+
}
|
|
4004
|
+
simulateMouseEvent(coordX, coordY, eventName = 'mousemove') {
|
|
4005
|
+
if (!this.draggingElement) {
|
|
4006
|
+
return;
|
|
4007
|
+
}
|
|
4008
|
+
const options = {
|
|
4009
|
+
bubbles: true,
|
|
4010
|
+
cancelable: true,
|
|
4011
|
+
view: window,
|
|
4012
|
+
pageX: 0,
|
|
4013
|
+
pageY: 0,
|
|
4014
|
+
clientX: coordX,
|
|
4015
|
+
clientY: coordY,
|
|
4016
|
+
};
|
|
4017
|
+
const event = new MouseEvent(eventName, options);
|
|
4018
|
+
this.draggingElement.dispatchEvent(event);
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
var SimulateDnD$1 = new SimulateDnD();
|
|
4022
|
+
|
|
3612
4023
|
function useEditorSubscriber() {
|
|
3613
4024
|
const entityStore = useEntityStore((state) => state.entityStore);
|
|
3614
4025
|
const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
|
|
@@ -3622,7 +4033,14 @@ function useEditorSubscriber() {
|
|
|
3622
4033
|
const setLocale = useEditorStore((state) => state.setLocale);
|
|
3623
4034
|
const setUnboundValues = useEditorStore((state) => state.setUnboundValues);
|
|
3624
4035
|
const setDataSource = useEditorStore((state) => state.setDataSource);
|
|
4036
|
+
const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
|
|
4037
|
+
const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
|
|
3625
4038
|
const resetEntityStore = useEntityStore((state) => state.resetEntityStore);
|
|
4039
|
+
const setComponentId = useDraggedItemStore((state) => state.setComponentId);
|
|
4040
|
+
const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
|
|
4041
|
+
const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
|
|
4042
|
+
const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
|
|
4043
|
+
const setScrollY = useDraggedItemStore((state) => state.setScrollY);
|
|
3626
4044
|
const reloadApp = () => {
|
|
3627
4045
|
sendMessage(OUTGOING_EVENTS.CanvasReload, undefined);
|
|
3628
4046
|
// Wait a moment to ensure that the message was sent
|
|
@@ -3723,12 +4141,12 @@ function useEditorSubscriber() {
|
|
|
3723
4141
|
}
|
|
3724
4142
|
const eventData = tryParseMessage(event);
|
|
3725
4143
|
console.debug(`[experiences-sdk-react::onMessage] Received message [${eventData.eventType}]`, eventData);
|
|
3726
|
-
if (eventData.eventType === PostMessageMethods$
|
|
4144
|
+
if (eventData.eventType === PostMessageMethods$2.REQUESTED_ENTITIES) {
|
|
3727
4145
|
// Expected message: This message is handled in the EntityStore to store fetched entities
|
|
3728
4146
|
return;
|
|
3729
4147
|
}
|
|
3730
4148
|
switch (eventData.eventType) {
|
|
3731
|
-
case INCOMING_EVENTS
|
|
4149
|
+
case INCOMING_EVENTS.ExperienceUpdated: {
|
|
3732
4150
|
const { tree, locale, changedNode, changedValueType, assemblies } = eventData.payload;
|
|
3733
4151
|
// Make sure to first store the assemblies before setting the tree and thus triggering a rerender
|
|
3734
4152
|
if (assemblies) {
|
|
@@ -3772,7 +4190,7 @@ function useEditorSubscriber() {
|
|
|
3772
4190
|
updateTree(tree);
|
|
3773
4191
|
break;
|
|
3774
4192
|
}
|
|
3775
|
-
case INCOMING_EVENTS
|
|
4193
|
+
case INCOMING_EVENTS.AssembliesRegistered: {
|
|
3776
4194
|
const { assemblies } = eventData.payload;
|
|
3777
4195
|
assemblies.forEach((definition) => {
|
|
3778
4196
|
addComponentRegistration({
|
|
@@ -3782,7 +4200,7 @@ function useEditorSubscriber() {
|
|
|
3782
4200
|
});
|
|
3783
4201
|
break;
|
|
3784
4202
|
}
|
|
3785
|
-
case INCOMING_EVENTS
|
|
4203
|
+
case INCOMING_EVENTS.AssembliesAdded: {
|
|
3786
4204
|
const { assembly, assemblyDefinition, } = eventData.payload;
|
|
3787
4205
|
entityStore.updateEntity(assembly);
|
|
3788
4206
|
// Using a Map here to avoid setting state and rerending all existing assemblies when a new assembly is added
|
|
@@ -3799,7 +4217,28 @@ function useEditorSubscriber() {
|
|
|
3799
4217
|
}
|
|
3800
4218
|
break;
|
|
3801
4219
|
}
|
|
3802
|
-
case INCOMING_EVENTS
|
|
4220
|
+
case INCOMING_EVENTS.CanvasResized: {
|
|
4221
|
+
const { selectedNodeId } = eventData.payload;
|
|
4222
|
+
if (selectedNodeId) {
|
|
4223
|
+
sendSelectedComponentCoordinates(selectedNodeId);
|
|
4224
|
+
}
|
|
4225
|
+
break;
|
|
4226
|
+
}
|
|
4227
|
+
case INCOMING_EVENTS.HoverComponent: {
|
|
4228
|
+
const { hoveredNodeId } = eventData.payload;
|
|
4229
|
+
setHoveredComponentId(hoveredNodeId);
|
|
4230
|
+
break;
|
|
4231
|
+
}
|
|
4232
|
+
case INCOMING_EVENTS.ComponentDraggingChanged: {
|
|
4233
|
+
const { isDragging } = eventData.payload;
|
|
4234
|
+
if (!isDragging) {
|
|
4235
|
+
setComponentId('');
|
|
4236
|
+
setDraggingOnCanvas(false);
|
|
4237
|
+
SimulateDnD$1.reset();
|
|
4238
|
+
}
|
|
4239
|
+
break;
|
|
4240
|
+
}
|
|
4241
|
+
case INCOMING_EVENTS.UpdatedEntity: {
|
|
3803
4242
|
const { entity: updatedEntity, shouldRerender } = eventData.payload;
|
|
3804
4243
|
if (updatedEntity) {
|
|
3805
4244
|
const storedEntity = entityStore.entities.find((entity) => entity.sys.id === updatedEntity.sys.id);
|
|
@@ -3812,7 +4251,52 @@ function useEditorSubscriber() {
|
|
|
3812
4251
|
}
|
|
3813
4252
|
break;
|
|
3814
4253
|
}
|
|
3815
|
-
case INCOMING_EVENTS
|
|
4254
|
+
case INCOMING_EVENTS.RequestEditorMode: {
|
|
4255
|
+
break;
|
|
4256
|
+
}
|
|
4257
|
+
case INCOMING_EVENTS.ComponentDragCanceled: {
|
|
4258
|
+
if (SimulateDnD$1.isDragging) {
|
|
4259
|
+
//simulate a mouseup event to cancel the drag
|
|
4260
|
+
SimulateDnD$1.endDrag(0, 0);
|
|
4261
|
+
}
|
|
4262
|
+
break;
|
|
4263
|
+
}
|
|
4264
|
+
case INCOMING_EVENTS.ComponentDragStarted: {
|
|
4265
|
+
const { id, isAssembly } = eventData.payload;
|
|
4266
|
+
SimulateDnD$1.setupDrag();
|
|
4267
|
+
setComponentId(`${id}:${isAssembly}` || '');
|
|
4268
|
+
setDraggingOnCanvas(true);
|
|
4269
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
4270
|
+
nodeId: '',
|
|
4271
|
+
});
|
|
4272
|
+
break;
|
|
4273
|
+
}
|
|
4274
|
+
case INCOMING_EVENTS.ComponentDragEnded: {
|
|
4275
|
+
SimulateDnD$1.reset();
|
|
4276
|
+
setComponentId('');
|
|
4277
|
+
setDraggingOnCanvas(false);
|
|
4278
|
+
break;
|
|
4279
|
+
}
|
|
4280
|
+
case INCOMING_EVENTS.SelectComponent: {
|
|
4281
|
+
const { selectedNodeId: nodeId } = eventData.payload;
|
|
4282
|
+
setSelectedNodeId(nodeId);
|
|
4283
|
+
sendSelectedComponentCoordinates(nodeId);
|
|
4284
|
+
break;
|
|
4285
|
+
}
|
|
4286
|
+
case INCOMING_EVENTS.MouseMove: {
|
|
4287
|
+
const { mouseX, mouseY } = eventData.payload;
|
|
4288
|
+
setMousePosition(mouseX, mouseY);
|
|
4289
|
+
if (SimulateDnD$1.isDraggingOnParent && !SimulateDnD$1.isDragging) {
|
|
4290
|
+
SimulateDnD$1.startDrag(mouseX, mouseY);
|
|
4291
|
+
}
|
|
4292
|
+
else {
|
|
4293
|
+
SimulateDnD$1.updateDrag(mouseX, mouseY);
|
|
4294
|
+
}
|
|
4295
|
+
break;
|
|
4296
|
+
}
|
|
4297
|
+
case INCOMING_EVENTS.ComponentMoveEnded: {
|
|
4298
|
+
const { mouseX, mouseY } = eventData.payload;
|
|
4299
|
+
SimulateDnD$1.endDrag(mouseX, mouseY);
|
|
3816
4300
|
break;
|
|
3817
4301
|
}
|
|
3818
4302
|
default:
|
|
@@ -3825,8 +4309,11 @@ function useEditorSubscriber() {
|
|
|
3825
4309
|
};
|
|
3826
4310
|
}, [
|
|
3827
4311
|
entityStore,
|
|
4312
|
+
setComponentId,
|
|
4313
|
+
setDraggingOnCanvas,
|
|
3828
4314
|
setDataSource,
|
|
3829
4315
|
setLocale,
|
|
4316
|
+
setSelectedNodeId,
|
|
3830
4317
|
dataSource,
|
|
3831
4318
|
areEntitiesFetched,
|
|
3832
4319
|
fetchMissingEntities,
|
|
@@ -3834,92 +4321,328 @@ function useEditorSubscriber() {
|
|
|
3834
4321
|
unboundValues,
|
|
3835
4322
|
updateTree,
|
|
3836
4323
|
updateNodesByUpdatedEntity,
|
|
4324
|
+
setMousePosition,
|
|
3837
4325
|
resetEntityStore,
|
|
4326
|
+
setHoveredComponentId,
|
|
3838
4327
|
]);
|
|
4328
|
+
/*
|
|
4329
|
+
* Handles on scroll business
|
|
4330
|
+
*/
|
|
4331
|
+
useEffect(() => {
|
|
4332
|
+
let timeoutId = 0;
|
|
4333
|
+
let isScrolling = false;
|
|
4334
|
+
const onScroll = () => {
|
|
4335
|
+
setScrollY(window.scrollY);
|
|
4336
|
+
if (isScrolling === false) {
|
|
4337
|
+
sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.Start);
|
|
4338
|
+
}
|
|
4339
|
+
sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.IsScrolling);
|
|
4340
|
+
isScrolling = true;
|
|
4341
|
+
clearTimeout(timeoutId);
|
|
4342
|
+
timeoutId = window.setTimeout(() => {
|
|
4343
|
+
if (isScrolling === false) {
|
|
4344
|
+
return;
|
|
4345
|
+
}
|
|
4346
|
+
isScrolling = false;
|
|
4347
|
+
sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.End);
|
|
4348
|
+
/**
|
|
4349
|
+
* On scroll end, send new co-ordinates of selected node
|
|
4350
|
+
*/
|
|
4351
|
+
if (selectedNodeId) {
|
|
4352
|
+
sendSelectedComponentCoordinates(selectedNodeId);
|
|
4353
|
+
}
|
|
4354
|
+
}, 150);
|
|
4355
|
+
};
|
|
4356
|
+
window.addEventListener('scroll', onScroll, { capture: true, passive: true });
|
|
4357
|
+
return () => {
|
|
4358
|
+
window.removeEventListener('scroll', onScroll, { capture: true });
|
|
4359
|
+
clearTimeout(timeoutId);
|
|
4360
|
+
};
|
|
4361
|
+
}, [selectedNodeId, setScrollY]);
|
|
3839
4362
|
}
|
|
3840
4363
|
|
|
3841
|
-
const
|
|
3842
|
-
|
|
3843
|
-
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: {
|
|
3844
|
-
border: '1px solid red',
|
|
3845
|
-
background: 'rgba(255, 0, 0, 0.1)',
|
|
3846
|
-
padding: '1rem 1rem 0 1rem',
|
|
3847
|
-
width: '100%',
|
|
3848
|
-
height: '100%',
|
|
3849
|
-
} },
|
|
3850
|
-
"Circular usage of patterns detected:",
|
|
3851
|
-
React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
|
|
3852
|
-
const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
|
|
3853
|
-
const entry = entityStore.getEntityFromLink(entryLink);
|
|
3854
|
-
const entryTitle = entry?.fields?.title;
|
|
3855
|
-
const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
|
|
3856
|
-
return React.createElement("li", { key: patternId }, text);
|
|
3857
|
-
}))));
|
|
4364
|
+
const onComponentMoved = (options) => {
|
|
4365
|
+
sendMessage(OUTGOING_EVENTS.ComponentMoved, options);
|
|
3858
4366
|
};
|
|
3859
4367
|
|
|
3860
|
-
|
|
3861
|
-
constructor(message) {
|
|
3862
|
-
super(message);
|
|
3863
|
-
this.name = 'ImportedComponentError';
|
|
3864
|
-
}
|
|
3865
|
-
}
|
|
3866
|
-
class ExperienceSDKError extends Error {
|
|
3867
|
-
constructor(message) {
|
|
3868
|
-
super(message);
|
|
3869
|
-
this.name = 'ExperienceSDKError';
|
|
3870
|
-
}
|
|
3871
|
-
}
|
|
3872
|
-
class ImportedComponentErrorBoundary extends React.Component {
|
|
3873
|
-
componentDidCatch(error, _errorInfo) {
|
|
3874
|
-
if (error.name === 'ImportedComponentError' || error.name === 'ExperienceSDKError') {
|
|
3875
|
-
// This error was already handled by a nested error boundary and should be passed upwards
|
|
3876
|
-
// We have to do this as we wrap every component on every layer with this error boundary and
|
|
3877
|
-
// thus an error deep in the tree bubbles through many layers of error boundaries.
|
|
3878
|
-
throw error;
|
|
3879
|
-
}
|
|
3880
|
-
// Differentiate between custom and SDK-provided components for error tracking
|
|
3881
|
-
const ErrorClass = isContentfulComponent(this.props.componentId)
|
|
3882
|
-
? ExperienceSDKError
|
|
3883
|
-
: ImportedComponentError;
|
|
3884
|
-
const err = new ErrorClass(error.message);
|
|
3885
|
-
err.stack = error.stack;
|
|
3886
|
-
throw err;
|
|
3887
|
-
}
|
|
3888
|
-
render() {
|
|
3889
|
-
return this.props.children;
|
|
3890
|
-
}
|
|
3891
|
-
}
|
|
4368
|
+
const generateId = (type) => `${type}-${v4()}`;
|
|
3892
4369
|
|
|
3893
|
-
const
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
|
|
3901
|
-
|
|
4370
|
+
const createTreeNode = ({ blockId, parentId, slotId }) => {
|
|
4371
|
+
const node = {
|
|
4372
|
+
type: 'block',
|
|
4373
|
+
data: {
|
|
4374
|
+
id: generateId(blockId),
|
|
4375
|
+
blockId,
|
|
4376
|
+
slotId,
|
|
4377
|
+
props: {},
|
|
4378
|
+
dataSource: {},
|
|
4379
|
+
breakpoints: [],
|
|
4380
|
+
unboundValues: {},
|
|
4381
|
+
},
|
|
4382
|
+
parentId,
|
|
4383
|
+
children: [],
|
|
4384
|
+
};
|
|
4385
|
+
return node;
|
|
3902
4386
|
};
|
|
3903
4387
|
|
|
3904
|
-
|
|
3905
|
-
|
|
3906
|
-
|
|
4388
|
+
const onComponentDropped = ({ node, index, parentBlockId, parentType, parentId, }) => {
|
|
4389
|
+
sendMessage(OUTGOING_EVENTS.ComponentDropped, {
|
|
4390
|
+
node,
|
|
4391
|
+
index: index ?? node.children.length,
|
|
4392
|
+
parentNode: {
|
|
4393
|
+
type: parentType,
|
|
4394
|
+
data: {
|
|
4395
|
+
blockId: parentBlockId,
|
|
4396
|
+
id: parentId,
|
|
4397
|
+
},
|
|
4398
|
+
},
|
|
4399
|
+
});
|
|
4400
|
+
};
|
|
3907
4401
|
|
|
3908
|
-
const
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
4402
|
+
const onDrop = ({ destinationIndex, componentType, destinationZoneId, data, slotId, }) => {
|
|
4403
|
+
const parentId = destinationZoneId;
|
|
4404
|
+
const parentNode = getItem({ id: parentId }, data);
|
|
4405
|
+
const parentIsRoot = parentId === ROOT_ID;
|
|
4406
|
+
const emptyComponentData = {
|
|
4407
|
+
type: 'block',
|
|
4408
|
+
parentId,
|
|
4409
|
+
children: [],
|
|
4410
|
+
data: {
|
|
4411
|
+
blockId: componentType,
|
|
4412
|
+
id: generateId(componentType),
|
|
4413
|
+
slotId,
|
|
4414
|
+
breakpoints: [],
|
|
4415
|
+
dataSource: {},
|
|
4416
|
+
props: {},
|
|
4417
|
+
unboundValues: {},
|
|
4418
|
+
},
|
|
4419
|
+
};
|
|
4420
|
+
onComponentDropped({
|
|
4421
|
+
node: emptyComponentData,
|
|
4422
|
+
index: destinationIndex,
|
|
4423
|
+
parentType: parentIsRoot ? 'root' : parentNode?.type,
|
|
4424
|
+
parentBlockId: parentNode?.data.blockId,
|
|
4425
|
+
parentId: parentIsRoot ? 'root' : parentId,
|
|
4426
|
+
});
|
|
4427
|
+
};
|
|
4428
|
+
|
|
4429
|
+
/**
|
|
4430
|
+
* Parses a droppable zone ID into a node ID and slot ID.
|
|
4431
|
+
*
|
|
4432
|
+
* The slot ID is optional and only present if the component implements multiple drop zones.
|
|
4433
|
+
*
|
|
4434
|
+
* @param zoneId - Expected formats are `nodeId` or `nodeId|slotId`.
|
|
4435
|
+
*/
|
|
4436
|
+
const parseZoneId = (zoneId) => {
|
|
4437
|
+
const [nodeId, slotId] = zoneId.includes('|') ? zoneId.split('|') : [zoneId, undefined];
|
|
4438
|
+
return { nodeId, slotId };
|
|
4439
|
+
};
|
|
4440
|
+
|
|
4441
|
+
function useCanvasInteractions() {
|
|
4442
|
+
const tree = useTreeStore((state) => state.tree);
|
|
4443
|
+
const reorderChildren = useTreeStore((state) => state.reorderChildren);
|
|
4444
|
+
const reparentChild = useTreeStore((state) => state.reparentChild);
|
|
4445
|
+
const addChild = useTreeStore((state) => state.addChild);
|
|
4446
|
+
const onAddComponent = (droppedItem) => {
|
|
4447
|
+
const { destination, draggableId } = droppedItem;
|
|
4448
|
+
if (!destination) {
|
|
4449
|
+
return;
|
|
4450
|
+
}
|
|
4451
|
+
/**
|
|
4452
|
+
* We only have the draggableId as information about the new component being dropped.
|
|
4453
|
+
* So we need to split it to get the blockId and the isAssembly flag.
|
|
4454
|
+
*/
|
|
4455
|
+
const [blockId, isAssembly] = draggableId.split(':');
|
|
4456
|
+
const { nodeId: parentId, slotId } = parseZoneId(destination.droppableId);
|
|
4457
|
+
const droppingOnRoot = parentId === ROOT_ID;
|
|
4458
|
+
const isValidRootComponent = blockId === CONTENTFUL_COMPONENTS$1.container.id;
|
|
4459
|
+
let node = createTreeNode({ blockId, parentId, slotId });
|
|
4460
|
+
if (droppingOnRoot && !isValidRootComponent) {
|
|
4461
|
+
const wrappingContainer = createTreeNode({
|
|
4462
|
+
blockId: CONTENTFUL_COMPONENTS$1.container.id,
|
|
4463
|
+
parentId,
|
|
4464
|
+
});
|
|
4465
|
+
const childNode = createTreeNode({
|
|
4466
|
+
blockId,
|
|
4467
|
+
parentId: wrappingContainer.data.id,
|
|
3915
4468
|
});
|
|
4469
|
+
node = wrappingContainer;
|
|
4470
|
+
node.children = [childNode];
|
|
3916
4471
|
}
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
4472
|
+
/**
|
|
4473
|
+
* isAssembly comes from a string ID so we need to check if it's 'true' or 'false'
|
|
4474
|
+
* in string format.
|
|
4475
|
+
*/
|
|
4476
|
+
if (isAssembly === 'false') {
|
|
4477
|
+
addChild(destination.index, parentId, node);
|
|
3920
4478
|
}
|
|
3921
|
-
|
|
3922
|
-
|
|
4479
|
+
onDrop({
|
|
4480
|
+
data: tree,
|
|
4481
|
+
componentType: blockId,
|
|
4482
|
+
destinationIndex: destination.index,
|
|
4483
|
+
destinationZoneId: parentId,
|
|
4484
|
+
slotId,
|
|
4485
|
+
});
|
|
4486
|
+
};
|
|
4487
|
+
const onMoveComponent = (droppedItem) => {
|
|
4488
|
+
const { destination, source, draggableId } = droppedItem;
|
|
4489
|
+
if (!destination || !source) {
|
|
4490
|
+
return;
|
|
4491
|
+
}
|
|
4492
|
+
if (destination.droppableId === source.droppableId) {
|
|
4493
|
+
reorderChildren(destination.index, destination.droppableId, source.index);
|
|
4494
|
+
}
|
|
4495
|
+
if (destination.droppableId !== source.droppableId) {
|
|
4496
|
+
reparentChild(destination.index, destination.droppableId, source.index, source.droppableId);
|
|
4497
|
+
}
|
|
4498
|
+
onComponentMoved({
|
|
4499
|
+
nodeId: draggableId,
|
|
4500
|
+
destinationIndex: destination.index,
|
|
4501
|
+
destinationParentId: destination.droppableId,
|
|
4502
|
+
sourceIndex: source.index,
|
|
4503
|
+
sourceParentId: source.droppableId,
|
|
4504
|
+
});
|
|
4505
|
+
};
|
|
4506
|
+
return { onAddComponent, onMoveComponent };
|
|
4507
|
+
}
|
|
4508
|
+
|
|
4509
|
+
const TestDNDContainer = ({ onDragEnd, onBeforeDragStart, onDragStart, onDragUpdate, children, }) => {
|
|
4510
|
+
const handleDragStart = (event) => {
|
|
4511
|
+
const draggedItem = event.nativeEvent;
|
|
4512
|
+
const start = {
|
|
4513
|
+
mode: draggedItem.mode,
|
|
4514
|
+
draggableId: draggedItem.draggableId,
|
|
4515
|
+
type: draggedItem.type,
|
|
4516
|
+
source: draggedItem.source,
|
|
4517
|
+
};
|
|
4518
|
+
onBeforeDragStart(start);
|
|
4519
|
+
onDragStart(start, {});
|
|
4520
|
+
};
|
|
4521
|
+
const handleDrag = (event) => {
|
|
4522
|
+
const draggedItem = event.nativeEvent;
|
|
4523
|
+
const update = {
|
|
4524
|
+
mode: draggedItem.mode,
|
|
4525
|
+
draggableId: draggedItem.draggableId,
|
|
4526
|
+
type: draggedItem.type,
|
|
4527
|
+
source: draggedItem.source,
|
|
4528
|
+
destination: draggedItem.destination,
|
|
4529
|
+
combine: draggedItem.combine,
|
|
4530
|
+
};
|
|
4531
|
+
onDragUpdate(update, {});
|
|
4532
|
+
};
|
|
4533
|
+
const handleDragEnd = (event) => {
|
|
4534
|
+
const draggedItem = event.nativeEvent;
|
|
4535
|
+
const result = {
|
|
4536
|
+
mode: draggedItem.mode,
|
|
4537
|
+
draggableId: draggedItem.draggableId,
|
|
4538
|
+
type: draggedItem.type,
|
|
4539
|
+
source: draggedItem.source,
|
|
4540
|
+
destination: draggedItem.destination,
|
|
4541
|
+
combine: draggedItem.combine,
|
|
4542
|
+
reason: draggedItem.reason,
|
|
4543
|
+
};
|
|
4544
|
+
onDragEnd(result, {});
|
|
4545
|
+
};
|
|
4546
|
+
return (React.createElement("div", { "data-test-id": "dnd-context-substitute", onDragStart: handleDragStart, onDrag: handleDrag, onDragEnd: handleDragEnd }, children));
|
|
4547
|
+
};
|
|
4548
|
+
|
|
4549
|
+
const DNDProvider = ({ children }) => {
|
|
4550
|
+
const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
|
|
4551
|
+
const draggedItem = useDraggedItemStore((state) => state.draggedItem);
|
|
4552
|
+
const setOnBeforeCaptureId = useDraggedItemStore((state) => state.setOnBeforeCaptureId);
|
|
4553
|
+
const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
|
|
4554
|
+
const updateItem = useDraggedItemStore((state) => state.updateItem);
|
|
4555
|
+
const { onAddComponent, onMoveComponent } = useCanvasInteractions();
|
|
4556
|
+
const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
|
|
4557
|
+
const prevSelectedNodeId = useRef(null);
|
|
4558
|
+
const isTestRun = typeof window !== 'undefined' && Object.prototype.hasOwnProperty.call(window, 'Cypress');
|
|
4559
|
+
const beforeDragStart = ({ source }) => {
|
|
4560
|
+
prevSelectedNodeId.current = selectedNodeId;
|
|
4561
|
+
// Unselect the current node when dragging and remove the outline
|
|
4562
|
+
setSelectedNodeId('');
|
|
4563
|
+
// Set dragging state here to make sure that DnD capture phase has completed
|
|
4564
|
+
setDraggingOnCanvas(true);
|
|
4565
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
4566
|
+
nodeId: '',
|
|
4567
|
+
});
|
|
4568
|
+
if (source.droppableId !== COMPONENT_LIST_ID) {
|
|
4569
|
+
sendMessage(OUTGOING_EVENTS.ComponentMoveStarted, undefined);
|
|
4570
|
+
}
|
|
4571
|
+
};
|
|
4572
|
+
const beforeCapture = ({ draggableId }) => {
|
|
4573
|
+
setOnBeforeCaptureId(draggableId);
|
|
4574
|
+
};
|
|
4575
|
+
const dragStart = (start) => {
|
|
4576
|
+
updateItem(start);
|
|
4577
|
+
};
|
|
4578
|
+
const dragUpdate = (update) => {
|
|
4579
|
+
updateItem(update);
|
|
4580
|
+
};
|
|
4581
|
+
const dragEnd = (dropResult) => {
|
|
4582
|
+
setDraggingOnCanvas(false);
|
|
4583
|
+
setOnBeforeCaptureId('');
|
|
4584
|
+
updateItem();
|
|
4585
|
+
SimulateDnD$1.reset();
|
|
4586
|
+
// If the component is being dropped onto itself, do nothing
|
|
4587
|
+
// This can happen from an apparent race condition where the hovering zone gets set
|
|
4588
|
+
// to the component after its dropped.
|
|
4589
|
+
if (dropResult.destination?.droppableId === dropResult.draggableId) {
|
|
4590
|
+
return;
|
|
4591
|
+
}
|
|
4592
|
+
if (!dropResult.destination) {
|
|
4593
|
+
if (!draggedItem?.destination) {
|
|
4594
|
+
// User cancel drag
|
|
4595
|
+
sendMessage(OUTGOING_EVENTS.ComponentDragCanceled, undefined);
|
|
4596
|
+
//select the previously selected node if drag was canceled
|
|
4597
|
+
if (prevSelectedNodeId.current) {
|
|
4598
|
+
setSelectedNodeId(prevSelectedNodeId.current);
|
|
4599
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
4600
|
+
nodeId: prevSelectedNodeId.current,
|
|
4601
|
+
});
|
|
4602
|
+
prevSelectedNodeId.current = null;
|
|
4603
|
+
}
|
|
4604
|
+
return;
|
|
4605
|
+
}
|
|
4606
|
+
// Use the destination from the draggedItem (when clicking the canvas)
|
|
4607
|
+
dropResult.destination = draggedItem.destination;
|
|
4608
|
+
}
|
|
4609
|
+
// New component added to canvas
|
|
4610
|
+
if (dropResult.source.droppableId.startsWith('component-list')) {
|
|
4611
|
+
onAddComponent(dropResult);
|
|
4612
|
+
}
|
|
4613
|
+
else {
|
|
4614
|
+
onMoveComponent(dropResult);
|
|
4615
|
+
}
|
|
4616
|
+
// If a node was previously selected prior to dragging, re-select it
|
|
4617
|
+
setSelectedNodeId(dropResult.draggableId);
|
|
4618
|
+
sendMessage(OUTGOING_EVENTS.ComponentMoveEnded, undefined);
|
|
4619
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
4620
|
+
nodeId: dropResult.draggableId,
|
|
4621
|
+
});
|
|
4622
|
+
};
|
|
4623
|
+
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)));
|
|
4624
|
+
};
|
|
4625
|
+
|
|
4626
|
+
/**
|
|
4627
|
+
* This hook gets the element co-ordinates of a specified element in the DOM
|
|
4628
|
+
* and sends the DOM Rect to the client app
|
|
4629
|
+
*/
|
|
4630
|
+
const useSelectedInstanceCoordinates = ({ node }) => {
|
|
4631
|
+
const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
|
|
4632
|
+
useEffect(() => {
|
|
4633
|
+
if (selectedNodeId !== node.data.id) {
|
|
4634
|
+
return;
|
|
4635
|
+
}
|
|
4636
|
+
// Allows the drop animation to finish before
|
|
4637
|
+
// calculating the components coordinates
|
|
4638
|
+
setTimeout(() => {
|
|
4639
|
+
sendSelectedComponentCoordinates(node.data.id);
|
|
4640
|
+
}, 10);
|
|
4641
|
+
}, [node, selectedNodeId]);
|
|
4642
|
+
const selectedElement = node.data.id
|
|
4643
|
+
? document.querySelector(`[data-cf-node-id="${selectedNodeId}"]`)
|
|
4644
|
+
: undefined;
|
|
4645
|
+
return selectedElement ? getElementCoordinates(selectedElement) : null;
|
|
3923
4646
|
};
|
|
3924
4647
|
|
|
3925
4648
|
/**
|
|
@@ -3967,13 +4690,15 @@ const getUnboundValues = ({ key, fallback, unboundValues, }) => {
|
|
|
3967
4690
|
return get$1(unboundValues, lodashPath, fallback);
|
|
3968
4691
|
};
|
|
3969
4692
|
|
|
3970
|
-
const useComponentProps = ({ node, resolveDesignValue, definition, options, }) => {
|
|
4693
|
+
const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, renderDropzone, definition, options, userIsDragging, requiresDragWrapper, }) => {
|
|
3971
4694
|
const unboundValues = useEditorStore((state) => state.unboundValues);
|
|
3972
4695
|
const hyperlinkPattern = useEditorStore((state) => state.hyperLinkPattern);
|
|
3973
4696
|
const locale = useEditorStore((state) => state.locale);
|
|
3974
4697
|
const dataSource = useEditorStore((state) => state.dataSource);
|
|
3975
4698
|
const entityStore = useEntityStore((state) => state.entityStore);
|
|
3976
|
-
const
|
|
4699
|
+
const draggingId = useDraggedItemStore((state) => state.onBeforeCaptureId);
|
|
4700
|
+
const nodeRect = useDraggedItemStore((state) => state.domRect);
|
|
4701
|
+
const isEmptyZone = !node.children.length;
|
|
3977
4702
|
const props = useMemo(() => {
|
|
3978
4703
|
const propsBase = {
|
|
3979
4704
|
cfSsrClassName: node.data.props.cfSsrClassName
|
|
@@ -4063,223 +4788,1133 @@ const useComponentProps = ({ node, resolveDesignValue, definition, options, }) =
|
|
|
4063
4788
|
return { ...acc };
|
|
4064
4789
|
}
|
|
4065
4790
|
}, {});
|
|
4791
|
+
const slotProps = {};
|
|
4792
|
+
if (definition.slots) {
|
|
4793
|
+
for (const slotId in definition.slots) {
|
|
4794
|
+
slotProps[slotId] = renderDropzone(node, {
|
|
4795
|
+
zoneId: [node.data.id, slotId].join('|'),
|
|
4796
|
+
});
|
|
4797
|
+
}
|
|
4798
|
+
}
|
|
4066
4799
|
return {
|
|
4067
4800
|
...propsBase,
|
|
4068
4801
|
...extractedProps,
|
|
4802
|
+
...slotProps,
|
|
4069
4803
|
};
|
|
4070
4804
|
}, [
|
|
4071
4805
|
hyperlinkPattern,
|
|
4072
4806
|
node,
|
|
4073
|
-
locale,
|
|
4074
|
-
definition,
|
|
4075
|
-
resolveDesignValue,
|
|
4076
|
-
dataSource,
|
|
4807
|
+
locale,
|
|
4808
|
+
definition,
|
|
4809
|
+
resolveDesignValue,
|
|
4810
|
+
dataSource,
|
|
4811
|
+
areEntitiesFetched,
|
|
4812
|
+
unboundValues,
|
|
4813
|
+
entityStore,
|
|
4814
|
+
renderDropzone,
|
|
4815
|
+
]);
|
|
4816
|
+
const cfStyles = useMemo(() => buildCfStyles(props), [props]);
|
|
4817
|
+
const cfVisibility = props['cfVisibility'];
|
|
4818
|
+
const isAssemblyBlock = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
|
|
4819
|
+
const isSingleColumn = node?.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id;
|
|
4820
|
+
const isStructureComponent = isContentfulStructureComponent(node?.data.blockId);
|
|
4821
|
+
const isPatternNode = node.type === ASSEMBLY_NODE_TYPE;
|
|
4822
|
+
const { overrideStyles, wrapperStyles } = useMemo(() => {
|
|
4823
|
+
// Move size styles to the wrapping div and override the component styles
|
|
4824
|
+
const overrideStyles = {};
|
|
4825
|
+
const wrapperStyles = { width: options?.wrapContainerWidth };
|
|
4826
|
+
if (requiresDragWrapper) {
|
|
4827
|
+
// when element is marked by user as not-visible, on that element the node `display: none !important`
|
|
4828
|
+
// will be set and it will disappear. However, when such a node has a wrapper div, the wrapper
|
|
4829
|
+
// should not have any css properties (at least not ones which force size), as such div should
|
|
4830
|
+
// simply be a zero height wrapper around element with `display: none !important`.
|
|
4831
|
+
// Hence we guard all wrapperStyles with `cfVisibility` check.
|
|
4832
|
+
if (cfVisibility && cfStyles.width)
|
|
4833
|
+
wrapperStyles.width = cfStyles.width;
|
|
4834
|
+
if (cfVisibility && cfStyles.height)
|
|
4835
|
+
wrapperStyles.height = cfStyles.height;
|
|
4836
|
+
if (cfVisibility && cfStyles.maxWidth)
|
|
4837
|
+
wrapperStyles.maxWidth = cfStyles.maxWidth;
|
|
4838
|
+
if (cfVisibility && cfStyles.margin)
|
|
4839
|
+
wrapperStyles.margin = cfStyles.margin;
|
|
4840
|
+
}
|
|
4841
|
+
// Override component styles to fill the wrapper
|
|
4842
|
+
if (wrapperStyles.width)
|
|
4843
|
+
overrideStyles.width = '100%';
|
|
4844
|
+
if (wrapperStyles.height)
|
|
4845
|
+
overrideStyles.height = '100%';
|
|
4846
|
+
if (wrapperStyles.margin)
|
|
4847
|
+
overrideStyles.margin = '0';
|
|
4848
|
+
if (wrapperStyles.maxWidth)
|
|
4849
|
+
overrideStyles.maxWidth = 'none';
|
|
4850
|
+
// Prevent the dragging element from changing sizes when it has a percentage width or height
|
|
4851
|
+
if (draggingId === node.data.id && nodeRect) {
|
|
4852
|
+
if (requiresDragWrapper) {
|
|
4853
|
+
if (isPercentValue(cfStyles.width))
|
|
4854
|
+
wrapperStyles.maxWidth = nodeRect.width;
|
|
4855
|
+
if (isPercentValue(cfStyles.height))
|
|
4856
|
+
wrapperStyles.maxHeight = nodeRect.height;
|
|
4857
|
+
}
|
|
4858
|
+
else {
|
|
4859
|
+
if (isPercentValue(cfStyles.width))
|
|
4860
|
+
overrideStyles.maxWidth = nodeRect.width;
|
|
4861
|
+
if (isPercentValue(cfStyles.height))
|
|
4862
|
+
overrideStyles.maxHeight = nodeRect.height;
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
return { overrideStyles, wrapperStyles };
|
|
4866
|
+
}, [
|
|
4867
|
+
cfStyles,
|
|
4868
|
+
options?.wrapContainerWidth,
|
|
4869
|
+
requiresDragWrapper,
|
|
4870
|
+
node.data.id,
|
|
4871
|
+
draggingId,
|
|
4872
|
+
nodeRect,
|
|
4873
|
+
cfVisibility,
|
|
4874
|
+
]);
|
|
4875
|
+
// Styles that will be applied to the component element
|
|
4876
|
+
// This has to be memoized to avoid recreating the styles in useEditorModeClassName on every render
|
|
4877
|
+
const componentStyles = useMemo(() => ({
|
|
4878
|
+
...cfStyles,
|
|
4879
|
+
...overrideStyles,
|
|
4880
|
+
...(isEmptyZone &&
|
|
4881
|
+
isStructureWithRelativeHeight(node?.data.blockId, cfStyles.height) && {
|
|
4882
|
+
minHeight: EMPTY_CONTAINER_HEIGHT,
|
|
4883
|
+
}),
|
|
4884
|
+
...(userIsDragging &&
|
|
4885
|
+
isStructureComponent &&
|
|
4886
|
+
!isSingleColumn &&
|
|
4887
|
+
!isAssemblyBlock && {
|
|
4888
|
+
padding: addExtraDropzonePadding(cfStyles.padding?.toString() || '0 0 0 0'),
|
|
4889
|
+
}),
|
|
4890
|
+
}), [
|
|
4891
|
+
cfStyles,
|
|
4892
|
+
isAssemblyBlock,
|
|
4893
|
+
isEmptyZone,
|
|
4894
|
+
isSingleColumn,
|
|
4895
|
+
isStructureComponent,
|
|
4896
|
+
node?.data.blockId,
|
|
4897
|
+
overrideStyles,
|
|
4898
|
+
userIsDragging,
|
|
4899
|
+
]);
|
|
4900
|
+
const componentClass = useEditorModeClassName({
|
|
4901
|
+
styles: componentStyles,
|
|
4902
|
+
nodeId: node.data.id,
|
|
4903
|
+
});
|
|
4904
|
+
const sharedProps = {
|
|
4905
|
+
'data-cf-node-id': node.data.id,
|
|
4906
|
+
'data-cf-node-block-id': node.data.blockId,
|
|
4907
|
+
'data-cf-node-block-type': node.type,
|
|
4908
|
+
className: props.cfSsrClassName ?? componentClass,
|
|
4909
|
+
...(definition?.children ? { children: renderDropzone(node) } : {}),
|
|
4910
|
+
};
|
|
4911
|
+
const customComponentProps = {
|
|
4912
|
+
...sharedProps,
|
|
4913
|
+
// Allows custom components to render differently in the editor. This needs to be activated
|
|
4914
|
+
// through options as the component has to be aware of this prop to not cause any React warnings.
|
|
4915
|
+
...(options?.enableCustomEditorView ? { isInExpEditorMode: true } : {}),
|
|
4916
|
+
...sanitizeNodeProps(props),
|
|
4917
|
+
};
|
|
4918
|
+
const structuralOrPatternComponentProps = {
|
|
4919
|
+
...sharedProps,
|
|
4920
|
+
editorMode: true,
|
|
4921
|
+
node,
|
|
4922
|
+
renderDropzone,
|
|
4923
|
+
};
|
|
4924
|
+
return {
|
|
4925
|
+
componentProps: isStructureComponent || isPatternNode
|
|
4926
|
+
? structuralOrPatternComponentProps
|
|
4927
|
+
: customComponentProps,
|
|
4928
|
+
componentStyles,
|
|
4929
|
+
wrapperStyles,
|
|
4930
|
+
};
|
|
4931
|
+
};
|
|
4932
|
+
const addExtraDropzonePadding = (padding) => padding
|
|
4933
|
+
.split(' ')
|
|
4934
|
+
.map((value) => parseFloat(value) === 0 ? `${DRAG_PADDING}px` : `calc(${value} + ${DRAG_PADDING}px)`)
|
|
4935
|
+
.join(' ');
|
|
4936
|
+
const isPercentValue = (value) => typeof value === 'string' && value.endsWith('%');
|
|
4937
|
+
|
|
4938
|
+
class ImportedComponentError extends Error {
|
|
4939
|
+
constructor(message) {
|
|
4940
|
+
super(message);
|
|
4941
|
+
this.name = 'ImportedComponentError';
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
class ExperienceSDKError extends Error {
|
|
4945
|
+
constructor(message) {
|
|
4946
|
+
super(message);
|
|
4947
|
+
this.name = 'ExperienceSDKError';
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
class ImportedComponentErrorBoundary extends React.Component {
|
|
4951
|
+
componentDidCatch(error, _errorInfo) {
|
|
4952
|
+
if (error.name === 'ImportedComponentError' || error.name === 'ExperienceSDKError') {
|
|
4953
|
+
// This error was already handled by a nested error boundary and should be passed upwards
|
|
4954
|
+
// We have to do this as we wrap every component on every layer with this error boundary and
|
|
4955
|
+
// thus an error deep in the tree bubbles through many layers of error boundaries.
|
|
4956
|
+
throw error;
|
|
4957
|
+
}
|
|
4958
|
+
// Differentiate between custom and SDK-provided components for error tracking
|
|
4959
|
+
const ErrorClass = isContentfulComponent(this.props.componentId)
|
|
4960
|
+
? ExperienceSDKError
|
|
4961
|
+
: ImportedComponentError;
|
|
4962
|
+
const err = new ErrorClass(error.message);
|
|
4963
|
+
err.stack = error.stack;
|
|
4964
|
+
throw err;
|
|
4965
|
+
}
|
|
4966
|
+
render() {
|
|
4967
|
+
return this.props.children;
|
|
4968
|
+
}
|
|
4969
|
+
}
|
|
4970
|
+
|
|
4971
|
+
const MissingComponentPlaceholder = ({ blockId }) => {
|
|
4972
|
+
return (React.createElement("div", { style: {
|
|
4973
|
+
border: '1px solid red',
|
|
4974
|
+
width: '100%',
|
|
4975
|
+
height: '100%',
|
|
4976
|
+
} },
|
|
4977
|
+
"Missing component '",
|
|
4978
|
+
blockId,
|
|
4979
|
+
"'"));
|
|
4980
|
+
};
|
|
4981
|
+
|
|
4982
|
+
const CircularDependencyErrorPlaceholder = forwardRef(({ wrappingPatternIds, ...props }, ref) => {
|
|
4983
|
+
const entityStore = useEntityStore((state) => state.entityStore);
|
|
4984
|
+
return (React.createElement("div", { ...props,
|
|
4985
|
+
// Pass through ref to avoid DND errors being logged
|
|
4986
|
+
ref: ref, "data-cf-node-error": "circular-pattern-dependency", style: {
|
|
4987
|
+
border: '1px solid red',
|
|
4988
|
+
background: 'rgba(255, 0, 0, 0.1)',
|
|
4989
|
+
padding: '1rem 1rem 0 1rem',
|
|
4990
|
+
width: '100%',
|
|
4991
|
+
height: '100%',
|
|
4992
|
+
} },
|
|
4993
|
+
"Circular usage of patterns detected:",
|
|
4994
|
+
React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
|
|
4995
|
+
const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
|
|
4996
|
+
const entry = entityStore.getEntityFromLink(entryLink);
|
|
4997
|
+
const entryTitle = entry?.fields?.title;
|
|
4998
|
+
const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
|
|
4999
|
+
return React.createElement("li", { key: patternId }, text);
|
|
5000
|
+
}))));
|
|
5001
|
+
});
|
|
5002
|
+
CircularDependencyErrorPlaceholder.displayName = 'CircularDependencyErrorPlaceholder';
|
|
5003
|
+
|
|
5004
|
+
const useComponent = ({ node, resolveDesignValue, renderDropzone, userIsDragging, wrappingPatternIds, }) => {
|
|
5005
|
+
const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
|
|
5006
|
+
const tree = useTreeStore((state) => state.tree);
|
|
5007
|
+
const componentRegistration = useMemo(() => {
|
|
5008
|
+
let registration = componentRegistry.get(node.data.blockId);
|
|
5009
|
+
if (node.type === ASSEMBLY_NODE_TYPE && !registration) {
|
|
5010
|
+
registration = createAssemblyRegistration({
|
|
5011
|
+
definitionId: node.data.blockId,
|
|
5012
|
+
component: Assembly,
|
|
5013
|
+
});
|
|
5014
|
+
}
|
|
5015
|
+
if (!registration) {
|
|
5016
|
+
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.`);
|
|
5017
|
+
return undefined;
|
|
5018
|
+
}
|
|
5019
|
+
return registration;
|
|
5020
|
+
}, [node]);
|
|
5021
|
+
const componentId = node.data.id;
|
|
5022
|
+
const isPatternNode = node.type === ASSEMBLY_NODE_TYPE;
|
|
5023
|
+
const isPatternComponent = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
|
|
5024
|
+
const parentComponentNode = getItem({ id: node.parentId }, tree);
|
|
5025
|
+
const isNestedPattern = isPatternNode &&
|
|
5026
|
+
[ASSEMBLY_BLOCK_NODE_TYPE, ASSEMBLY_NODE_TYPE].includes(parentComponentNode?.type ?? '');
|
|
5027
|
+
const isStructureComponent = isContentfulStructureComponent(node.data.blockId);
|
|
5028
|
+
const requiresDragWrapper = !isPatternNode && !isStructureComponent && !componentRegistration?.options?.wrapComponent;
|
|
5029
|
+
const { componentProps, wrapperStyles } = useComponentProps({
|
|
5030
|
+
node,
|
|
4077
5031
|
areEntitiesFetched,
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
...cfStyles,
|
|
4085
|
-
...(!node.children.length &&
|
|
4086
|
-
isStructureWithRelativeHeight(node.data.blockId, cfStyles.height) && {
|
|
4087
|
-
minHeight: EMPTY_CONTAINER_HEIGHT$1,
|
|
4088
|
-
}),
|
|
4089
|
-
}), [cfStyles, node.children.length, node.data.blockId]);
|
|
4090
|
-
const cfCsrClassName = useEditorModeClassName({
|
|
4091
|
-
styles: componentStyles,
|
|
4092
|
-
nodeId: node.data.id,
|
|
5032
|
+
resolveDesignValue,
|
|
5033
|
+
renderDropzone,
|
|
5034
|
+
definition: componentRegistration?.definition,
|
|
5035
|
+
options: componentRegistration?.options,
|
|
5036
|
+
userIsDragging,
|
|
5037
|
+
requiresDragWrapper,
|
|
4093
5038
|
});
|
|
4094
|
-
const
|
|
4095
|
-
const
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
5039
|
+
const elementToRender = (props) => {
|
|
5040
|
+
const { dragProps = {} } = props || {};
|
|
5041
|
+
const { children, innerRef, Tag = 'div', ToolTipAndPlaceholder, style, ...rest } = dragProps;
|
|
5042
|
+
const { 'data-cf-node-block-id': dataCfNodeBlockId, 'data-cf-node-block-type': dataCfNodeBlockType, 'data-cf-node-id': dataCfNodeId, } = componentProps;
|
|
5043
|
+
const refCallback = (refNode) => {
|
|
5044
|
+
if (innerRef && refNode)
|
|
5045
|
+
innerRef(refNode);
|
|
4100
5046
|
};
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
if (isStructureComponent) {
|
|
4104
|
-
return {
|
|
4105
|
-
...sharedProps,
|
|
4106
|
-
editorMode: true,
|
|
4107
|
-
node,
|
|
4108
|
-
};
|
|
5047
|
+
if (!componentRegistration) {
|
|
5048
|
+
return React.createElement(MissingComponentPlaceholder, { blockId: node.data.blockId });
|
|
4109
5049
|
}
|
|
5050
|
+
if (node.data.blockId && wrappingPatternIds.has(node.data.blockId)) {
|
|
5051
|
+
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 }));
|
|
5052
|
+
}
|
|
5053
|
+
const element = React.createElement(ImportedComponentErrorBoundary, { componentId: node.data.blockId }, React.createElement(componentRegistration.component, {
|
|
5054
|
+
...componentProps,
|
|
5055
|
+
dragProps,
|
|
5056
|
+
}));
|
|
5057
|
+
if (!requiresDragWrapper) {
|
|
5058
|
+
return element;
|
|
5059
|
+
}
|
|
5060
|
+
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 },
|
|
5061
|
+
ToolTipAndPlaceholder,
|
|
5062
|
+
element));
|
|
5063
|
+
};
|
|
5064
|
+
return {
|
|
5065
|
+
node,
|
|
5066
|
+
parentComponentNode,
|
|
5067
|
+
isAssembly: isPatternNode,
|
|
5068
|
+
isPatternNode,
|
|
5069
|
+
isPatternComponent,
|
|
5070
|
+
isNestedPattern,
|
|
5071
|
+
componentId,
|
|
5072
|
+
elementToRender,
|
|
5073
|
+
definition: componentRegistration?.definition,
|
|
5074
|
+
};
|
|
5075
|
+
};
|
|
5076
|
+
|
|
5077
|
+
const calcOffsetLeft = (parentElement, placeholderWidth, nodeWidth) => {
|
|
5078
|
+
if (!parentElement) {
|
|
5079
|
+
return 0;
|
|
5080
|
+
}
|
|
5081
|
+
const alignItems = window.getComputedStyle(parentElement).alignItems;
|
|
5082
|
+
if (alignItems === 'center') {
|
|
5083
|
+
return -(placeholderWidth - nodeWidth) / 2;
|
|
5084
|
+
}
|
|
5085
|
+
if (alignItems === 'end') {
|
|
5086
|
+
return -placeholderWidth + nodeWidth + 2;
|
|
5087
|
+
}
|
|
5088
|
+
return 0;
|
|
5089
|
+
};
|
|
5090
|
+
const calcOffsetTop = (parentElement, placeholderHeight, nodeHeight) => {
|
|
5091
|
+
if (!parentElement) {
|
|
5092
|
+
return 0;
|
|
5093
|
+
}
|
|
5094
|
+
const alignItems = window.getComputedStyle(parentElement).alignItems;
|
|
5095
|
+
if (alignItems === 'center') {
|
|
5096
|
+
return -(placeholderHeight - nodeHeight) / 2;
|
|
5097
|
+
}
|
|
5098
|
+
if (alignItems === 'end') {
|
|
5099
|
+
return -placeholderHeight + nodeHeight + 2;
|
|
5100
|
+
}
|
|
5101
|
+
return 0;
|
|
5102
|
+
};
|
|
5103
|
+
const getPaddingOffset = (element) => {
|
|
5104
|
+
const paddingLeft = parseFloat(window.getComputedStyle(element).paddingLeft);
|
|
5105
|
+
const paddingRight = parseFloat(window.getComputedStyle(element).paddingRight);
|
|
5106
|
+
const paddingTop = parseFloat(window.getComputedStyle(element).paddingTop);
|
|
5107
|
+
const paddingBottom = parseFloat(window.getComputedStyle(element).paddingBottom);
|
|
5108
|
+
const horizontalOffset = paddingLeft + paddingRight;
|
|
5109
|
+
const verticalOffset = paddingTop + paddingBottom;
|
|
5110
|
+
return [horizontalOffset, verticalOffset];
|
|
5111
|
+
};
|
|
5112
|
+
/**
|
|
5113
|
+
* Calculate the size and position of the dropzone indicator
|
|
5114
|
+
* when dragging a new component onto the canvas
|
|
5115
|
+
*/
|
|
5116
|
+
const calcNewComponentStyles = (params) => {
|
|
5117
|
+
const { destinationIndex, elementIndex, dropzoneElementId, id, direction, totalIndexes } = params;
|
|
5118
|
+
const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
|
|
5119
|
+
const isHorizontal = direction === 'horizontal';
|
|
5120
|
+
const isRightAlign = isHorizontal && isEnd;
|
|
5121
|
+
const isBottomAlign = !isHorizontal && isEnd;
|
|
5122
|
+
const dropzone = document.querySelector(`[data-rfd-droppable-id="${dropzoneElementId}"]`);
|
|
5123
|
+
const element = document.querySelector(`[data-ctfl-draggable-id="${id}"]`);
|
|
5124
|
+
if (!dropzone || !element) {
|
|
5125
|
+
return emptyStyles;
|
|
5126
|
+
}
|
|
5127
|
+
const elementSizes = element.getBoundingClientRect();
|
|
5128
|
+
const dropzoneSizes = dropzone.getBoundingClientRect();
|
|
5129
|
+
const [horizontalPadding, verticalPadding] = getPaddingOffset(dropzone);
|
|
5130
|
+
const width = isHorizontal ? DRAGGABLE_WIDTH : dropzoneSizes.width - horizontalPadding;
|
|
5131
|
+
const height = isHorizontal ? dropzoneSizes.height - verticalPadding : DRAGGABLE_HEIGHT;
|
|
5132
|
+
const top = isHorizontal
|
|
5133
|
+
? calcOffsetTop(element.parentElement, height, elementSizes.height)
|
|
5134
|
+
: -height;
|
|
5135
|
+
const left = isHorizontal
|
|
5136
|
+
? -width
|
|
5137
|
+
: calcOffsetLeft(element.parentElement, width, elementSizes.width);
|
|
5138
|
+
return {
|
|
5139
|
+
width,
|
|
5140
|
+
height,
|
|
5141
|
+
top: !isBottomAlign ? top : 'unset',
|
|
5142
|
+
right: isRightAlign ? -width : 'unset',
|
|
5143
|
+
bottom: isBottomAlign ? -height : 'unset',
|
|
5144
|
+
left: !isRightAlign ? left : 'unset',
|
|
5145
|
+
};
|
|
5146
|
+
};
|
|
5147
|
+
/**
|
|
5148
|
+
* Calculate the size and position of the dropzone indicator
|
|
5149
|
+
* when moving an existing component on the canvas
|
|
5150
|
+
*/
|
|
5151
|
+
const calcMovementStyles = (params) => {
|
|
5152
|
+
const { destinationIndex, sourceIndex, destinationId, sourceId, elementIndex, dropzoneElementId, id, direction, totalIndexes, draggableId, } = params;
|
|
5153
|
+
const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
|
|
5154
|
+
const isHorizontal = direction === 'horizontal';
|
|
5155
|
+
const isSameZone = destinationId === sourceId;
|
|
5156
|
+
const isBelowSourceIndex = destinationIndex > sourceIndex;
|
|
5157
|
+
const isRightAlign = isHorizontal && (isEnd || (isSameZone && isBelowSourceIndex));
|
|
5158
|
+
const isBottomAlign = !isHorizontal && (isEnd || (isSameZone && isBelowSourceIndex));
|
|
5159
|
+
const dropzone = document.querySelector(`[data-rfd-droppable-id="${dropzoneElementId}"]`);
|
|
5160
|
+
const draggable = document.querySelector(`[data-rfd-draggable-id="${draggableId}"]`);
|
|
5161
|
+
const element = document.querySelector(`[data-ctfl-draggable-id="${id}"]`);
|
|
5162
|
+
if (!dropzone || !element || !draggable) {
|
|
5163
|
+
return emptyStyles;
|
|
5164
|
+
}
|
|
5165
|
+
const elementSizes = element.getBoundingClientRect();
|
|
5166
|
+
const dropzoneSizes = dropzone.getBoundingClientRect();
|
|
5167
|
+
const draggableSizes = draggable.getBoundingClientRect();
|
|
5168
|
+
const [horizontalPadding, verticalPadding] = getPaddingOffset(dropzone);
|
|
5169
|
+
const width = isHorizontal ? draggableSizes.width : dropzoneSizes.width - horizontalPadding;
|
|
5170
|
+
const height = isHorizontal ? dropzoneSizes.height - verticalPadding : draggableSizes.height;
|
|
5171
|
+
const top = isHorizontal
|
|
5172
|
+
? calcOffsetTop(element.parentElement, height, elementSizes.height)
|
|
5173
|
+
: -height;
|
|
5174
|
+
const left = isHorizontal
|
|
5175
|
+
? -width
|
|
5176
|
+
: calcOffsetLeft(element.parentElement, width, elementSizes.width);
|
|
5177
|
+
return {
|
|
5178
|
+
width,
|
|
5179
|
+
height,
|
|
5180
|
+
top: !isBottomAlign ? top : 'unset',
|
|
5181
|
+
right: isRightAlign ? -width : 'unset',
|
|
5182
|
+
bottom: isBottomAlign ? -height : 'unset',
|
|
5183
|
+
left: !isRightAlign ? left : 'unset',
|
|
5184
|
+
};
|
|
5185
|
+
};
|
|
5186
|
+
const emptyStyles = { width: 0, height: 0 };
|
|
5187
|
+
const calcPlaceholderStyles = (params) => {
|
|
5188
|
+
const { isDraggingOver, sourceId } = params;
|
|
5189
|
+
if (!isDraggingOver) {
|
|
5190
|
+
return emptyStyles;
|
|
5191
|
+
}
|
|
5192
|
+
if (sourceId === COMPONENT_LIST_ID) {
|
|
5193
|
+
return calcNewComponentStyles(params);
|
|
5194
|
+
}
|
|
5195
|
+
return calcMovementStyles(params);
|
|
5196
|
+
};
|
|
5197
|
+
const Placeholder = (props) => {
|
|
5198
|
+
const sourceIndex = useDraggedItemStore((state) => state.draggedItem?.source.index) ?? -1;
|
|
5199
|
+
const draggableId = useDraggedItemStore((state) => state.draggedItem?.draggableId) ?? '';
|
|
5200
|
+
const sourceId = useDraggedItemStore((state) => state.draggedItem?.source.droppableId) ?? '';
|
|
5201
|
+
const destinationIndex = useDraggedItemStore((state) => state.draggedItem?.destination?.index) ?? -1;
|
|
5202
|
+
const destinationId = useDraggedItemStore((state) => state.draggedItem?.destination?.droppableId) ?? '';
|
|
5203
|
+
const { elementIndex, totalIndexes, isDraggingOver } = props;
|
|
5204
|
+
const isActive = destinationIndex === elementIndex;
|
|
5205
|
+
const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
|
|
5206
|
+
const isVisible = isEnd || isActive;
|
|
5207
|
+
const isComponentList = destinationId === COMPONENT_LIST_ID;
|
|
5208
|
+
return (!isComponentList &&
|
|
5209
|
+
isDraggingOver &&
|
|
5210
|
+
isVisible && (React.createElement("div", { style: {
|
|
5211
|
+
...calcPlaceholderStyles({
|
|
5212
|
+
...props,
|
|
5213
|
+
sourceId,
|
|
5214
|
+
sourceIndex,
|
|
5215
|
+
destinationId,
|
|
5216
|
+
destinationIndex,
|
|
5217
|
+
draggableId,
|
|
5218
|
+
}),
|
|
5219
|
+
backgroundColor: 'rgba(var(--exp-builder-blue300-rgb), 0.5)',
|
|
5220
|
+
position: 'absolute',
|
|
5221
|
+
pointerEvents: 'none',
|
|
5222
|
+
} })));
|
|
5223
|
+
};
|
|
5224
|
+
|
|
5225
|
+
var css_248z$2 = ".styles-module_hitbox__i3wKV {\n position: fixed;\n pointer-events: all;\n}\n";
|
|
5226
|
+
var styles$2 = {"hitbox":"styles-module_hitbox__i3wKV"};
|
|
5227
|
+
styleInject(css_248z$2);
|
|
5228
|
+
|
|
5229
|
+
const useZoneStore = create()((set) => ({
|
|
5230
|
+
zones: {},
|
|
5231
|
+
hoveringZone: '',
|
|
5232
|
+
setHoveringZone(zoneId) {
|
|
5233
|
+
set({
|
|
5234
|
+
hoveringZone: zoneId,
|
|
5235
|
+
});
|
|
5236
|
+
},
|
|
5237
|
+
upsertZone(id, data) {
|
|
5238
|
+
set(produce((state) => {
|
|
5239
|
+
state.zones[id] = { ...(state.zones[id] || {}), ...data };
|
|
5240
|
+
}));
|
|
5241
|
+
},
|
|
5242
|
+
}));
|
|
5243
|
+
|
|
5244
|
+
const { WIDTH, HEIGHT, INITIAL_OFFSET, OFFSET_INCREMENT, MIN_HEIGHT, MIN_DEPTH_HEIGHT, DEEP_ZONE } = HITBOX;
|
|
5245
|
+
const calcOffsetDepth = (depth) => {
|
|
5246
|
+
return INITIAL_OFFSET - OFFSET_INCREMENT * depth;
|
|
5247
|
+
};
|
|
5248
|
+
const getHitboxStyles = ({ direction, zoneDepth, domRect, scrollY, offsetRect, }) => {
|
|
5249
|
+
if (!domRect) {
|
|
4110
5250
|
return {
|
|
4111
|
-
|
|
4112
|
-
// Allows custom components to render differently in the editor. This needs to be activated
|
|
4113
|
-
// through options as the component has to be aware of this prop to not cause any React warnings.
|
|
4114
|
-
...(options?.enableCustomEditorView ? { isInExpEditorMode: true } : {}),
|
|
4115
|
-
...sanitizeNodeProps(props),
|
|
5251
|
+
display: 'none',
|
|
4116
5252
|
};
|
|
4117
|
-
}
|
|
4118
|
-
|
|
5253
|
+
}
|
|
5254
|
+
const { width, height, top, left, bottom, right } = domRect;
|
|
5255
|
+
const { height: offsetHeight, width: offsetWidth } = offsetRect || { height: 0, width: 0 };
|
|
5256
|
+
const MAX_SELF_HEIGHT = DRAGGABLE_HEIGHT * 2;
|
|
5257
|
+
const isDeepZone = zoneDepth > DEEP_ZONE;
|
|
5258
|
+
const isAboveMaxHeight = height > MAX_SELF_HEIGHT;
|
|
5259
|
+
switch (direction) {
|
|
5260
|
+
case HitboxDirection.TOP:
|
|
5261
|
+
return {
|
|
5262
|
+
width,
|
|
5263
|
+
height: HEIGHT,
|
|
5264
|
+
top: top + offsetHeight - calcOffsetDepth(zoneDepth) - scrollY,
|
|
5265
|
+
left,
|
|
5266
|
+
zIndex: 100 + zoneDepth,
|
|
5267
|
+
};
|
|
5268
|
+
case HitboxDirection.BOTTOM:
|
|
5269
|
+
return {
|
|
5270
|
+
width,
|
|
5271
|
+
height: HEIGHT,
|
|
5272
|
+
top: bottom + offsetHeight + calcOffsetDepth(zoneDepth) - scrollY,
|
|
5273
|
+
left,
|
|
5274
|
+
zIndex: 100 + zoneDepth,
|
|
5275
|
+
};
|
|
5276
|
+
case HitboxDirection.LEFT:
|
|
5277
|
+
return {
|
|
5278
|
+
width: WIDTH,
|
|
5279
|
+
height: height - HEIGHT,
|
|
5280
|
+
left: left + offsetWidth - calcOffsetDepth(zoneDepth) - WIDTH / 2,
|
|
5281
|
+
top: top + HEIGHT / 2 - scrollY,
|
|
5282
|
+
zIndex: 100 + zoneDepth,
|
|
5283
|
+
};
|
|
5284
|
+
case HitboxDirection.RIGHT:
|
|
5285
|
+
return {
|
|
5286
|
+
width: WIDTH,
|
|
5287
|
+
height: height - HEIGHT,
|
|
5288
|
+
left: right + offsetWidth + calcOffsetDepth(zoneDepth) - WIDTH / 2,
|
|
5289
|
+
top: top + HEIGHT / 2 - scrollY,
|
|
5290
|
+
zIndex: 100 + zoneDepth,
|
|
5291
|
+
};
|
|
5292
|
+
case HitboxDirection.SELF_VERTICAL: {
|
|
5293
|
+
if (isAboveMaxHeight && !isDeepZone) {
|
|
5294
|
+
return { display: 'none' };
|
|
5295
|
+
}
|
|
5296
|
+
const selfHeight = isDeepZone ? MIN_DEPTH_HEIGHT : MIN_HEIGHT;
|
|
5297
|
+
return {
|
|
5298
|
+
width,
|
|
5299
|
+
height: selfHeight,
|
|
5300
|
+
left,
|
|
5301
|
+
top: top + height / 2 - selfHeight / 2 - scrollY,
|
|
5302
|
+
zIndex: 1000 + zoneDepth,
|
|
5303
|
+
};
|
|
5304
|
+
}
|
|
5305
|
+
case HitboxDirection.SELF_HORIZONTAL: {
|
|
5306
|
+
if (width > DRAGGABLE_WIDTH) {
|
|
5307
|
+
return { display: 'none' };
|
|
5308
|
+
}
|
|
5309
|
+
return {
|
|
5310
|
+
width: width - DRAGGABLE_WIDTH * 2,
|
|
5311
|
+
height,
|
|
5312
|
+
left: left + DRAGGABLE_WIDTH,
|
|
5313
|
+
top: top - scrollY,
|
|
5314
|
+
zIndex: 1000 + zoneDepth,
|
|
5315
|
+
};
|
|
5316
|
+
}
|
|
5317
|
+
default:
|
|
5318
|
+
return {};
|
|
5319
|
+
}
|
|
4119
5320
|
};
|
|
4120
5321
|
|
|
4121
|
-
|
|
4122
|
-
const
|
|
4123
|
-
const
|
|
4124
|
-
|
|
4125
|
-
|
|
5322
|
+
const Hitboxes = ({ zoneId, parentZoneId, isEmptyZone }) => {
|
|
5323
|
+
const tree = useTreeStore((state) => state.tree);
|
|
5324
|
+
const isDraggingOnCanvas = useDraggedItemStore((state) => state.isDraggingOnCanvas);
|
|
5325
|
+
const scrollY = useDraggedItemStore((state) => state.scrollY);
|
|
5326
|
+
const zoneDepth = useMemo(() => getItemDepthFromNode({ id: parentZoneId }, tree.root), [tree, parentZoneId]);
|
|
5327
|
+
const zones = useZoneStore((state) => state.zones);
|
|
5328
|
+
const hoveringZone = useZoneStore((state) => state.hoveringZone);
|
|
5329
|
+
const isHoveringZone = hoveringZone === zoneId;
|
|
5330
|
+
const hitboxContainer = useMemo(() => {
|
|
5331
|
+
return document.querySelector('[data-ctfl-hitboxes]');
|
|
5332
|
+
}, []);
|
|
5333
|
+
const domRect = useMemo(() => {
|
|
5334
|
+
if (!isDraggingOnCanvas)
|
|
5335
|
+
return;
|
|
5336
|
+
return document.querySelector(`[${CTFL_ZONE_ID}="${zoneId}"]`)?.getBoundingClientRect();
|
|
5337
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5338
|
+
}, [zoneId, isDraggingOnCanvas]);
|
|
5339
|
+
// Use the size of the cloned dragging element to offset the position of the hitboxes
|
|
5340
|
+
// So that when dragging causes a dropzone to expand, the hitboxes will be in the correct position
|
|
5341
|
+
const offsetRect = useMemo(() => {
|
|
5342
|
+
if (!isDraggingOnCanvas || isEmptyZone || !isHoveringZone)
|
|
5343
|
+
return;
|
|
5344
|
+
return document.querySelector(`[${CTFL_DRAGGING_ELEMENT}]`)?.getBoundingClientRect();
|
|
5345
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5346
|
+
}, [isEmptyZone, isHoveringZone, isDraggingOnCanvas]);
|
|
5347
|
+
const zoneDirection = zones[parentZoneId]?.direction || 'vertical';
|
|
5348
|
+
const isVertical = zoneDirection === 'vertical';
|
|
5349
|
+
const isRoot = parentZoneId === ROOT_ID;
|
|
5350
|
+
const { slotId: parentSlotId } = parseZoneId(parentZoneId);
|
|
5351
|
+
const getStyles = useCallback((direction) => getHitboxStyles({
|
|
5352
|
+
direction,
|
|
5353
|
+
zoneDepth,
|
|
5354
|
+
domRect,
|
|
5355
|
+
scrollY,
|
|
5356
|
+
offsetRect,
|
|
5357
|
+
}), [zoneDepth, domRect, scrollY, offsetRect]);
|
|
5358
|
+
const renderFinalRootHitbox = () => {
|
|
5359
|
+
if (!isRoot)
|
|
5360
|
+
return null;
|
|
5361
|
+
return (React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(HitboxDirection.BOTTOM) }));
|
|
5362
|
+
};
|
|
5363
|
+
const renderSurroundingHitboxes = () => {
|
|
5364
|
+
if (isRoot || parentSlotId)
|
|
5365
|
+
return null;
|
|
5366
|
+
return (React.createElement(React.Fragment, null,
|
|
5367
|
+
React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.TOP : HitboxDirection.LEFT) }),
|
|
5368
|
+
React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.BOTTOM : HitboxDirection.RIGHT) })));
|
|
5369
|
+
};
|
|
5370
|
+
const ActiveHitboxes = (React.createElement(React.Fragment, null,
|
|
5371
|
+
React.createElement("div", { "data-ctfl-zone-id": zoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.SELF_VERTICAL : HitboxDirection.SELF_HORIZONTAL) }),
|
|
5372
|
+
renderSurroundingHitboxes(),
|
|
5373
|
+
renderFinalRootHitbox()));
|
|
5374
|
+
if (!hitboxContainer) {
|
|
5375
|
+
return null;
|
|
5376
|
+
}
|
|
5377
|
+
return createPortal(ActiveHitboxes, hitboxContainer);
|
|
5378
|
+
};
|
|
5379
|
+
|
|
5380
|
+
const isRelativePreviewSize = (width) => {
|
|
5381
|
+
// For now, we solely allow 100% as relative value
|
|
5382
|
+
return width === '100%';
|
|
5383
|
+
};
|
|
5384
|
+
const getTooltipPositions = ({ previewSize, tooltipRect, coordinates, }) => {
|
|
5385
|
+
if (!coordinates || !tooltipRect) {
|
|
5386
|
+
return { display: 'none' };
|
|
5387
|
+
}
|
|
5388
|
+
/**
|
|
5389
|
+
* By default, the tooltip floats to the left of the element
|
|
5390
|
+
*/
|
|
5391
|
+
const newTooltipStyles = { display: 'flex' };
|
|
5392
|
+
// If the preview size is relative, we don't change the floating direction
|
|
5393
|
+
if (!isRelativePreviewSize(previewSize)) {
|
|
5394
|
+
const previewSizeMatch = previewSize.match(/(\d{1,})px/);
|
|
5395
|
+
if (!previewSizeMatch) {
|
|
5396
|
+
return { display: 'none' };
|
|
4126
5397
|
}
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
5398
|
+
const previewSizePx = parseInt(previewSizeMatch[1]);
|
|
5399
|
+
/**
|
|
5400
|
+
* If the element is at the right edge of the canvas, and the element isn't wide enough to fit the tooltip width,
|
|
5401
|
+
* we float the tooltip to the right of the element.
|
|
5402
|
+
*/
|
|
5403
|
+
if (tooltipRect.width > previewSizePx - coordinates.right &&
|
|
5404
|
+
tooltipRect.width > coordinates.width) {
|
|
5405
|
+
newTooltipStyles['float'] = 'right';
|
|
5406
|
+
}
|
|
5407
|
+
}
|
|
5408
|
+
const tooltipHeight = tooltipRect.height === 0 ? 32 : tooltipRect.height;
|
|
5409
|
+
/**
|
|
5410
|
+
* For elements with small heights, we don't want the tooltip covering the content in the element,
|
|
5411
|
+
* so we show the tooltip at the top or bottom.
|
|
5412
|
+
*/
|
|
5413
|
+
if (tooltipHeight * 2 > coordinates.height) {
|
|
5414
|
+
/**
|
|
5415
|
+
* If there's enough space for the tooltip at the top of the element, we show the tooltip at the top of the element,
|
|
5416
|
+
* else we show the tooltip at the bottom.
|
|
5417
|
+
*/
|
|
5418
|
+
if (tooltipHeight < coordinates.top) {
|
|
5419
|
+
newTooltipStyles['bottom'] = coordinates.height;
|
|
5420
|
+
}
|
|
5421
|
+
else {
|
|
5422
|
+
newTooltipStyles['top'] = coordinates.height;
|
|
5423
|
+
}
|
|
5424
|
+
}
|
|
5425
|
+
/**
|
|
5426
|
+
* If the component draws outside of the borders of the canvas to the left we move the tooltip to the right
|
|
5427
|
+
* so that it is fully visible.
|
|
5428
|
+
*/
|
|
5429
|
+
if (coordinates.left < 0) {
|
|
5430
|
+
newTooltipStyles['left'] = -coordinates.left;
|
|
5431
|
+
}
|
|
5432
|
+
/**
|
|
5433
|
+
* If for any reason, the element's top is negative, we show the tooltip at the bottom
|
|
5434
|
+
*/
|
|
5435
|
+
if (coordinates.top < 0) {
|
|
5436
|
+
newTooltipStyles['top'] = coordinates.height;
|
|
5437
|
+
}
|
|
5438
|
+
return newTooltipStyles;
|
|
5439
|
+
};
|
|
5440
|
+
|
|
5441
|
+
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";
|
|
5442
|
+
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"};
|
|
5443
|
+
styleInject(css_248z$1);
|
|
5444
|
+
|
|
5445
|
+
const Tooltip = ({ coordinates, id, label, isAssemblyBlock, isContainer, isSelected, }) => {
|
|
5446
|
+
const tooltipRef = useRef(null);
|
|
5447
|
+
const previewSize = '100%'; // This should be based on breakpoints and added to usememo dependency array
|
|
5448
|
+
const tooltipStyles = useMemo(() => {
|
|
5449
|
+
const tooltipRect = tooltipRef.current?.getBoundingClientRect();
|
|
5450
|
+
const draggableRect = document
|
|
5451
|
+
.querySelector(`[data-ctfl-draggable-id="${id}"]`)
|
|
5452
|
+
?.getBoundingClientRect();
|
|
5453
|
+
const newTooltipStyles = getTooltipPositions({
|
|
5454
|
+
previewSize,
|
|
5455
|
+
tooltipRect,
|
|
5456
|
+
coordinates: draggableRect,
|
|
5457
|
+
});
|
|
5458
|
+
return newTooltipStyles;
|
|
5459
|
+
// 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
|
|
5460
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5461
|
+
}, [coordinates, id, tooltipRef.current]);
|
|
5462
|
+
if (isSelected) {
|
|
5463
|
+
return null;
|
|
5464
|
+
}
|
|
5465
|
+
return (React.createElement("div", { "data-tooltip": true, className: styles$1.tooltipWrapper },
|
|
5466
|
+
React.createElement("div", { "data-tooltip": true, ref: tooltipRef, style: tooltipStyles, className: classNames(styles$1.overlay, {
|
|
5467
|
+
[styles$1.overlayContainer]: isContainer,
|
|
5468
|
+
[styles$1.overlayAssembly]: isAssemblyBlock,
|
|
5469
|
+
}) }, label)));
|
|
5470
|
+
};
|
|
5471
|
+
|
|
5472
|
+
function useSingleColumn(node, resolveDesignValue) {
|
|
5473
|
+
const tree = useTreeStore((store) => store.tree);
|
|
5474
|
+
const isSingleColumn = node.data.blockId === CONTENTFUL_COMPONENTS$1.singleColumn.id;
|
|
5475
|
+
const isWrapped = useMemo(() => {
|
|
5476
|
+
if (!node.parentId || !isSingleColumn) {
|
|
5477
|
+
return false;
|
|
5478
|
+
}
|
|
5479
|
+
const parentNode = getItem({ id: node.parentId }, tree);
|
|
5480
|
+
if (!parentNode || parentNode.data.blockId !== CONTENTFUL_COMPONENTS$1.columns.id) {
|
|
5481
|
+
return false;
|
|
5482
|
+
}
|
|
5483
|
+
const { cfWrapColumns } = parentNode.data.props;
|
|
5484
|
+
if (cfWrapColumns.type !== 'DesignValue') {
|
|
5485
|
+
return false;
|
|
5486
|
+
}
|
|
5487
|
+
return resolveDesignValue(cfWrapColumns.valuesByBreakpoint);
|
|
5488
|
+
}, [tree, node, isSingleColumn, resolveDesignValue]);
|
|
5489
|
+
return {
|
|
5490
|
+
isSingleColumn,
|
|
5491
|
+
isWrapped,
|
|
5492
|
+
};
|
|
4148
5493
|
}
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
5494
|
+
|
|
5495
|
+
function getStyle$1(style, snapshot) {
|
|
5496
|
+
if (!snapshot.isDropAnimating) {
|
|
5497
|
+
return style;
|
|
5498
|
+
}
|
|
5499
|
+
return {
|
|
5500
|
+
...style,
|
|
5501
|
+
// cannot be 0, but make it super tiny
|
|
5502
|
+
transitionDuration: `0.001s`,
|
|
5503
|
+
};
|
|
5504
|
+
}
|
|
5505
|
+
const EditorBlock = ({ node: rawNode, resolveDesignValue, renderDropzone, index, zoneId, userIsDragging, placeholder, wrappingPatternIds, }) => {
|
|
5506
|
+
const { slotId } = parseZoneId(zoneId);
|
|
5507
|
+
const ref = useRef(null);
|
|
5508
|
+
const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
|
|
5509
|
+
const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
|
|
5510
|
+
const { node, componentId, elementToRender, definition, isPatternNode, isPatternComponent, isNestedPattern, } = useComponent({
|
|
5511
|
+
node: rawNode,
|
|
4152
5512
|
resolveDesignValue,
|
|
4153
|
-
|
|
4154
|
-
|
|
5513
|
+
renderDropzone,
|
|
5514
|
+
userIsDragging,
|
|
5515
|
+
wrappingPatternIds,
|
|
5516
|
+
});
|
|
5517
|
+
const { isSingleColumn, isWrapped } = useSingleColumn(node, resolveDesignValue);
|
|
5518
|
+
const setDomRect = useDraggedItemStore((state) => state.setDomRect);
|
|
5519
|
+
const isHoveredComponent = useDraggedItemStore((state) => state.hoveredComponentId === componentId);
|
|
5520
|
+
const coordinates = useSelectedInstanceCoordinates({ node });
|
|
5521
|
+
const displayName = node.data.displayName || rawNode.data.displayName || definition?.name;
|
|
5522
|
+
const testId = `draggable-${node.data.blockId ?? 'node'}`;
|
|
5523
|
+
const isSelected = node.data.id === selectedNodeId;
|
|
5524
|
+
const isContainer = node.data.blockId === CONTENTFUL_COMPONENTS$1.container.id;
|
|
5525
|
+
const isSlotComponent = Boolean(node.data.slotId);
|
|
5526
|
+
const isDragDisabled = isNestedPattern || isPatternComponent || (isSingleColumn && isWrapped) || isSlotComponent;
|
|
5527
|
+
const isEmptyZone = useMemo(() => {
|
|
5528
|
+
return !node.children.filter((node) => node.data.slotId === slotId).length;
|
|
5529
|
+
}, [node.children, slotId]);
|
|
5530
|
+
useDraggablePosition({
|
|
5531
|
+
draggableId: componentId,
|
|
5532
|
+
draggableRef: ref,
|
|
5533
|
+
position: DraggablePosition.MOUSE_POSITION,
|
|
4155
5534
|
});
|
|
4156
|
-
|
|
5535
|
+
const onClick = (e) => {
|
|
5536
|
+
e.stopPropagation();
|
|
5537
|
+
if (!userIsDragging) {
|
|
5538
|
+
setSelectedNodeId(node.data.id);
|
|
5539
|
+
// if it is the assembly directly we just want to select it as a normal component
|
|
5540
|
+
if (isPatternNode) {
|
|
5541
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
5542
|
+
nodeId: node.data.id,
|
|
5543
|
+
});
|
|
5544
|
+
return;
|
|
5545
|
+
}
|
|
5546
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
5547
|
+
assembly: node.data.assembly,
|
|
5548
|
+
nodeId: node.data.id,
|
|
5549
|
+
});
|
|
5550
|
+
}
|
|
5551
|
+
};
|
|
5552
|
+
const onMouseOver = (e) => {
|
|
5553
|
+
e.stopPropagation();
|
|
5554
|
+
if (userIsDragging)
|
|
5555
|
+
return;
|
|
5556
|
+
sendMessage(OUTGOING_EVENTS.NewHoveredElement, {
|
|
5557
|
+
nodeId: componentId,
|
|
5558
|
+
});
|
|
5559
|
+
};
|
|
5560
|
+
const onMouseDown = (e) => {
|
|
5561
|
+
if (isDragDisabled) {
|
|
5562
|
+
return;
|
|
5563
|
+
}
|
|
5564
|
+
e.stopPropagation();
|
|
5565
|
+
setDomRect(e.currentTarget.getBoundingClientRect());
|
|
5566
|
+
};
|
|
5567
|
+
const ToolTipAndPlaceholder = (React.createElement(React.Fragment, null,
|
|
5568
|
+
React.createElement(Tooltip, { id: componentId, coordinates: coordinates, isAssemblyBlock: isPatternNode || isPatternComponent, isContainer: isContainer, isSelected: isSelected, label: displayName || 'No label specified' }),
|
|
5569
|
+
React.createElement(Placeholder, { ...placeholder, id: componentId }),
|
|
5570
|
+
userIsDragging && !isPatternComponent && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId, isEmptyZone: isEmptyZone }))));
|
|
5571
|
+
return (React.createElement(Draggable, { key: componentId, draggableId: componentId, index: index, isDragDisabled: isDragDisabled, disableInteractiveElementBlocking: true }, (provided, snapshot) => elementToRender({
|
|
5572
|
+
dragProps: {
|
|
5573
|
+
...provided.draggableProps,
|
|
5574
|
+
...provided.dragHandleProps,
|
|
5575
|
+
'data-ctfl-draggable-id': componentId,
|
|
5576
|
+
'data-test-id': testId,
|
|
5577
|
+
innerRef: (refNode) => {
|
|
5578
|
+
provided?.innerRef(refNode);
|
|
5579
|
+
ref.current = refNode;
|
|
5580
|
+
},
|
|
5581
|
+
className: classNames(styles$1.DraggableComponent, {
|
|
5582
|
+
[styles$1.isAssemblyBlock]: isPatternComponent || isPatternNode,
|
|
5583
|
+
[styles$1.isDragging]: snapshot?.isDragging || userIsDragging,
|
|
5584
|
+
[styles$1.isSelected]: isSelected,
|
|
5585
|
+
[styles$1.isHoveringComponent]: isHoveredComponent,
|
|
5586
|
+
}),
|
|
5587
|
+
style: getStyle$1(provided.draggableProps.style, snapshot),
|
|
5588
|
+
onMouseDown,
|
|
5589
|
+
onMouseOver,
|
|
5590
|
+
onClick,
|
|
5591
|
+
ToolTipAndPlaceholder,
|
|
5592
|
+
},
|
|
5593
|
+
})));
|
|
4157
5594
|
};
|
|
4158
5595
|
|
|
4159
|
-
var css_248z = ".
|
|
4160
|
-
var styles = {"
|
|
5596
|
+
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";
|
|
5597
|
+
var styles = {"container":"EmptyContainer-module_container__XPH5b","highlight":"EmptyContainer-module_highlight__lcICy","icon":"EmptyContainer-module_icon__82-2O","label":"EmptyContainer-module_label__4TxRa"};
|
|
4161
5598
|
styleInject(css_248z);
|
|
4162
5599
|
|
|
4163
|
-
const
|
|
4164
|
-
return (React.createElement("div", { className: styles
|
|
4165
|
-
|
|
5600
|
+
const EmptyContainer = ({ isDragging }) => {
|
|
5601
|
+
return (React.createElement("div", { className: classNames(styles.container, {
|
|
5602
|
+
[styles.highlight]: isDragging,
|
|
5603
|
+
}), "data-type": "empty-container" },
|
|
5604
|
+
React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "37", height: "36", fill: "none", className: styles.icon },
|
|
4166
5605
|
React.createElement("rect", { width: "11.676", height: "11.676", x: "18.512", y: ".153", rx: "1.621", transform: "rotate(45 18.512 .153)" }),
|
|
4167
5606
|
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)" }),
|
|
4168
5607
|
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)" }),
|
|
4169
5608
|
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)" }),
|
|
4170
5609
|
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" })),
|
|
4171
|
-
React.createElement("span", { className: styles
|
|
5610
|
+
React.createElement("span", { className: styles.label }, "Add components to begin")));
|
|
4172
5611
|
};
|
|
4173
5612
|
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
5613
|
+
const useDropzoneDirection = ({ resolveDesignValue, node, zoneId }) => {
|
|
5614
|
+
const zone = useZoneStore((state) => state.zones);
|
|
5615
|
+
const upsertZone = useZoneStore((state) => state.upsertZone);
|
|
5616
|
+
useEffect(() => {
|
|
5617
|
+
function getDirection() {
|
|
5618
|
+
if (!node || !node.data.blockId) {
|
|
5619
|
+
return 'vertical';
|
|
5620
|
+
}
|
|
5621
|
+
if (!isContentfulStructureComponent(node.data.blockId)) {
|
|
5622
|
+
return 'vertical';
|
|
5623
|
+
}
|
|
5624
|
+
if (node.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id) {
|
|
5625
|
+
return 'horizontal';
|
|
5626
|
+
}
|
|
5627
|
+
const designValues = node.data.props['cfFlexDirection'];
|
|
5628
|
+
if (!designValues || !resolveDesignValue || designValues.type !== 'DesignValue') {
|
|
5629
|
+
return 'vertical';
|
|
5630
|
+
}
|
|
5631
|
+
const direction = resolveDesignValue(designValues.valuesByBreakpoint);
|
|
5632
|
+
if (direction === 'row') {
|
|
5633
|
+
return 'horizontal';
|
|
5634
|
+
}
|
|
5635
|
+
return 'vertical';
|
|
5636
|
+
}
|
|
5637
|
+
upsertZone(zoneId, { direction: getDirection() });
|
|
5638
|
+
}, [node, resolveDesignValue, zoneId, upsertZone]);
|
|
5639
|
+
return zone[zoneId]?.direction || 'vertical';
|
|
5640
|
+
};
|
|
5641
|
+
|
|
5642
|
+
function getStyle(style = {}, snapshot) {
|
|
5643
|
+
if (!snapshot?.isDropAnimating) {
|
|
5644
|
+
return style;
|
|
5645
|
+
}
|
|
5646
|
+
return {
|
|
5647
|
+
...style,
|
|
5648
|
+
// cannot be 0, but make it super tiny
|
|
5649
|
+
transitionDuration: `0.001s`,
|
|
5650
|
+
};
|
|
5651
|
+
}
|
|
5652
|
+
const EditorBlockClone = ({ node: rawNode, resolveDesignValue, snapshot, provided, renderDropzone, wrappingPatternIds, }) => {
|
|
5653
|
+
const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
|
|
5654
|
+
const { node, elementToRender } = useComponent({
|
|
5655
|
+
node: rawNode,
|
|
5656
|
+
resolveDesignValue,
|
|
5657
|
+
renderDropzone,
|
|
5658
|
+
userIsDragging,
|
|
5659
|
+
wrappingPatternIds,
|
|
5660
|
+
});
|
|
5661
|
+
const isAssemblyBlock = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
|
|
5662
|
+
return elementToRender({
|
|
5663
|
+
dragProps: {
|
|
5664
|
+
...provided?.draggableProps,
|
|
5665
|
+
...provided?.dragHandleProps,
|
|
5666
|
+
'data-ctfl-dragging-element': 'true',
|
|
5667
|
+
innerRef: provided?.innerRef,
|
|
5668
|
+
className: classNames(styles$1.DraggableComponent, styles$1.DraggableClone, {
|
|
5669
|
+
[styles$1.isAssemblyBlock]: isAssemblyBlock,
|
|
5670
|
+
[styles$1.isDragging]: snapshot?.isDragging,
|
|
5671
|
+
}),
|
|
5672
|
+
style: getStyle(provided?.draggableProps.style, snapshot),
|
|
4186
5673
|
},
|
|
4187
|
-
nodes: nodeToCoordinatesMap,
|
|
4188
|
-
sourceEvent,
|
|
4189
5674
|
});
|
|
4190
5675
|
};
|
|
4191
|
-
|
|
4192
|
-
|
|
4193
|
-
if (
|
|
4194
|
-
const
|
|
4195
|
-
|
|
4196
|
-
coordinates: {
|
|
4197
|
-
x: rect.x + window.scrollX,
|
|
4198
|
-
y: rect.y + window.scrollY,
|
|
4199
|
-
width: rect.width,
|
|
4200
|
-
height: rect.height,
|
|
4201
|
-
},
|
|
4202
|
-
};
|
|
5676
|
+
|
|
5677
|
+
const getHtmlDragProps = (dragProps) => {
|
|
5678
|
+
if (dragProps) {
|
|
5679
|
+
const { ToolTipAndPlaceholder, Tag, innerRef, wrapComponent, ...htmlDragProps } = dragProps;
|
|
5680
|
+
return htmlDragProps;
|
|
4203
5681
|
}
|
|
4204
|
-
|
|
5682
|
+
return {};
|
|
4205
5683
|
};
|
|
4206
|
-
const
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
}
|
|
4213
|
-
return new Promise((resolve, reject) => {
|
|
4214
|
-
const handleImageLoad = (event) => {
|
|
4215
|
-
imageNode.removeEventListener('load', handleImageLoad);
|
|
4216
|
-
imageNode.removeEventListener('error', handleImageLoad);
|
|
4217
|
-
if (event.type === 'error') {
|
|
4218
|
-
console.warn('Image failed to load:', imageNode);
|
|
4219
|
-
reject();
|
|
4220
|
-
}
|
|
4221
|
-
else {
|
|
4222
|
-
resolve();
|
|
4223
|
-
}
|
|
4224
|
-
};
|
|
4225
|
-
imageNode.addEventListener('load', handleImageLoad);
|
|
4226
|
-
imageNode.addEventListener('error', handleImageLoad);
|
|
4227
|
-
});
|
|
4228
|
-
}));
|
|
5684
|
+
const getHtmlComponentProps = (props) => {
|
|
5685
|
+
if (props) {
|
|
5686
|
+
const { editorMode, renderDropzone, node, ...htmlProps } = props;
|
|
5687
|
+
return htmlProps;
|
|
5688
|
+
}
|
|
5689
|
+
return {};
|
|
4229
5690
|
};
|
|
4230
5691
|
|
|
4231
|
-
|
|
4232
|
-
const
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
}, [debouncedUpdateGeometry]);
|
|
4256
|
-
// Handling DOM mutations
|
|
4257
|
-
useEffect(() => {
|
|
4258
|
-
if (!rootContainerRef.current)
|
|
4259
|
-
return;
|
|
4260
|
-
const observer = new MutationObserver(() => debouncedUpdateGeometry(treeRef.current, 'mutation'));
|
|
4261
|
-
observer.observe(rootContainerRef.current, {
|
|
4262
|
-
childList: true,
|
|
4263
|
-
subtree: true,
|
|
4264
|
-
attributes: true,
|
|
4265
|
-
});
|
|
4266
|
-
return () => observer.disconnect();
|
|
4267
|
-
}, [debouncedUpdateGeometry, rootContainerRef]);
|
|
4268
|
-
};
|
|
5692
|
+
function DropzoneClone({ node, zoneId, resolveDesignValue, WrapperComponent = 'div', renderDropzone, dragProps, wrappingPatternIds, ...rest }) {
|
|
5693
|
+
const tree = useTreeStore((state) => state.tree);
|
|
5694
|
+
const content = node?.children || tree.root?.children || [];
|
|
5695
|
+
const { slotId } = parseZoneId(zoneId);
|
|
5696
|
+
const htmlDraggableProps = getHtmlDragProps(dragProps);
|
|
5697
|
+
const htmlProps = getHtmlComponentProps(rest);
|
|
5698
|
+
const isRootZone = zoneId === ROOT_ID;
|
|
5699
|
+
if (!resolveDesignValue) {
|
|
5700
|
+
return null;
|
|
5701
|
+
}
|
|
5702
|
+
return (React.createElement(WrapperComponent, { ...htmlDraggableProps, ...htmlProps, className: classNames(dragProps?.className, styles$1.Dropzone, styles$1.DropzoneClone, rest.className, {
|
|
5703
|
+
[styles$1.isRoot]: isRootZone,
|
|
5704
|
+
[styles$1.isEmptyZone]: !content.length,
|
|
5705
|
+
}), "data-ctfl-slot-id": slotId, ref: (refNode) => {
|
|
5706
|
+
if (dragProps?.innerRef) {
|
|
5707
|
+
dragProps.innerRef(refNode);
|
|
5708
|
+
}
|
|
5709
|
+
} }, content
|
|
5710
|
+
.filter((node) => node.data.slotId === slotId)
|
|
5711
|
+
.map((item) => {
|
|
5712
|
+
const componentId = item.data.id;
|
|
5713
|
+
return (React.createElement(EditorBlockClone, { key: componentId, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone, wrappingPatternIds: wrappingPatternIds }));
|
|
5714
|
+
})));
|
|
5715
|
+
}
|
|
4269
5716
|
|
|
4270
|
-
|
|
4271
|
-
const
|
|
5717
|
+
function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponent = 'div', dragProps, wrappingPatternIds: parentWrappingPatternIds = new Set(), ...rest }) {
|
|
5718
|
+
const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
|
|
5719
|
+
const draggedItem = useDraggedItemStore((state) => state.draggedItem);
|
|
5720
|
+
const isDraggingNewComponent = useDraggedItemStore((state) => Boolean(state.componentId));
|
|
5721
|
+
const isHoveringZone = useZoneStore((state) => state.hoveringZone === zoneId);
|
|
4272
5722
|
const tree = useTreeStore((state) => state.tree);
|
|
4273
|
-
|
|
5723
|
+
const content = node?.children || tree.root?.children || [];
|
|
5724
|
+
const { slotId } = parseZoneId(zoneId);
|
|
5725
|
+
const direction = useDropzoneDirection({ resolveDesignValue, node, zoneId });
|
|
5726
|
+
const draggedDestinationId = draggedItem && draggedItem.destination?.droppableId;
|
|
5727
|
+
const draggedNode = useMemo(() => {
|
|
5728
|
+
if (!draggedItem)
|
|
5729
|
+
return;
|
|
5730
|
+
return getItem({ id: draggedItem.draggableId }, tree);
|
|
5731
|
+
}, [draggedItem, tree]);
|
|
5732
|
+
const isRootZone = zoneId === ROOT_ID;
|
|
5733
|
+
const isDestination = draggedDestinationId === zoneId;
|
|
5734
|
+
const isEmptyCanvas = isRootZone && !content.length;
|
|
5735
|
+
const isAssembly = ASSEMBLY_NODE_TYPES.includes(node?.type || '');
|
|
5736
|
+
const isRootAssembly = node?.type === ASSEMBLY_NODE_TYPE;
|
|
5737
|
+
const htmlDraggableProps = getHtmlDragProps(dragProps);
|
|
5738
|
+
const htmlProps = getHtmlComponentProps(rest);
|
|
5739
|
+
const wrappingPatternIds = useMemo(() => {
|
|
5740
|
+
// On the top level, the node is not defined. If the root blockId is not the default string,
|
|
5741
|
+
// we assume that it is the entry ID of the experience/ pattern to properly detect circular dependencies
|
|
5742
|
+
if (!node && tree.root.data.blockId && tree.root.data.blockId !== ROOT_ID) {
|
|
5743
|
+
return new Set([tree.root.data.blockId, ...parentWrappingPatternIds]);
|
|
5744
|
+
}
|
|
5745
|
+
if (isRootAssembly && node?.data.blockId) {
|
|
5746
|
+
return new Set([node.data.blockId, ...parentWrappingPatternIds]);
|
|
5747
|
+
}
|
|
5748
|
+
return parentWrappingPatternIds;
|
|
5749
|
+
}, [isRootAssembly, node, parentWrappingPatternIds, tree.root.data.blockId]);
|
|
5750
|
+
// To avoid a circular dependency, we create the recursive rendering function here and trickle it down
|
|
5751
|
+
const renderDropzone = useCallback((node, props) => {
|
|
5752
|
+
return (React.createElement(Dropzone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds, ...props }));
|
|
5753
|
+
}, [wrappingPatternIds, resolveDesignValue]);
|
|
5754
|
+
const renderClonedDropzone = useCallback((node, props) => {
|
|
5755
|
+
return (React.createElement(DropzoneClone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, renderDropzone: renderClonedDropzone, wrappingPatternIds: wrappingPatternIds, ...props }));
|
|
5756
|
+
}, [resolveDesignValue, wrappingPatternIds]);
|
|
5757
|
+
const isDropzoneEnabled = useMemo(() => {
|
|
5758
|
+
const isColumns = node?.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id;
|
|
5759
|
+
const isDraggingSingleColumn = draggedNode?.data.blockId === CONTENTFUL_COMPONENTS$1.singleColumn.id;
|
|
5760
|
+
const isParentOfDraggedNode = node?.data.id === draggedNode?.parentId;
|
|
5761
|
+
// If dragging a single column, only enable the dropzone of the parent
|
|
5762
|
+
// columns component
|
|
5763
|
+
if (isDraggingSingleColumn && isColumns && isParentOfDraggedNode) {
|
|
5764
|
+
return true;
|
|
5765
|
+
}
|
|
5766
|
+
// If dragging a single column, disable dropzones for any component besides
|
|
5767
|
+
// the parent of the dragged single column
|
|
5768
|
+
if (isDraggingSingleColumn && !isParentOfDraggedNode) {
|
|
5769
|
+
return false;
|
|
5770
|
+
}
|
|
5771
|
+
// Disable dropzone for Columns component
|
|
5772
|
+
if (isColumns) {
|
|
5773
|
+
return false;
|
|
5774
|
+
}
|
|
5775
|
+
// Disable dropzone for Assembly
|
|
5776
|
+
if (isAssembly) {
|
|
5777
|
+
return false;
|
|
5778
|
+
}
|
|
5779
|
+
// Enable dropzone for the non-root hovered zones if component is not allowed on root
|
|
5780
|
+
if (!isDraggingNewComponent &&
|
|
5781
|
+
!isComponentAllowedOnRoot({ type: draggedNode?.type, componentId: draggedNode?.data.blockId })) {
|
|
5782
|
+
return isHoveringZone && !isRootZone;
|
|
5783
|
+
}
|
|
5784
|
+
// Enable dropzone for the hovered zone only
|
|
5785
|
+
return isHoveringZone;
|
|
5786
|
+
}, [isAssembly, isHoveringZone, isRootZone, isDraggingNewComponent, draggedNode, node]);
|
|
5787
|
+
if (!resolveDesignValue) {
|
|
5788
|
+
return null;
|
|
5789
|
+
}
|
|
5790
|
+
const isPatternWrapperComponentFullHeight = isRootAssembly
|
|
5791
|
+
? node.children.length === 1 &&
|
|
5792
|
+
resolveDesignValue(node?.children[0]?.data.props.cfHeight?.valuesByBreakpoint ?? {}, 'cfHeight') === '100%'
|
|
5793
|
+
: false;
|
|
5794
|
+
const isPatternWrapperComponentFullWidth = isRootAssembly
|
|
5795
|
+
? node.children.length === 1 &&
|
|
5796
|
+
resolveDesignValue(node?.children[0]?.data.props.cfWidth?.valuesByBreakpoint ?? {}, 'cfWidth') === '100%'
|
|
5797
|
+
: false;
|
|
5798
|
+
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) => {
|
|
5799
|
+
return (React.createElement(WrapperComponent, { ...(provided || { droppableProps: {} }).droppableProps, ...htmlDraggableProps, ...htmlProps, ref: (refNode) => {
|
|
5800
|
+
if (dragProps?.innerRef) {
|
|
5801
|
+
dragProps.innerRef(refNode);
|
|
5802
|
+
}
|
|
5803
|
+
provided?.innerRef(refNode);
|
|
5804
|
+
}, id: zoneId, "data-ctfl-zone-id": zoneId, "data-ctfl-slot-id": slotId, className: classNames(dragProps?.className, styles$1.Dropzone, className, {
|
|
5805
|
+
[styles$1.isEmptyCanvas]: isEmptyCanvas,
|
|
5806
|
+
[styles$1.isDragging]: userIsDragging,
|
|
5807
|
+
[styles$1.isDestination]: isDestination && !isAssembly,
|
|
5808
|
+
[styles$1.isRoot]: isRootZone,
|
|
5809
|
+
[styles$1.isEmptyZone]: !content.length,
|
|
5810
|
+
[styles$1.isSlot]: Boolean(slotId),
|
|
5811
|
+
[styles$1.fullHeight]: isPatternWrapperComponentFullHeight,
|
|
5812
|
+
[styles$1.fullWidth]: isPatternWrapperComponentFullWidth,
|
|
5813
|
+
}) },
|
|
5814
|
+
isEmptyCanvas ? (React.createElement(EmptyContainer, { isDragging: isRootZone && userIsDragging })) : (content
|
|
5815
|
+
.filter((node) => node.data.slotId === slotId)
|
|
5816
|
+
.map((item, i) => (React.createElement(EditorBlock, { placeholder: {
|
|
5817
|
+
isDraggingOver: snapshot?.isDraggingOver,
|
|
5818
|
+
totalIndexes: content.length,
|
|
5819
|
+
elementIndex: i,
|
|
5820
|
+
dropzoneElementId: zoneId,
|
|
5821
|
+
direction,
|
|
5822
|
+
}, index: i, zoneId: zoneId, key: item.data.id, userIsDragging: userIsDragging, draggingNewComponent: isDraggingNewComponent, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone, wrappingPatternIds: wrappingPatternIds })))),
|
|
5823
|
+
provided?.placeholder,
|
|
5824
|
+
dragProps?.ToolTipAndPlaceholder));
|
|
5825
|
+
}));
|
|
5826
|
+
}
|
|
5827
|
+
|
|
5828
|
+
const RootRenderer = ({ onChange }) => {
|
|
4274
5829
|
useEditorSubscriber();
|
|
5830
|
+
const dragItem = useDraggedItemStore((state) => state.componentId);
|
|
5831
|
+
const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
|
|
5832
|
+
const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
|
|
4275
5833
|
const breakpoints = useTreeStore((state) => state.breakpoints);
|
|
5834
|
+
const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
|
|
5835
|
+
const containerRef = useRef(null);
|
|
4276
5836
|
const { resolveDesignValue } = useBreakpoints(breakpoints);
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
const
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
5837
|
+
const [containerStyles, setContainerStyles] = useState({});
|
|
5838
|
+
const tree = useTreeStore((state) => state.tree);
|
|
5839
|
+
const handleMouseOver = useCallback(() => {
|
|
5840
|
+
// Remove hover state set by UI when mouse is over canvas
|
|
5841
|
+
setHoveredComponentId();
|
|
5842
|
+
// Remove hover styling from components in the layers tab
|
|
5843
|
+
sendMessage(OUTGOING_EVENTS.NewHoveredElement, {});
|
|
5844
|
+
}, [setHoveredComponentId]);
|
|
5845
|
+
const handleClickOutside = useCallback((e) => {
|
|
5846
|
+
const element = e.target;
|
|
5847
|
+
const isRoot = element.getAttribute('data-ctfl-zone-id') === ROOT_ID;
|
|
5848
|
+
const clickedOnCanvas = element.closest(`[data-ctfl-root]`);
|
|
5849
|
+
if (clickedOnCanvas && !isRoot) {
|
|
5850
|
+
return;
|
|
5851
|
+
}
|
|
5852
|
+
sendMessage(OUTGOING_EVENTS.OutsideCanvasClick, {
|
|
5853
|
+
outsideCanvasClick: true,
|
|
5854
|
+
});
|
|
5855
|
+
sendMessage(OUTGOING_EVENTS.ComponentSelected, {
|
|
5856
|
+
nodeId: '',
|
|
5857
|
+
});
|
|
5858
|
+
setSelectedNodeId('');
|
|
5859
|
+
}, [setSelectedNodeId]);
|
|
5860
|
+
const handleResizeCanvas = useCallback(() => {
|
|
5861
|
+
const parentElement = containerRef.current?.parentElement;
|
|
5862
|
+
if (!parentElement) {
|
|
5863
|
+
return;
|
|
5864
|
+
}
|
|
5865
|
+
let siblingHeight = 0;
|
|
5866
|
+
for (const child of parentElement.children) {
|
|
5867
|
+
if (!child.hasAttribute('data-ctfl-root')) {
|
|
5868
|
+
siblingHeight += child.getBoundingClientRect().height;
|
|
5869
|
+
}
|
|
5870
|
+
}
|
|
5871
|
+
if (!siblingHeight) {
|
|
5872
|
+
/**
|
|
5873
|
+
* DRAGGABLE_HEIGHT is subtracted here due to an uninteded scrolling effect
|
|
5874
|
+
* when dragging a new component onto the canvas
|
|
5875
|
+
*
|
|
5876
|
+
* The DRAGGABLE_HEIGHT is then added as margin bottom to offset this value
|
|
5877
|
+
* so that visually there is no difference to the user.
|
|
5878
|
+
*/
|
|
5879
|
+
setContainerStyles({
|
|
5880
|
+
minHeight: `${window.innerHeight - DRAGGABLE_HEIGHT}px`,
|
|
5881
|
+
});
|
|
5882
|
+
return;
|
|
5883
|
+
}
|
|
5884
|
+
setContainerStyles({
|
|
5885
|
+
minHeight: `${window.innerHeight - siblingHeight}px`,
|
|
5886
|
+
});
|
|
5887
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5888
|
+
}, [containerRef.current]);
|
|
5889
|
+
useEffect(() => {
|
|
5890
|
+
if (onChange)
|
|
5891
|
+
onChange(tree);
|
|
5892
|
+
}, [tree, onChange]);
|
|
5893
|
+
useEffect(() => {
|
|
5894
|
+
window.addEventListener('mouseover', handleMouseOver);
|
|
5895
|
+
return () => {
|
|
5896
|
+
window.removeEventListener('mouseover', handleMouseOver);
|
|
5897
|
+
};
|
|
5898
|
+
}, [handleMouseOver]);
|
|
5899
|
+
useEffect(() => {
|
|
5900
|
+
document.addEventListener('click', handleClickOutside);
|
|
5901
|
+
return () => {
|
|
5902
|
+
document.removeEventListener('click', handleClickOutside);
|
|
5903
|
+
};
|
|
5904
|
+
}, [handleClickOutside]);
|
|
5905
|
+
useEffect(() => {
|
|
5906
|
+
handleResizeCanvas();
|
|
5907
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
5908
|
+
}, [containerRef.current]);
|
|
5909
|
+
return (React.createElement(DNDProvider, null,
|
|
5910
|
+
dragItem && React.createElement(DraggableContainer, { id: dragItem }),
|
|
5911
|
+
React.createElement("div", { "data-ctfl-root": true, className: styles$3.container, ref: containerRef, style: containerStyles },
|
|
5912
|
+
userIsDragging && React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitbox }),
|
|
5913
|
+
React.createElement(Dropzone, { zoneId: ROOT_ID, resolveDesignValue: resolveDesignValue }),
|
|
5914
|
+
userIsDragging && (React.createElement(React.Fragment, null,
|
|
5915
|
+
React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitboxLower }),
|
|
5916
|
+
React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.canvasBottomSpacer })))),
|
|
5917
|
+
React.createElement("div", { "data-ctfl-hitboxes": true })));
|
|
4283
5918
|
};
|
|
4284
5919
|
|
|
4285
5920
|
const useInitializeEditor = () => {
|
|
@@ -4323,11 +5958,38 @@ const useInitializeEditor = () => {
|
|
|
4323
5958
|
const VisualEditorRoot = ({ experience }) => {
|
|
4324
5959
|
const initialized = useInitializeEditor();
|
|
4325
5960
|
const setHyperLinkPattern = useEditorStore((state) => state.setHyperLinkPattern);
|
|
5961
|
+
const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
|
|
5962
|
+
const setHoveringZone = useZoneStore((state) => state.setHoveringZone);
|
|
4326
5963
|
useEffect(() => {
|
|
4327
5964
|
if (experience?.hyperlinkPattern) {
|
|
4328
5965
|
setHyperLinkPattern(experience.hyperlinkPattern);
|
|
4329
5966
|
}
|
|
4330
5967
|
}, [experience?.hyperlinkPattern, setHyperLinkPattern]);
|
|
5968
|
+
useEffect(() => {
|
|
5969
|
+
const onMouseMove = (e) => {
|
|
5970
|
+
setMousePosition(e.clientX, e.clientY);
|
|
5971
|
+
const target = e.target;
|
|
5972
|
+
const zoneId = target.closest(`[${CTFL_ZONE_ID}]`)?.getAttribute(CTFL_ZONE_ID);
|
|
5973
|
+
if (zoneId) {
|
|
5974
|
+
setHoveringZone(zoneId);
|
|
5975
|
+
}
|
|
5976
|
+
if (!SimulateDnD$1.isDragging) {
|
|
5977
|
+
return;
|
|
5978
|
+
}
|
|
5979
|
+
if (target.id === NEW_COMPONENT_ID) {
|
|
5980
|
+
return;
|
|
5981
|
+
}
|
|
5982
|
+
SimulateDnD$1.updateDrag(e.clientX, e.clientY);
|
|
5983
|
+
sendMessage(OUTGOING_EVENTS.MouseMove, {
|
|
5984
|
+
clientX: e.pageX - window.scrollX,
|
|
5985
|
+
clientY: e.pageY - window.scrollY,
|
|
5986
|
+
});
|
|
5987
|
+
};
|
|
5988
|
+
document.addEventListener('mousemove', onMouseMove);
|
|
5989
|
+
return () => {
|
|
5990
|
+
document.removeEventListener('mousemove', onMouseMove);
|
|
5991
|
+
};
|
|
5992
|
+
}, [setHoveringZone, setMousePosition]);
|
|
4331
5993
|
if (!initialized)
|
|
4332
5994
|
return null;
|
|
4333
5995
|
return React.createElement(RootRenderer, null);
|