@contentful/experiences-visual-editor-react 1.38.1-dev-20250528T1136-d03d94c.0 → 1.39.0-alpha-20250528T1342-e28bc3d.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 CHANGED
@@ -1,38 +1,268 @@
1
1
  import styleInject from 'style-inject';
2
- import React, { useEffect, useRef, useState, useCallback, forwardRef, useLayoutEffect, useMemo } from 'react';
2
+ import React, { useState, useEffect, useCallback, forwardRef, useMemo, useLayoutEffect, useRef } from 'react';
3
+ import { create } from 'zustand';
4
+ import { produce } from 'immer';
5
+ import { isEqual, omit, isArray, get as get$1 } from 'lodash-es';
3
6
  import { z } from 'zod';
4
- import { omit, isArray, isEqual, get as get$1 } from 'lodash-es';
5
7
  import md5 from 'md5';
6
8
  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';
10
9
  import '@contentful/rich-text-react-renderer';
11
- import { v4 } from 'uuid';
12
- import { createPortal } from 'react-dom';
13
- import classNames from 'classnames';
14
10
 
15
11
  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";
16
12
  styleInject(css_248z$b);
17
13
 
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
+ };
18
232
  const INCOMING_EVENTS$1 = {
19
233
  RequestEditorMode: 'requestEditorMode',
20
234
  RequestReadOnlyMode: 'requestReadOnlyMode',
21
235
  ExperienceUpdated: 'componentTreeUpdated',
236
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
22
237
  ComponentDraggingChanged: 'componentDraggingChanged',
238
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
23
239
  ComponentDragCanceled: 'componentDragCanceled',
240
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
24
241
  ComponentDragStarted: 'componentDragStarted',
242
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
25
243
  ComponentDragEnded: 'componentDragEnded',
244
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
26
245
  ComponentMoveEnded: 'componentMoveEnded',
246
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
27
247
  CanvasResized: 'canvasResized',
248
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
28
249
  SelectComponent: 'selectComponent',
250
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
29
251
  HoverComponent: 'hoverComponent',
30
252
  UpdatedEntity: 'updatedEntity',
31
253
  AssembliesAdded: 'assembliesAdded',
32
254
  AssembliesRegistered: 'assembliesRegistered',
255
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
33
256
  MouseMove: 'mouseMove',
34
257
  RequestedEntities: 'REQUESTED_ENTITIES',
35
258
  };
259
+ const INTERNAL_EVENTS = {
260
+ ComponentsRegistered: 'cfComponentsRegistered',
261
+ VisualEditorInitialize: 'cfVisualEditorInitialize',
262
+ };
263
+ const VISUAL_EDITOR_EVENTS = {
264
+ Ready: 'cfVisualEditorReady',
265
+ };
36
266
  /**
37
267
  * These modes are ONLY intended to be internally used within the context of
38
268
  * editing an experience inside of Contentful Studio. i.e. these modes
@@ -44,40 +274,89 @@ var StudioCanvasMode$3;
44
274
  StudioCanvasMode["EDITOR"] = "editorMode";
45
275
  StudioCanvasMode["NONE"] = "none";
46
276
  })(StudioCanvasMode$3 || (StudioCanvasMode$3 = {}));
47
- const CONTENTFUL_COMPONENTS$2 = {
48
- section: {
49
- id: 'contentful-section',
50
- name: 'Section',
51
- },
52
- container: {
53
- id: 'contentful-container',
54
- name: 'Container',
55
- },
56
- columns: {
57
- id: 'contentful-columns',
58
- name: 'Columns',
59
- },
60
- singleColumn: {
61
- id: 'contentful-single-column',
62
- name: 'Column',
63
- },
64
- button: {
65
- id: 'contentful-button',
66
- name: 'Button',
67
- },
68
- heading: {
69
- id: 'contentful-heading',
70
- name: 'Heading',
71
- },
72
- image: {
73
- id: 'contentful-image',
74
- name: 'Image',
75
- },
76
- richText: {
77
- id: 'contentful-richText',
78
- name: 'Rich Text',
79
- },
80
- text: {
277
+ const ASSEMBLY_NODE_TYPE = 'assembly';
278
+ const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
279
+ const EMPTY_CONTAINER_HEIGHT$1 = '80px';
280
+ const HYPERLINK_DEFAULT_PATTERN = `/{locale}/{entry.fields.slug}/`;
281
+ var PostMessageMethods$3;
282
+ (function (PostMessageMethods) {
283
+ PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
284
+ PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
285
+ })(PostMessageMethods$3 || (PostMessageMethods$3 = {}));
286
+
287
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
288
+ const INCOMING_EVENTS = {
289
+ RequestEditorMode: 'requestEditorMode',
290
+ RequestReadOnlyMode: 'requestReadOnlyMode',
291
+ ExperienceUpdated: 'componentTreeUpdated',
292
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
293
+ ComponentDraggingChanged: 'componentDraggingChanged',
294
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
295
+ ComponentDragCanceled: 'componentDragCanceled',
296
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
297
+ ComponentDragStarted: 'componentDragStarted',
298
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
299
+ ComponentDragEnded: 'componentDragEnded',
300
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
301
+ ComponentMoveEnded: 'componentMoveEnded',
302
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
303
+ CanvasResized: 'canvasResized',
304
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
305
+ SelectComponent: 'selectComponent',
306
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
307
+ HoverComponent: 'hoverComponent',
308
+ UpdatedEntity: 'updatedEntity',
309
+ AssembliesAdded: 'assembliesAdded',
310
+ AssembliesRegistered: 'assembliesRegistered',
311
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
312
+ MouseMove: 'mouseMove',
313
+ RequestedEntities: 'REQUESTED_ENTITIES',
314
+ };
315
+ /**
316
+ * These modes are ONLY intended to be internally used within the context of
317
+ * editing an experience inside of Contentful Studio. i.e. these modes
318
+ * intentionally do not include preview/delivery modes.
319
+ */
320
+ var StudioCanvasMode$2;
321
+ (function (StudioCanvasMode) {
322
+ StudioCanvasMode["READ_ONLY"] = "readOnlyMode";
323
+ StudioCanvasMode["EDITOR"] = "editorMode";
324
+ StudioCanvasMode["NONE"] = "none";
325
+ })(StudioCanvasMode$2 || (StudioCanvasMode$2 = {}));
326
+ const CONTENTFUL_COMPONENTS$1 = {
327
+ section: {
328
+ id: 'contentful-section',
329
+ name: 'Section',
330
+ },
331
+ container: {
332
+ id: 'contentful-container',
333
+ name: 'Container',
334
+ },
335
+ columns: {
336
+ id: 'contentful-columns',
337
+ name: 'Columns',
338
+ },
339
+ singleColumn: {
340
+ id: 'contentful-single-column',
341
+ name: 'Column',
342
+ },
343
+ button: {
344
+ id: 'contentful-button',
345
+ name: 'Button',
346
+ },
347
+ heading: {
348
+ id: 'contentful-heading',
349
+ name: 'Heading',
350
+ },
351
+ image: {
352
+ id: 'contentful-image',
353
+ name: 'Image',
354
+ },
355
+ richText: {
356
+ id: 'contentful-richText',
357
+ name: 'Rich Text',
358
+ },
359
+ text: {
81
360
  id: 'contentful-text',
82
361
  name: 'Text',
83
362
  },
@@ -90,9 +369,6 @@ const CONTENTFUL_COMPONENTS$2 = {
90
369
  name: 'Carousel',
91
370
  },
92
371
  };
93
- const ASSEMBLY_NODE_TYPE$1 = 'assembly';
94
- const ASSEMBLY_DEFAULT_CATEGORY$1 = 'Assemblies';
95
- const ASSEMBLY_BLOCK_NODE_TYPE$1 = 'assemblyBlock';
96
372
  const CF_STYLE_ATTRIBUTES = [
97
373
  'cfVisibility',
98
374
  'cfHorizontalAlignment',
@@ -135,30 +411,24 @@ const CF_STYLE_ATTRIBUTES = [
135
411
  'cfBackgroundImageAlignmentVertical',
136
412
  'cfBackgroundImageAlignmentHorizontal',
137
413
  ];
138
- const EMPTY_CONTAINER_HEIGHT$1 = '80px';
414
+ const EMPTY_CONTAINER_HEIGHT = '80px';
139
415
  const DEFAULT_IMAGE_WIDTH = '500px';
140
- var PostMessageMethods$3;
416
+ var PostMessageMethods$2;
141
417
  (function (PostMessageMethods) {
142
418
  PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
143
419
  PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
144
- })(PostMessageMethods$3 || (PostMessageMethods$3 = {}));
420
+ })(PostMessageMethods$2 || (PostMessageMethods$2 = {}));
145
421
  const SUPPORTED_IMAGE_FORMATS = ['jpg', 'png', 'webp', 'gif', 'avif'];
146
422
 
147
423
  const structureComponentIds = new Set([
148
- CONTENTFUL_COMPONENTS$2.section.id,
149
- CONTENTFUL_COMPONENTS$2.columns.id,
150
- CONTENTFUL_COMPONENTS$2.container.id,
151
- CONTENTFUL_COMPONENTS$2.singleColumn.id,
424
+ CONTENTFUL_COMPONENTS$1.section.id,
425
+ CONTENTFUL_COMPONENTS$1.columns.id,
426
+ CONTENTFUL_COMPONENTS$1.container.id,
427
+ CONTENTFUL_COMPONENTS$1.singleColumn.id,
152
428
  ]);
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 ?? '');
429
+ const allContentfulComponentIds = new Set(Object.values(CONTENTFUL_COMPONENTS$1).map((component) => component.id));
156
430
  const isContentfulStructureComponent = (componentId) => structureComponentIds.has((componentId ?? ''));
157
431
  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;
162
432
  const isStructureWithRelativeHeight = (componentId, height) => {
163
433
  return isContentfulStructureComponent(componentId) && !height?.toString().endsWith('px');
164
434
  };
@@ -1122,51 +1392,6 @@ let DebugLogger$1 = class DebugLogger {
1122
1392
  DebugLogger$1.instance = null;
1123
1393
  DebugLogger$1.getInstance();
1124
1394
 
1125
- const findOutermostCoordinates = (first, second) => {
1126
- return {
1127
- top: Math.min(first.top, second.top),
1128
- right: Math.max(first.right, second.right),
1129
- bottom: Math.max(first.bottom, second.bottom),
1130
- left: Math.min(first.left, second.left),
1131
- };
1132
- };
1133
- const getElementCoordinates = (element) => {
1134
- const rect = element.getBoundingClientRect();
1135
- /**
1136
- * If element does not have children, or element has it's own width or height,
1137
- * return the element's coordinates.
1138
- */
1139
- if (element.children.length === 0 || rect.width !== 0 || rect.height !== 0) {
1140
- return rect;
1141
- }
1142
- const rects = [];
1143
- /**
1144
- * If element has children, or element does not have it's own width and height,
1145
- * we find the cordinates of the children, and assume the outermost coordinates of the children
1146
- * as the coordinate of the element.
1147
- *
1148
- * E.g child1 => {top: 2, bottom: 3, left: 4, right: 6} & child2 => {top: 1, bottom: 8, left: 12, right: 24}
1149
- * The final assumed coordinates of the element would be => { top: 1, right: 24, bottom: 8, left: 4 }
1150
- */
1151
- for (const child of element.children) {
1152
- const childRect = getElementCoordinates(child);
1153
- if (childRect.width !== 0 || childRect.height !== 0) {
1154
- const { top, right, bottom, left } = childRect;
1155
- rects.push({ top, right, bottom, left });
1156
- }
1157
- }
1158
- if (rects.length === 0) {
1159
- return rect;
1160
- }
1161
- const { top, right, bottom, left } = rects.reduce(findOutermostCoordinates);
1162
- return DOMRect.fromRect({
1163
- x: left,
1164
- y: top,
1165
- height: bottom - top,
1166
- width: right - left,
1167
- });
1168
- };
1169
-
1170
1395
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
1171
1396
  const isLinkToAsset = (variable) => {
1172
1397
  if (!variable)
@@ -1578,7 +1803,7 @@ const calculateNodeDefaultHeight = ({ blockId, children, value, }) => {
1578
1803
  if (children.length) {
1579
1804
  return '100%';
1580
1805
  }
1581
- return EMPTY_CONTAINER_HEIGHT$1;
1806
+ return EMPTY_CONTAINER_HEIGHT;
1582
1807
  };
1583
1808
 
1584
1809
  function getOptimizedImageUrl(url, width, quality, format) {
@@ -2009,10 +2234,10 @@ const tryParseMessage = (event) => {
2009
2234
  throw new ParseError(`Field eventData.source must be equal to 'composability-app', instead of '${eventData.source}'`);
2010
2235
  }
2011
2236
  // check eventData.eventType
2012
- const supportedEventTypes = Object.values(INCOMING_EVENTS$1);
2237
+ const supportedEventTypes = Object.values(INCOMING_EVENTS);
2013
2238
  if (!supportedEventTypes.includes(eventData.eventType)) {
2014
2239
  // Expected message: This message is handled in the EntityStore to store fetched entities
2015
- if (eventData.eventType !== PostMessageMethods$3.REQUESTED_ENTITIES) {
2240
+ if (eventData.eventType !== PostMessageMethods$2.REQUESTED_ENTITIES) {
2016
2241
  throw new ParseError(`Field eventData.eventType must be one of the supported values: [${supportedEventTypes.join(', ')}]`);
2017
2242
  }
2018
2243
  }
@@ -2277,7 +2502,7 @@ class EditorEntityStore extends EntityStoreBase {
2277
2502
  }
2278
2503
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
2279
2504
  const newPromise = new Promise((resolve, reject) => {
2280
- const unsubscribe = this.subscribe(PostMessageMethods$3.REQUESTED_ENTITIES, (message) => {
2505
+ const unsubscribe = this.subscribe(PostMessageMethods$2.REQUESTED_ENTITIES, (message) => {
2281
2506
  const messageIds = [
2282
2507
  ...message.entities.map((entity) => entity.sys.id),
2283
2508
  ...(message.missingEntityIds ?? []),
@@ -2299,7 +2524,7 @@ class EditorEntityStore extends EntityStoreBase {
2299
2524
  ids.forEach((id) => this.cleanupPromise(id));
2300
2525
  unsubscribe();
2301
2526
  }, this.timeoutDuration);
2302
- this.sendMessage(PostMessageMethods$3.REQUEST_ENTITIES, {
2527
+ this.sendMessage(PostMessageMethods$2.REQUEST_ENTITIES, {
2303
2528
  entityIds: missing,
2304
2529
  entityType: type,
2305
2530
  locale: this.locale,
@@ -2405,563 +2630,70 @@ class EditorModeEntityStore extends EditorEntityStore {
2405
2630
  const entity = this.getEntryOrAsset(entityLinkOrEntity, path.join('/'));
2406
2631
  if (!entity) {
2407
2632
  return;
2408
- }
2409
- const fieldValue = get(entity, path);
2410
- return transformAssetFileToUrl(fieldValue);
2411
- }
2412
- }
2413
-
2414
- var VisualEditorMode$1;
2415
- (function (VisualEditorMode) {
2416
- VisualEditorMode["LazyLoad"] = "lazyLoad";
2417
- VisualEditorMode["InjectScript"] = "injectScript";
2418
- })(VisualEditorMode$1 || (VisualEditorMode$1 = {}));
2419
-
2420
- class DeepReference {
2421
- constructor({ path, dataSource }) {
2422
- const { key, field, referentField } = parseDataSourcePathWithL1DeepBindings(path);
2423
- this.originalPath = path;
2424
- this.entityId = dataSource[key].sys.id;
2425
- this.entityLink = dataSource[key];
2426
- this.field = field;
2427
- this.referentField = referentField;
2428
- }
2429
- get headEntityId() {
2430
- return this.entityId;
2431
- }
2432
- /**
2433
- * Extracts referent from the path, using EntityStore as source of
2434
- * entities during the resolution path.
2435
- */
2436
- extractReferent(entityStore) {
2437
- const headEntity = entityStore.getEntityFromLink(this.entityLink);
2438
- const maybeReferentLink = headEntity?.fields[this.field];
2439
- if (undefined === maybeReferentLink) {
2440
- // field references nothing (or even field doesn't exist)
2441
- return undefined;
2442
- }
2443
- if (!isLink(maybeReferentLink)) {
2444
- // Scenario of "impostor referent", where one of the deepPath's segments is not a Link but some other type
2445
- // Under normal circumstance we expect field to be a Link, but it could be an "impostor"
2446
- // eg. `Text` or `Number` or anything like that; could be due to CT changes or manual path creation via CMA
2447
- return undefined;
2448
- }
2449
- return maybeReferentLink;
2450
- }
2451
- static from(opt) {
2452
- return new DeepReference(opt);
2453
- }
2454
- }
2455
- function gatherDeepReferencesFromTree(startingNode, dataSource) {
2456
- const deepReferences = [];
2457
- treeVisit(startingNode, (node) => {
2458
- if (!node.data.props)
2459
- return;
2460
- for (const [, variableMapping] of Object.entries(node.data.props)) {
2461
- if (variableMapping.type !== 'BoundValue')
2462
- continue;
2463
- if (!isDeepPath(variableMapping.path))
2464
- continue;
2465
- deepReferences.push(DeepReference.from({
2466
- path: variableMapping.path,
2467
- dataSource,
2468
- }));
2469
- }
2470
- });
2471
- return deepReferences;
2472
- }
2473
-
2474
- const useDraggedItemStore = create((set) => ({
2475
- draggedItem: undefined,
2476
- hoveredComponentId: undefined,
2477
- domRect: undefined,
2478
- componentId: '',
2479
- isDraggingOnCanvas: false,
2480
- onBeforeCaptureId: '',
2481
- mouseX: 0,
2482
- mouseY: 0,
2483
- scrollY: 0,
2484
- setComponentId(id) {
2485
- set({ componentId: id });
2486
- },
2487
- setHoveredComponentId(id) {
2488
- set({ hoveredComponentId: id });
2489
- },
2490
- updateItem: (item) => {
2491
- set({ draggedItem: item });
2492
- },
2493
- setDraggingOnCanvas: (isDraggingOnCanvas) => {
2494
- set({ isDraggingOnCanvas });
2495
- },
2496
- setOnBeforeCaptureId: (onBeforeCaptureId) => {
2497
- set({ onBeforeCaptureId });
2498
- },
2499
- setMousePosition(x, y) {
2500
- set({ mouseX: x, mouseY: y });
2501
- },
2502
- setDomRect(domRect) {
2503
- set({ domRect });
2504
- },
2505
- setScrollY(y) {
2506
- set({ scrollY: y });
2507
- },
2508
- }));
2509
-
2510
- const SCROLL_STATES = {
2511
- Start: 'scrollStart',
2512
- IsScrolling: 'isScrolling',
2513
- End: 'scrollEnd',
2514
- };
2515
- const OUTGOING_EVENTS = {
2516
- Connected: 'connected',
2517
- DesignTokens: 'registerDesignTokens',
2518
- RegisteredBreakpoints: 'registeredBreakpoints',
2519
- MouseMove: 'mouseMove',
2520
- NewHoveredElement: 'newHoveredElement',
2521
- ComponentSelected: 'componentSelected',
2522
- RegisteredComponents: 'registeredComponents',
2523
- RequestComponentTreeUpdate: 'requestComponentTreeUpdate',
2524
- ComponentDragCanceled: 'componentDragCanceled',
2525
- ComponentDropped: 'componentDropped',
2526
- ComponentMoved: 'componentMoved',
2527
- CanvasReload: 'canvasReload',
2528
- UpdateSelectedComponentCoordinates: 'updateSelectedComponentCoordinates',
2529
- CanvasScroll: 'canvasScrolling',
2530
- CanvasError: 'canvasError',
2531
- ComponentMoveStarted: 'componentMoveStarted',
2532
- ComponentMoveEnded: 'componentMoveEnded',
2533
- OutsideCanvasClick: 'outsideCanvasClick',
2534
- SDKFeatures: 'sdkFeatures',
2535
- RequestEntities: 'REQUEST_ENTITIES',
2536
- };
2537
- const INCOMING_EVENTS = {
2538
- RequestEditorMode: 'requestEditorMode',
2539
- RequestReadOnlyMode: 'requestReadOnlyMode',
2540
- ExperienceUpdated: 'componentTreeUpdated',
2541
- ComponentDraggingChanged: 'componentDraggingChanged',
2542
- ComponentDragCanceled: 'componentDragCanceled',
2543
- ComponentDragStarted: 'componentDragStarted',
2544
- ComponentDragEnded: 'componentDragEnded',
2545
- ComponentMoveEnded: 'componentMoveEnded',
2546
- CanvasResized: 'canvasResized',
2547
- SelectComponent: 'selectComponent',
2548
- HoverComponent: 'hoverComponent',
2549
- UpdatedEntity: 'updatedEntity',
2550
- AssembliesAdded: 'assembliesAdded',
2551
- AssembliesRegistered: 'assembliesRegistered',
2552
- MouseMove: 'mouseMove',
2553
- RequestedEntities: 'REQUESTED_ENTITIES',
2554
- };
2555
- const INTERNAL_EVENTS = {
2556
- ComponentsRegistered: 'cfComponentsRegistered',
2557
- VisualEditorInitialize: 'cfVisualEditorInitialize',
2558
- };
2559
- const VISUAL_EDITOR_EVENTS = {
2560
- Ready: 'cfVisualEditorReady',
2561
- };
2562
- /**
2563
- * These modes are ONLY intended to be internally used within the context of
2564
- * editing an experience inside of Contentful Studio. i.e. these modes
2565
- * intentionally do not include preview/delivery modes.
2566
- */
2567
- var StudioCanvasMode$2;
2568
- (function (StudioCanvasMode) {
2569
- StudioCanvasMode["READ_ONLY"] = "readOnlyMode";
2570
- StudioCanvasMode["EDITOR"] = "editorMode";
2571
- StudioCanvasMode["NONE"] = "none";
2572
- })(StudioCanvasMode$2 || (StudioCanvasMode$2 = {}));
2573
- const CONTENTFUL_COMPONENTS$1 = {
2574
- section: {
2575
- id: 'contentful-section',
2576
- name: 'Section',
2577
- },
2578
- container: {
2579
- id: 'contentful-container',
2580
- name: 'Container',
2581
- },
2582
- columns: {
2583
- id: 'contentful-columns',
2584
- name: 'Columns',
2585
- },
2586
- singleColumn: {
2587
- id: 'contentful-single-column',
2588
- name: 'Column',
2589
- },
2590
- button: {
2591
- id: 'contentful-button',
2592
- name: 'Button',
2593
- },
2594
- heading: {
2595
- id: 'contentful-heading',
2596
- name: 'Heading',
2597
- },
2598
- image: {
2599
- id: 'contentful-image',
2600
- name: 'Image',
2601
- },
2602
- richText: {
2603
- id: 'contentful-richText',
2604
- name: 'Rich Text',
2605
- },
2606
- text: {
2607
- id: 'contentful-text',
2608
- name: 'Text',
2609
- },
2610
- divider: {
2611
- id: 'contentful-divider',
2612
- name: 'Divider',
2613
- },
2614
- carousel: {
2615
- id: 'contentful-carousel',
2616
- name: 'Carousel',
2617
- },
2618
- };
2619
- const ASSEMBLY_NODE_TYPE = 'assembly';
2620
- const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
2621
- const ASSEMBLY_BLOCK_NODE_TYPE = 'assemblyBlock';
2622
- const ASSEMBLY_NODE_TYPES = [ASSEMBLY_NODE_TYPE, ASSEMBLY_BLOCK_NODE_TYPE];
2623
- const EMPTY_CONTAINER_HEIGHT = '80px';
2624
- const HYPERLINK_DEFAULT_PATTERN = `/{locale}/{entry.fields.slug}/`;
2625
- var PostMessageMethods$2;
2626
- (function (PostMessageMethods) {
2627
- PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
2628
- PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
2629
- })(PostMessageMethods$2 || (PostMessageMethods$2 = {}));
2630
-
2631
- const DRAGGABLE_HEIGHT = 30;
2632
- const DRAGGABLE_WIDTH = 50;
2633
- const DRAG_PADDING = 4;
2634
- const ROOT_ID = 'root';
2635
- const COMPONENT_LIST_ID = 'component-list';
2636
- const NEW_COMPONENT_ID = 'ctfl-new-draggable';
2637
- const CTFL_ZONE_ID = 'data-ctfl-zone-id';
2638
- const CTFL_DRAGGING_ELEMENT = 'data-ctfl-dragging-element';
2639
- const HITBOX = {
2640
- WIDTH: 70,
2641
- HEIGHT: 20,
2642
- INITIAL_OFFSET: 10,
2643
- OFFSET_INCREMENT: 8,
2644
- MIN_HEIGHT: 45,
2645
- MIN_DEPTH_HEIGHT: 20,
2646
- DEEP_ZONE: 5,
2647
- };
2648
- var TreeAction;
2649
- (function (TreeAction) {
2650
- TreeAction[TreeAction["REMOVE_NODE"] = 0] = "REMOVE_NODE";
2651
- TreeAction[TreeAction["ADD_NODE"] = 1] = "ADD_NODE";
2652
- TreeAction[TreeAction["MOVE_NODE"] = 2] = "MOVE_NODE";
2653
- TreeAction[TreeAction["UPDATE_NODE"] = 3] = "UPDATE_NODE";
2654
- TreeAction[TreeAction["REORDER_NODE"] = 4] = "REORDER_NODE";
2655
- TreeAction[TreeAction["REPLACE_NODE"] = 5] = "REPLACE_NODE";
2656
- })(TreeAction || (TreeAction = {}));
2657
- var HitboxDirection;
2658
- (function (HitboxDirection) {
2659
- HitboxDirection[HitboxDirection["TOP"] = 0] = "TOP";
2660
- HitboxDirection[HitboxDirection["LEFT"] = 1] = "LEFT";
2661
- HitboxDirection[HitboxDirection["RIGHT"] = 2] = "RIGHT";
2662
- HitboxDirection[HitboxDirection["BOTTOM"] = 3] = "BOTTOM";
2663
- HitboxDirection[HitboxDirection["SELF_VERTICAL"] = 4] = "SELF_VERTICAL";
2664
- HitboxDirection[HitboxDirection["SELF_HORIZONTAL"] = 5] = "SELF_HORIZONTAL";
2665
- })(HitboxDirection || (HitboxDirection = {}));
2666
- var DraggablePosition;
2667
- (function (DraggablePosition) {
2668
- DraggablePosition[DraggablePosition["CENTERED"] = 0] = "CENTERED";
2669
- DraggablePosition[DraggablePosition["MOUSE_POSITION"] = 1] = "MOUSE_POSITION";
2670
- })(DraggablePosition || (DraggablePosition = {}));
2671
-
2672
- function useDraggablePosition({ draggableId, draggableRef, position }) {
2673
- const isDraggingOnCanvas = useDraggedItemStore((state) => state.isDraggingOnCanvas);
2674
- const draggingId = useDraggedItemStore((state) => state.onBeforeCaptureId);
2675
- const preDragDomRect = useDraggedItemStore((state) => state.domRect);
2676
- useEffect(() => {
2677
- const el = draggableRef?.current ??
2678
- document.querySelector(`[${CTFL_DRAGGING_ELEMENT}][data-cf-node-id="${draggableId}"]`);
2679
- if (!isDraggingOnCanvas || draggingId !== draggableId || !el) {
2680
- return;
2681
- }
2682
- const isCentered = position === DraggablePosition.CENTERED || !preDragDomRect;
2683
- const domRect = isCentered ? el.getBoundingClientRect() : preDragDomRect;
2684
- const { mouseX, mouseY } = useDraggedItemStore.getState();
2685
- const top = isCentered ? mouseY - domRect.height / 2 : domRect.top;
2686
- const left = isCentered ? mouseX - domRect.width / 2 : domRect.left;
2687
- el.style.position = 'fixed';
2688
- el.style.left = `${left}px`;
2689
- el.style.top = `${top}px`;
2690
- el.style.width = `${domRect.width}px`;
2691
- el.style.height = `${domRect.height}px`;
2692
- }, [draggableRef, draggableId, isDraggingOnCanvas, draggingId, position, preDragDomRect]);
2693
- }
2694
-
2695
- function getStyle$2(style, snapshot) {
2696
- if (!snapshot.isDropAnimating) {
2697
- return style;
2698
- }
2699
- return {
2700
- ...style,
2701
- // cannot be 0, but make it super tiny
2702
- transitionDuration: `0.001s`,
2703
- };
2704
- }
2705
- const DraggableContainer = ({ id }) => {
2706
- const ref = useRef(null);
2707
- useDraggablePosition({
2708
- draggableId: id,
2709
- draggableRef: ref,
2710
- position: DraggablePosition.CENTERED,
2711
- });
2712
- return (React.createElement("div", { id: COMPONENT_LIST_ID, style: {
2713
- position: 'absolute',
2714
- top: 0,
2715
- left: 0,
2716
- pointerEvents: 'none',
2717
- zIndex: -1,
2718
- } },
2719
- React.createElement(Droppable, { droppableId: COMPONENT_LIST_ID, isDropDisabled: true }, (provided) => (React.createElement("div", { ...provided.droppableProps, ref: provided.innerRef },
2720
- 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) => {
2721
- provided.innerRef(node);
2722
- ref.current = node;
2723
- }, ...provided.draggableProps, ...provided.dragHandleProps, style: {
2724
- ...getStyle$2(provided.draggableProps.style, snapshot),
2725
- width: DRAGGABLE_WIDTH,
2726
- height: DRAGGABLE_HEIGHT,
2727
- pointerEvents: 'none',
2728
- } }))),
2729
- provided.placeholder)))));
2730
- };
2731
-
2732
- function getItemFromTree(id, node) {
2733
- // Check if the current node's id matches the search id
2734
- if (node.data.id === id) {
2735
- return node;
2736
- }
2737
- // Recursively search through each child
2738
- for (const child of node.children) {
2739
- const foundNode = getItemFromTree(id, child);
2740
- if (foundNode) {
2741
- // Node found in children
2742
- return foundNode;
2743
- }
2744
- }
2745
- // If the node is not found in this branch of the tree, return undefined
2746
- return undefined;
2747
- }
2748
- function findDepthById(node, id, currentDepth = 1) {
2749
- if (node.data.id === id) {
2750
- return currentDepth;
2751
- }
2752
- // If the node has children, check each one
2753
- for (const child of node.children) {
2754
- const childDepth = findDepthById(child, id, currentDepth + 1);
2755
- if (childDepth !== -1) {
2756
- return childDepth; // Found the node in a child
2757
- }
2758
- }
2759
- return -1; // Node not found in this branch
2760
- }
2761
- const getChildFromTree = (parentId, index, node) => {
2762
- // Check if the current node's id matches the search id
2763
- if (node.data.id === parentId) {
2764
- return node.children[index];
2765
- }
2766
- // Recursively search through each child
2767
- for (const child of node.children) {
2768
- const foundNode = getChildFromTree(parentId, index, child);
2769
- if (foundNode) {
2770
- // Node found in children
2771
- return foundNode;
2772
- }
2773
- }
2774
- // If the node is not found in this branch of the tree, return undefined
2775
- return undefined;
2776
- };
2777
- const getItem = (selector, tree) => {
2778
- return getItemFromTree(selector.id, {
2779
- type: 'block',
2780
- data: {
2781
- id: ROOT_ID,
2782
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2783
- },
2784
- children: tree.root.children,
2785
- });
2786
- };
2787
- const getItemDepthFromNode = (selector, node) => {
2788
- return findDepthById(node, selector.id);
2789
- };
2790
-
2791
- function updateNode(nodeId, updatedNode, node) {
2792
- if (node.data.id === nodeId) {
2793
- node.data = updatedNode.data;
2794
- return;
2795
- }
2796
- node.children.forEach((childNode) => updateNode(nodeId, updatedNode, childNode));
2797
- }
2798
- function replaceNode(indexToReplace, updatedNode, node) {
2799
- if (node.data.id === updatedNode.parentId) {
2800
- node.children = [
2801
- ...node.children.slice(0, indexToReplace),
2802
- updatedNode,
2803
- ...node.children.slice(indexToReplace + 1),
2804
- ];
2805
- return;
2806
- }
2807
- node.children.forEach((childNode) => replaceNode(indexToReplace, updatedNode, childNode));
2808
- }
2809
- function removeChildNode(indexToRemove, nodeId, parentNodeId, node) {
2810
- if (node.data.id === parentNodeId) {
2811
- const childIndex = node.children.findIndex((child) => child.data.id === nodeId);
2812
- node.children.splice(childIndex === -1 ? indexToRemove : childIndex, 1);
2813
- return;
2814
- }
2815
- node.children.forEach((childNode) => removeChildNode(indexToRemove, nodeId, parentNodeId, childNode));
2816
- }
2817
- function addChildNode(indexToAdd, parentNodeId, nodeToAdd, node) {
2818
- if (node.data.id === parentNodeId) {
2819
- node.children = [
2820
- ...node.children.slice(0, indexToAdd),
2821
- nodeToAdd,
2822
- ...node.children.slice(indexToAdd),
2823
- ];
2824
- return;
2825
- }
2826
- node.children.forEach((childNode) => addChildNode(indexToAdd, parentNodeId, nodeToAdd, childNode));
2827
- }
2828
- function reorderChildNode(oldIndex, newIndex, parentNodeId, node) {
2829
- if (node.data.id === parentNodeId) {
2830
- // Remove the child from the old position
2831
- const [childToMove] = node.children.splice(oldIndex, 1);
2832
- // Insert the child at the new position
2833
- node.children.splice(newIndex, 0, childToMove);
2834
- return;
2835
- }
2836
- node.children.forEach((childNode) => reorderChildNode(oldIndex, newIndex, parentNodeId, childNode));
2837
- }
2838
- function reparentChildNode(oldIndex, newIndex, sourceNodeId, destinationNodeId, node) {
2839
- const nodeToMove = getChildFromTree(sourceNodeId, oldIndex, node);
2840
- if (!nodeToMove) {
2841
- return;
2842
- }
2843
- removeChildNode(oldIndex, nodeToMove.data.id, sourceNodeId, node);
2844
- addChildNode(newIndex, destinationNodeId, nodeToMove, node);
2845
- }
2846
-
2847
- function missingNodeAction({ index, nodeAdded, child, tree, parentNodeId, currentNode, }) {
2848
- if (nodeAdded) {
2849
- return { type: TreeAction.ADD_NODE, indexToAdd: index, nodeToAdd: child, parentNodeId };
2850
- }
2851
- const item = getItem({ id: child.data.id }, tree);
2852
- if (item) {
2853
- const parentNode = getItem({ id: item.parentId }, tree);
2854
- if (!parentNode) {
2855
- return null;
2856
- }
2857
- const sourceIndex = parentNode.children.findIndex((c) => c.data.id === child.data.id);
2858
- return { type: TreeAction.MOVE_NODE, sourceIndex, destinationIndex: index, parentNodeId };
2859
- }
2860
- return {
2861
- type: TreeAction.REPLACE_NODE,
2862
- originalId: currentNode.children[index].data.id,
2863
- indexToReplace: index,
2864
- node: child,
2865
- };
2866
- }
2867
- function matchingNodeAction({ index, originalIndex, nodeRemoved, nodeAdded, parentNodeId, }) {
2868
- if (index !== originalIndex && !nodeRemoved && !nodeAdded) {
2869
- return {
2870
- type: TreeAction.REORDER_NODE,
2871
- sourceIndex: originalIndex,
2872
- destinationIndex: index,
2873
- parentNodeId,
2874
- };
2633
+ }
2634
+ const fieldValue = get(entity, path);
2635
+ return transformAssetFileToUrl(fieldValue);
2875
2636
  }
2876
- return null;
2877
2637
  }
2878
- function compareNodes({ currentNode, updatedNode, originalTree, differences = [], }) {
2879
- // In the end, this map contains the list of nodes that are not present
2880
- // in the updated tree and must be removed
2881
- const map = new Map();
2882
- if (!currentNode || !updatedNode) {
2883
- return differences;
2638
+
2639
+ var VisualEditorMode$1;
2640
+ (function (VisualEditorMode) {
2641
+ VisualEditorMode["LazyLoad"] = "lazyLoad";
2642
+ VisualEditorMode["InjectScript"] = "injectScript";
2643
+ })(VisualEditorMode$1 || (VisualEditorMode$1 = {}));
2644
+
2645
+ class DeepReference {
2646
+ constructor({ path, dataSource }) {
2647
+ const { key, field, referentField } = parseDataSourcePathWithL1DeepBindings(path);
2648
+ this.originalPath = path;
2649
+ this.entityId = dataSource[key].sys.id;
2650
+ this.entityLink = dataSource[key];
2651
+ this.field = field;
2652
+ this.referentField = referentField;
2653
+ }
2654
+ get headEntityId() {
2655
+ return this.entityId;
2884
2656
  }
2885
- // On each tree level, consider only the children of the current node to differentiate between added, removed, or replaced case
2886
- const currentNodeCount = currentNode.children.length;
2887
- const updatedNodeCount = updatedNode.children.length;
2888
- const nodeRemoved = currentNodeCount > updatedNodeCount;
2889
- const nodeAdded = currentNodeCount < updatedNodeCount;
2890
- const parentNodeId = updatedNode.data.id;
2891
2657
  /**
2892
- * The data of the current node has changed, we need to update
2893
- * this node to reflect the data change. (design, content, unbound values)
2658
+ * Extracts referent from the path, using EntityStore as source of
2659
+ * entities during the resolution path.
2894
2660
  */
2895
- if (!isEqual(currentNode.data, updatedNode.data)) {
2896
- differences.push({
2897
- type: TreeAction.UPDATE_NODE,
2898
- nodeId: currentNode.data.id,
2899
- node: updatedNode,
2900
- });
2901
- }
2902
- // Map children of the first tree by their ID
2903
- currentNode.children.forEach((child, index) => map.set(child.data.id, index));
2904
- // Compare with the second tree
2905
- updatedNode.children.forEach((child, index) => {
2906
- const childId = child.data.id;
2907
- // The original tree does not have this node in the updated tree.
2908
- if (!map.has(childId)) {
2909
- const diff = missingNodeAction({
2910
- index,
2911
- child,
2912
- nodeAdded,
2913
- parentNodeId,
2914
- tree: originalTree,
2915
- currentNode,
2916
- });
2917
- if (diff?.type === TreeAction.REPLACE_NODE) {
2918
- // Remove it from the deletion map to avoid adding another REMOVE_NODE action
2919
- map.delete(diff.originalId);
2920
- }
2921
- return differences.push(diff);
2661
+ extractReferent(entityStore) {
2662
+ const headEntity = entityStore.getEntityFromLink(this.entityLink);
2663
+ const maybeReferentLink = headEntity?.fields[this.field];
2664
+ if (undefined === maybeReferentLink) {
2665
+ // field references nothing (or even field doesn't exist)
2666
+ return undefined;
2922
2667
  }
2923
- const originalIndex = map.get(childId);
2924
- const diff = matchingNodeAction({
2925
- index,
2926
- originalIndex,
2927
- nodeAdded,
2928
- nodeRemoved,
2929
- parentNodeId,
2930
- });
2931
- differences.push(diff);
2932
- map.delete(childId);
2933
- compareNodes({
2934
- currentNode: currentNode.children[originalIndex],
2935
- updatedNode: child,
2936
- originalTree,
2937
- differences,
2938
- });
2939
- });
2940
- map.forEach((index, key) => {
2941
- // If the node count of the entire tree doesn't signify
2942
- // a node was removed, don't add that as a diff
2943
- if (!nodeRemoved) {
2944
- return;
2668
+ if (!isLink(maybeReferentLink)) {
2669
+ // Scenario of "impostor referent", where one of the deepPath's segments is not a Link but some other type
2670
+ // Under normal circumstance we expect field to be a Link, but it could be an "impostor"
2671
+ // eg. `Text` or `Number` or anything like that; could be due to CT changes or manual path creation via CMA
2672
+ return undefined;
2945
2673
  }
2946
- // Remaining nodes in the map are removed in the second tree
2947
- differences.push({
2948
- type: TreeAction.REMOVE_NODE,
2949
- indexToRemove: index,
2950
- parentNodeId,
2951
- idToRemove: key,
2952
- });
2953
- });
2954
- return differences;
2674
+ return maybeReferentLink;
2675
+ }
2676
+ static from(opt) {
2677
+ return new DeepReference(opt);
2678
+ }
2955
2679
  }
2956
- function getTreeDiffs(tree1, tree2, originalTree) {
2957
- const differences = [];
2958
- compareNodes({
2959
- currentNode: tree1,
2960
- updatedNode: tree2,
2961
- originalTree,
2962
- differences,
2680
+ function gatherDeepReferencesFromTree(startingNode, dataSource) {
2681
+ const deepReferences = [];
2682
+ treeVisit(startingNode, (node) => {
2683
+ if (!node.data.props)
2684
+ return;
2685
+ for (const [, variableMapping] of Object.entries(node.data.props)) {
2686
+ if (variableMapping.type !== 'BoundValue')
2687
+ continue;
2688
+ if (!isDeepPath(variableMapping.path))
2689
+ continue;
2690
+ deepReferences.push(DeepReference.from({
2691
+ path: variableMapping.path,
2692
+ dataSource,
2693
+ }));
2694
+ }
2963
2695
  });
2964
- return differences.filter((diff) => diff);
2696
+ return deepReferences;
2965
2697
  }
2966
2698
 
2967
2699
  const useTreeStore = create((set, get) => ({
@@ -2995,20 +2727,6 @@ const useTreeStore = create((set, get) => ({
2995
2727
  });
2996
2728
  }));
2997
2729
  },
2998
- /**
2999
- * NOTE: this is for debugging purposes only as it causes ugly canvas flash.
3000
- *
3001
- * Force updates entire tree. Usually shouldn't be used as updateTree()
3002
- * uses smart update algorithm based on diffs. But for troubleshooting
3003
- * you may want to force update the tree so leaving this in.
3004
- */
3005
- updateTreeForced: (tree) => {
3006
- set({
3007
- tree,
3008
- // Breakpoints must be updated, as we receive completely new tree with possibly new breakpoints
3009
- breakpoints: tree?.root?.data?.breakpoints || [],
3010
- });
3011
- },
3012
2730
  updateTree: (tree) => {
3013
2731
  const currentTree = get().tree;
3014
2732
  /**
@@ -3054,21 +2772,6 @@ const useTreeStore = create((set, get) => ({
3054
2772
  state.breakpoints = tree?.root?.data?.breakpoints || [];
3055
2773
  }));
3056
2774
  },
3057
- addChild: (index, parentId, node) => {
3058
- set(produce((state) => {
3059
- addChildNode(index, parentId, node, state.tree.root);
3060
- }));
3061
- },
3062
- reorderChildren: (destinationIndex, destinationParentId, sourceIndex) => {
3063
- set(produce((state) => {
3064
- reorderChildNode(sourceIndex, destinationIndex, destinationParentId, state.tree.root);
3065
- }));
3066
- },
3067
- reparentChild: (destinationIndex, destinationParentId, sourceIndex, sourceParentId) => {
3068
- set(produce((state) => {
3069
- reparentChildNode(sourceIndex, destinationIndex, sourceParentId, destinationParentId, state.tree.root);
3070
- }));
3071
- },
3072
2775
  }));
3073
2776
  const hasBreakpointDiffs = (currentTree, newTree) => {
3074
2777
  const currentBreakpoints = currentTree?.root?.data?.breakpoints ?? [];
@@ -3086,8 +2789,8 @@ const cloneDeepAsPOJO = (obj) => {
3086
2789
  return JSON.parse(JSON.stringify(obj));
3087
2790
  };
3088
2791
 
3089
- 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";
3090
- var styles$3 = {"hitbox":"render-module_hitbox__l4ysJ","hitboxLower":"render-module_hitboxLower__tgsA1","canvasBottomSpacer":"render-module_canvasBottomSpacer__JuxVh","container":"render-module_container__-C3d7"};
2792
+ var css_248z$a = ".RootRenderer-module_rootContainer__9UawM {\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";
2793
+ var styles$2 = {"rootContainer":"RootRenderer-module_rootContainer__9UawM"};
3091
2794
  styleInject(css_248z$a);
3092
2795
 
3093
2796
  // TODO: In order to support integrations without React, we should extract this heavy logic into simple
@@ -3126,66 +2829,6 @@ const useBreakpoints = (breakpoints) => {
3126
2829
  return { resolveDesignValue };
3127
2830
  };
3128
2831
 
3129
- /**
3130
- * This function gets the element co-ordinates of a specified component in the DOM and its parent
3131
- * and sends the DOM Rect to the client app
3132
- */
3133
- const sendSelectedComponentCoordinates = (instanceId) => {
3134
- const selection = getSelectionNodes(instanceId);
3135
- if (selection?.target) {
3136
- const sendUpdateSelectedComponentCoordinates = () => {
3137
- sendMessage(OUTGOING_EVENTS.UpdateSelectedComponentCoordinates, {
3138
- selectedNodeCoordinates: getElementCoordinates(selection.target),
3139
- selectedAssemblyChildCoordinates: selection.patternChild
3140
- ? getElementCoordinates(selection.patternChild)
3141
- : undefined,
3142
- parentCoordinates: selection.parent ? getElementCoordinates(selection.parent) : undefined,
3143
- });
3144
- };
3145
- // If the target contains an image, wait for this image to be loaded before sending the coordinates
3146
- const childImage = selection.target.querySelector('img');
3147
- if (childImage) {
3148
- const handleImageLoad = () => {
3149
- sendUpdateSelectedComponentCoordinates();
3150
- childImage.removeEventListener('load', handleImageLoad);
3151
- };
3152
- childImage.addEventListener('load', handleImageLoad);
3153
- }
3154
- sendUpdateSelectedComponentCoordinates();
3155
- }
3156
- };
3157
- const getSelectionNodes = (instanceId) => {
3158
- if (!instanceId)
3159
- return;
3160
- let selectedNode = document.querySelector(`[data-cf-node-id="${instanceId}"]`);
3161
- let selectedPatternChild = null;
3162
- let selectedParent = null;
3163
- // Use RegEx instead of split to match the last occurrence of '---' in the instanceId instead of the first one
3164
- const idMatch = instanceId.match(/(.*)---(.*)/);
3165
- const rootNodeId = idMatch?.[1] ?? instanceId;
3166
- const nodeLocation = idMatch?.[2];
3167
- const isNestedPattern = nodeLocation && selectedNode?.dataset?.cfNodeBlockType === ASSEMBLY_NODE_TYPE;
3168
- const isPatternChild = !isNestedPattern && nodeLocation;
3169
- if (isPatternChild) {
3170
- // For pattern child nodes, render the pattern itself as selected component
3171
- selectedPatternChild = selectedNode;
3172
- selectedNode = document.querySelector(`[data-cf-node-id="${rootNodeId}"]`);
3173
- }
3174
- else if (isNestedPattern) {
3175
- // For nested patterns, return the upper pattern as parent
3176
- selectedParent = document.querySelector(`[data-cf-node-id="${rootNodeId}"]`);
3177
- }
3178
- else {
3179
- // Find the next valid parent of the selected element
3180
- selectedParent = selectedNode?.parentElement ?? null;
3181
- // Ensure that the selection parent is a VisualEditorBlock
3182
- while (selectedParent && !selectedParent.dataset?.cfNodeId) {
3183
- selectedParent = selectedParent?.parentElement;
3184
- }
3185
- }
3186
- return { target: selectedNode, patternChild: selectedPatternChild, parent: selectedParent };
3187
- };
3188
-
3189
2832
  // Note: During development, the hot reloading might empty this and it
3190
2833
  // stays empty leading to not rendering assemblies. Ideally, this is
3191
2834
  // integrated into the state machine to keep track of its state.
@@ -3219,14 +2862,10 @@ const useEditorStore = create((set, get) => ({
3219
2862
  dataSource: {},
3220
2863
  hyperLinkPattern: undefined,
3221
2864
  unboundValues: {},
3222
- selectedNodeId: null,
3223
2865
  locale: null,
3224
2866
  setHyperLinkPattern: (pattern) => {
3225
2867
  set({ hyperLinkPattern: pattern });
3226
2868
  },
3227
- setSelectedNodeId: (id) => {
3228
- set({ selectedNodeId: id });
3229
- },
3230
2869
  setDataSource(data) {
3231
2870
  const dataSource = get().dataSource;
3232
2871
  const newDataSource = { ...dataSource, ...data };
@@ -3258,6 +2897,7 @@ const useEditorStore = create((set, get) => ({
3258
2897
  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";
3259
2898
  styleInject(css_248z$8);
3260
2899
 
2900
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
3261
2901
  /**
3262
2902
  * These modes are ONLY intended to be internally used within the context of
3263
2903
  * editing an experience inside of Contentful Studio. i.e. these modes
@@ -3804,8 +3444,8 @@ var VisualEditorMode;
3804
3444
  VisualEditorMode["InjectScript"] = "injectScript";
3805
3445
  })(VisualEditorMode || (VisualEditorMode = {}));
3806
3446
 
3807
- 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";
3808
- styleInject(css_248z$2$1);
3447
+ var css_248z$2 = ".contentful-container {\n position: relative;\n display: flex;\n box-sizing: border-box;\n pointer-events: all;\n}\n\n.contentful-container::-webkit-scrollbar {\n display: none; /* Safari and Chrome */\n}\n\n.cf-container-wrapper {\n position: relative;\n width: 100%;\n}\n\n.contentful-container:after {\n content: '';\n display: block;\n position: absolute;\n pointer-events: none;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n overflow-x: clip;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 12px;\n color: var(--exp-builder-gray400);\n z-index: 1;\n}\n\n.contentful-section-label:after {\n content: 'Section';\n}\n\n.contentful-container-label:after {\n content: 'Container';\n}\n\n/* used by ContentfulSectionAsHyperlink.tsx */\n\n.contentful-container-link,\n.contentful-container-link:active,\n.contentful-container-link:visited,\n.contentful-container-link:hover,\n.contentful-container-link:read-write,\n.contentful-container-link:focus-visible {\n color: inherit;\n text-decoration: unset;\n outline: unset;\n}\n";
3448
+ styleInject(css_248z$2);
3809
3449
 
3810
3450
  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) => {
3811
3451
  return (React.createElement("div", { id: id, ref: ref, style: {
@@ -3829,34 +3469,14 @@ const Flex = forwardRef(({ id, children, onMouseEnter, onMouseUp, onMouseLeave,
3829
3469
  });
3830
3470
  Flex.displayName = 'Flex';
3831
3471
 
3832
- 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";
3472
+ var css_248z$1$1 = ".cf-divider {\n display: contents;\n position: relative;\n width: 100%;\n height: 100%;\n}\n\n.cf-divider hr {\n border: none;\n}\n";
3833
3473
  styleInject(css_248z$1$1);
3834
3474
 
3835
- 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";
3475
+ var css_248z$9 = ".cf-columns {\n display: flex;\n gap: 24px;\n grid-template-columns: repeat(12, 1fr);\n flex-direction: column;\n min-height: 0; /* NEW */\n min-width: 0; /* NEW; needed for Firefox */\n}\n\n@media (min-width: 768px) {\n .cf-columns {\n display: grid;\n }\n}\n\n.cf-single-column-wrapper {\n position: relative;\n}\n\n.cf-single-column-wrapper:after {\n content: '';\n display: block;\n position: absolute;\n pointer-events: none;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n overflow-x: clip;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 12px;\n color: var(--exp-builder-gray400);\n z-index: 1;\n}\n\n.cf-single-column-label:after {\n content: 'Column';\n}\n";
3836
3476
  styleInject(css_248z$9);
3837
3477
 
3838
- const ColumnWrapper = forwardRef((props, ref) => {
3839
- return (React.createElement("div", { ref: ref, ...props, style: {
3840
- ...(props.style || {}),
3841
- display: 'grid',
3842
- gridTemplateColumns: 'repeat(12, [col-start] 1fr)',
3843
- } }, props.children));
3844
- });
3845
- ColumnWrapper.displayName = 'ColumnWrapper';
3846
-
3847
3478
  const assemblyStyle = { display: 'contents' };
3848
- // Feel free to do any magic as regards variable definitions for assemblies
3849
- // Or if this isn't necessary by the time we figure that part out, we can bid this part farewell
3850
3479
  const Assembly = (props) => {
3851
- if (props.editorMode) {
3852
- const { node, dragProps, ...editorModeProps } = props;
3853
- return props.renderDropzone(node, {
3854
- ...editorModeProps,
3855
- ['data-test-id']: 'contentful-assembly',
3856
- className: props.className,
3857
- dragProps,
3858
- });
3859
- }
3860
3480
  // Using a display contents so assembly content/children
3861
3481
  // can appear as if they are direct children of the div wrapper's parent
3862
3482
  return React.createElement("div", { "data-test-id": "assembly", ...props, style: assemblyStyle });
@@ -3879,75 +3499,6 @@ const useEntityStore = create((set) => ({
3879
3499
  },
3880
3500
  }));
3881
3501
 
3882
- class DragState {
3883
- constructor() {
3884
- this.isDragStartedOnParent = false;
3885
- this.isDraggingItem = false;
3886
- }
3887
- get isDragging() {
3888
- return this.isDraggingItem;
3889
- }
3890
- get isDraggingOnParent() {
3891
- return this.isDragStartedOnParent;
3892
- }
3893
- updateIsDragging(isDraggingItem) {
3894
- this.isDraggingItem = isDraggingItem;
3895
- }
3896
- updateIsDragStartedOnParent(isDragStartedOnParent) {
3897
- this.isDragStartedOnParent = isDragStartedOnParent;
3898
- }
3899
- resetState() {
3900
- this.isDraggingItem = false;
3901
- this.isDragStartedOnParent = false;
3902
- }
3903
- }
3904
-
3905
- class SimulateDnD extends DragState {
3906
- constructor() {
3907
- super();
3908
- this.draggingElement = null;
3909
- }
3910
- setupDrag() {
3911
- this.updateIsDragStartedOnParent(true);
3912
- }
3913
- startDrag(coordX, coordY) {
3914
- this.draggingElement = document.getElementById(NEW_COMPONENT_ID);
3915
- this.updateIsDragging(true);
3916
- this.simulateMouseEvent(coordX, coordY, 'mousedown');
3917
- }
3918
- updateDrag(coordX, coordY) {
3919
- if (!this.draggingElement) {
3920
- this.draggingElement = document.querySelector(`[${CTFL_DRAGGING_ELEMENT}]`);
3921
- }
3922
- this.simulateMouseEvent(coordX, coordY);
3923
- }
3924
- endDrag(coordX, coordY) {
3925
- this.simulateMouseEvent(coordX, coordY, 'mouseup');
3926
- this.reset();
3927
- }
3928
- reset() {
3929
- this.draggingElement = null;
3930
- this.resetState();
3931
- }
3932
- simulateMouseEvent(coordX, coordY, eventName = 'mousemove') {
3933
- if (!this.draggingElement) {
3934
- return;
3935
- }
3936
- const options = {
3937
- bubbles: true,
3938
- cancelable: true,
3939
- view: window,
3940
- pageX: 0,
3941
- pageY: 0,
3942
- clientX: coordX,
3943
- clientY: coordY,
3944
- };
3945
- const event = new MouseEvent(eventName, options);
3946
- this.draggingElement.dispatchEvent(event);
3947
- }
3948
- }
3949
- var SimulateDnD$1 = new SimulateDnD();
3950
-
3951
3502
  function useEditorSubscriber() {
3952
3503
  const entityStore = useEntityStore((state) => state.entityStore);
3953
3504
  const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
@@ -3961,14 +3512,7 @@ function useEditorSubscriber() {
3961
3512
  const setLocale = useEditorStore((state) => state.setLocale);
3962
3513
  const setUnboundValues = useEditorStore((state) => state.setUnboundValues);
3963
3514
  const setDataSource = useEditorStore((state) => state.setDataSource);
3964
- const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
3965
- const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
3966
3515
  const resetEntityStore = useEntityStore((state) => state.resetEntityStore);
3967
- const setComponentId = useDraggedItemStore((state) => state.setComponentId);
3968
- const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
3969
- const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
3970
- const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
3971
- const setScrollY = useDraggedItemStore((state) => state.setScrollY);
3972
3516
  const reloadApp = () => {
3973
3517
  sendMessage(OUTGOING_EVENTS.CanvasReload, undefined);
3974
3518
  // Wait a moment to ensure that the message was sent
@@ -4069,12 +3613,12 @@ function useEditorSubscriber() {
4069
3613
  }
4070
3614
  const eventData = tryParseMessage(event);
4071
3615
  console.debug(`[experiences-sdk-react::onMessage] Received message [${eventData.eventType}]`, eventData);
4072
- if (eventData.eventType === PostMessageMethods$2.REQUESTED_ENTITIES) {
3616
+ if (eventData.eventType === PostMessageMethods$3.REQUESTED_ENTITIES) {
4073
3617
  // Expected message: This message is handled in the EntityStore to store fetched entities
4074
3618
  return;
4075
3619
  }
4076
3620
  switch (eventData.eventType) {
4077
- case INCOMING_EVENTS.ExperienceUpdated: {
3621
+ case INCOMING_EVENTS$1.ExperienceUpdated: {
4078
3622
  const { tree, locale, changedNode, changedValueType, assemblies } = eventData.payload;
4079
3623
  // Make sure to first store the assemblies before setting the tree and thus triggering a rerender
4080
3624
  if (assemblies) {
@@ -4118,7 +3662,7 @@ function useEditorSubscriber() {
4118
3662
  updateTree(tree);
4119
3663
  break;
4120
3664
  }
4121
- case INCOMING_EVENTS.AssembliesRegistered: {
3665
+ case INCOMING_EVENTS$1.AssembliesRegistered: {
4122
3666
  const { assemblies } = eventData.payload;
4123
3667
  assemblies.forEach((definition) => {
4124
3668
  addComponentRegistration({
@@ -4128,7 +3672,7 @@ function useEditorSubscriber() {
4128
3672
  });
4129
3673
  break;
4130
3674
  }
4131
- case INCOMING_EVENTS.AssembliesAdded: {
3675
+ case INCOMING_EVENTS$1.AssembliesAdded: {
4132
3676
  const { assembly, assemblyDefinition, } = eventData.payload;
4133
3677
  entityStore.updateEntity(assembly);
4134
3678
  // Using a Map here to avoid setting state and rerending all existing assemblies when a new assembly is added
@@ -4145,28 +3689,7 @@ function useEditorSubscriber() {
4145
3689
  }
4146
3690
  break;
4147
3691
  }
4148
- case INCOMING_EVENTS.CanvasResized: {
4149
- const { selectedNodeId } = eventData.payload;
4150
- if (selectedNodeId) {
4151
- sendSelectedComponentCoordinates(selectedNodeId);
4152
- }
4153
- break;
4154
- }
4155
- case INCOMING_EVENTS.HoverComponent: {
4156
- const { hoveredNodeId } = eventData.payload;
4157
- setHoveredComponentId(hoveredNodeId);
4158
- break;
4159
- }
4160
- case INCOMING_EVENTS.ComponentDraggingChanged: {
4161
- const { isDragging } = eventData.payload;
4162
- if (!isDragging) {
4163
- setComponentId('');
4164
- setDraggingOnCanvas(false);
4165
- SimulateDnD$1.reset();
4166
- }
4167
- break;
4168
- }
4169
- case INCOMING_EVENTS.UpdatedEntity: {
3692
+ case INCOMING_EVENTS$1.UpdatedEntity: {
4170
3693
  const { entity: updatedEntity, shouldRerender } = eventData.payload;
4171
3694
  if (updatedEntity) {
4172
3695
  const storedEntity = entityStore.entities.find((entity) => entity.sys.id === updatedEntity.sys.id);
@@ -4179,52 +3702,7 @@ function useEditorSubscriber() {
4179
3702
  }
4180
3703
  break;
4181
3704
  }
4182
- case INCOMING_EVENTS.RequestEditorMode: {
4183
- break;
4184
- }
4185
- case INCOMING_EVENTS.ComponentDragCanceled: {
4186
- if (SimulateDnD$1.isDragging) {
4187
- //simulate a mouseup event to cancel the drag
4188
- SimulateDnD$1.endDrag(0, 0);
4189
- }
4190
- break;
4191
- }
4192
- case INCOMING_EVENTS.ComponentDragStarted: {
4193
- const { id, isAssembly } = eventData.payload;
4194
- SimulateDnD$1.setupDrag();
4195
- setComponentId(`${id}:${isAssembly}` || '');
4196
- setDraggingOnCanvas(true);
4197
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
4198
- nodeId: '',
4199
- });
4200
- break;
4201
- }
4202
- case INCOMING_EVENTS.ComponentDragEnded: {
4203
- SimulateDnD$1.reset();
4204
- setComponentId('');
4205
- setDraggingOnCanvas(false);
4206
- break;
4207
- }
4208
- case INCOMING_EVENTS.SelectComponent: {
4209
- const { selectedNodeId: nodeId } = eventData.payload;
4210
- setSelectedNodeId(nodeId);
4211
- sendSelectedComponentCoordinates(nodeId);
4212
- break;
4213
- }
4214
- case INCOMING_EVENTS.MouseMove: {
4215
- const { mouseX, mouseY } = eventData.payload;
4216
- setMousePosition(mouseX, mouseY);
4217
- if (SimulateDnD$1.isDraggingOnParent && !SimulateDnD$1.isDragging) {
4218
- SimulateDnD$1.startDrag(mouseX, mouseY);
4219
- }
4220
- else {
4221
- SimulateDnD$1.updateDrag(mouseX, mouseY);
4222
- }
4223
- break;
4224
- }
4225
- case INCOMING_EVENTS.ComponentMoveEnded: {
4226
- const { mouseX, mouseY } = eventData.payload;
4227
- SimulateDnD$1.endDrag(mouseX, mouseY);
3705
+ case INCOMING_EVENTS$1.RequestEditorMode: {
4228
3706
  break;
4229
3707
  }
4230
3708
  default:
@@ -4237,340 +3715,101 @@ function useEditorSubscriber() {
4237
3715
  };
4238
3716
  }, [
4239
3717
  entityStore,
4240
- setComponentId,
4241
- setDraggingOnCanvas,
4242
3718
  setDataSource,
4243
3719
  setLocale,
4244
- setSelectedNodeId,
4245
3720
  dataSource,
4246
3721
  areEntitiesFetched,
4247
3722
  fetchMissingEntities,
4248
3723
  setUnboundValues,
4249
3724
  unboundValues,
4250
- updateTree,
4251
- updateNodesByUpdatedEntity,
4252
- setMousePosition,
4253
- resetEntityStore,
4254
- setHoveredComponentId,
4255
- ]);
4256
- /*
4257
- * Handles on scroll business
4258
- */
4259
- useEffect(() => {
4260
- let timeoutId = 0;
4261
- let isScrolling = false;
4262
- const onScroll = () => {
4263
- setScrollY(window.scrollY);
4264
- if (isScrolling === false) {
4265
- sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.Start);
4266
- }
4267
- sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.IsScrolling);
4268
- isScrolling = true;
4269
- clearTimeout(timeoutId);
4270
- timeoutId = window.setTimeout(() => {
4271
- if (isScrolling === false) {
4272
- return;
4273
- }
4274
- isScrolling = false;
4275
- sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.End);
4276
- /**
4277
- * On scroll end, send new co-ordinates of selected node
4278
- */
4279
- if (selectedNodeId) {
4280
- sendSelectedComponentCoordinates(selectedNodeId);
4281
- }
4282
- }, 150);
4283
- };
4284
- window.addEventListener('scroll', onScroll, { capture: true, passive: true });
4285
- return () => {
4286
- window.removeEventListener('scroll', onScroll, { capture: true });
4287
- clearTimeout(timeoutId);
4288
- };
4289
- }, [selectedNodeId, setScrollY]);
4290
- }
4291
-
4292
- const onComponentMoved = (options) => {
4293
- sendMessage(OUTGOING_EVENTS.ComponentMoved, options);
4294
- };
4295
-
4296
- const generateId = (type) => `${type}-${v4()}`;
4297
-
4298
- const createTreeNode = ({ blockId, parentId, slotId }) => {
4299
- const node = {
4300
- type: 'block',
4301
- data: {
4302
- id: generateId(blockId),
4303
- blockId,
4304
- slotId,
4305
- props: {},
4306
- dataSource: {},
4307
- breakpoints: [],
4308
- unboundValues: {},
4309
- },
4310
- parentId,
4311
- children: [],
4312
- };
4313
- return node;
4314
- };
4315
-
4316
- const onComponentDropped = ({ node, index, parentBlockId, parentType, parentId, }) => {
4317
- sendMessage(OUTGOING_EVENTS.ComponentDropped, {
4318
- node,
4319
- index: index ?? node.children.length,
4320
- parentNode: {
4321
- type: parentType,
4322
- data: {
4323
- blockId: parentBlockId,
4324
- id: parentId,
4325
- },
4326
- },
4327
- });
4328
- };
4329
-
4330
- const onDrop = ({ destinationIndex, componentType, destinationZoneId, data, slotId, }) => {
4331
- const parentId = destinationZoneId;
4332
- const parentNode = getItem({ id: parentId }, data);
4333
- const parentIsRoot = parentId === ROOT_ID;
4334
- const emptyComponentData = {
4335
- type: 'block',
4336
- parentId,
4337
- children: [],
4338
- data: {
4339
- blockId: componentType,
4340
- id: generateId(componentType),
4341
- slotId,
4342
- breakpoints: [],
4343
- dataSource: {},
4344
- props: {},
4345
- unboundValues: {},
4346
- },
4347
- };
4348
- onComponentDropped({
4349
- node: emptyComponentData,
4350
- index: destinationIndex,
4351
- parentType: parentIsRoot ? 'root' : parentNode?.type,
4352
- parentBlockId: parentNode?.data.blockId,
4353
- parentId: parentIsRoot ? 'root' : parentId,
4354
- });
4355
- };
3725
+ updateTree,
3726
+ updateNodesByUpdatedEntity,
3727
+ resetEntityStore,
3728
+ ]);
3729
+ }
4356
3730
 
4357
- /**
4358
- * Parses a droppable zone ID into a node ID and slot ID.
4359
- *
4360
- * The slot ID is optional and only present if the component implements multiple drop zones.
4361
- *
4362
- * @param zoneId - Expected formats are `nodeId` or `nodeId|slotId`.
4363
- */
4364
- const parseZoneId = (zoneId) => {
4365
- const [nodeId, slotId] = zoneId.includes('|') ? zoneId.split('|') : [zoneId, undefined];
4366
- return { nodeId, slotId };
3731
+ const CircularDependencyErrorPlaceholder = ({ node, wrappingPatternIds, }) => {
3732
+ const entityStore = useEntityStore((state) => state.entityStore);
3733
+ return (React.createElement("div", { "data-cf-node-id": node.data.id, "data-cf-node-block-id": node.data.blockId, "data-cf-node-block-type": node.type, "data-cf-node-error": "circular-pattern-dependency", style: {
3734
+ border: '1px solid red',
3735
+ background: 'rgba(255, 0, 0, 0.1)',
3736
+ padding: '1rem 1rem 0 1rem',
3737
+ width: '100%',
3738
+ height: '100%',
3739
+ } },
3740
+ "Circular usage of patterns detected:",
3741
+ React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
3742
+ const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
3743
+ const entry = entityStore.getEntityFromLink(entryLink);
3744
+ const entryTitle = entry?.fields?.title;
3745
+ const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
3746
+ return React.createElement("li", { key: patternId }, text);
3747
+ }))));
4367
3748
  };
4368
3749
 
4369
- function useCanvasInteractions() {
4370
- const tree = useTreeStore((state) => state.tree);
4371
- const reorderChildren = useTreeStore((state) => state.reorderChildren);
4372
- const reparentChild = useTreeStore((state) => state.reparentChild);
4373
- const addChild = useTreeStore((state) => state.addChild);
4374
- const onAddComponent = (droppedItem) => {
4375
- const { destination, draggableId } = droppedItem;
4376
- if (!destination) {
4377
- return;
4378
- }
4379
- /**
4380
- * We only have the draggableId as information about the new component being dropped.
4381
- * So we need to split it to get the blockId and the isAssembly flag.
4382
- */
4383
- const [blockId, isAssembly] = draggableId.split(':');
4384
- const { nodeId: parentId, slotId } = parseZoneId(destination.droppableId);
4385
- const droppingOnRoot = parentId === ROOT_ID;
4386
- const isValidRootComponent = blockId === CONTENTFUL_COMPONENTS$1.container.id;
4387
- let node = createTreeNode({ blockId, parentId, slotId });
4388
- if (droppingOnRoot && !isValidRootComponent) {
4389
- const wrappingContainer = createTreeNode({
4390
- blockId: CONTENTFUL_COMPONENTS$1.container.id,
4391
- parentId,
4392
- });
4393
- const childNode = createTreeNode({
4394
- blockId,
4395
- parentId: wrappingContainer.data.id,
4396
- });
4397
- node = wrappingContainer;
4398
- node.children = [childNode];
4399
- }
4400
- /**
4401
- * isAssembly comes from a string ID so we need to check if it's 'true' or 'false'
4402
- * in string format.
4403
- */
4404
- if (isAssembly === 'false') {
4405
- addChild(destination.index, parentId, node);
4406
- }
4407
- onDrop({
4408
- data: tree,
4409
- componentType: blockId,
4410
- destinationIndex: destination.index,
4411
- destinationZoneId: parentId,
4412
- slotId,
4413
- });
4414
- };
4415
- const onMoveComponent = (droppedItem) => {
4416
- const { destination, source, draggableId } = droppedItem;
4417
- if (!destination || !source) {
4418
- return;
4419
- }
4420
- if (destination.droppableId === source.droppableId) {
4421
- reorderChildren(destination.index, destination.droppableId, source.index);
4422
- }
4423
- if (destination.droppableId !== source.droppableId) {
4424
- reparentChild(destination.index, destination.droppableId, source.index, source.droppableId);
3750
+ class ImportedComponentError extends Error {
3751
+ constructor(message) {
3752
+ super(message);
3753
+ this.name = 'ImportedComponentError';
3754
+ }
3755
+ }
3756
+ class ExperienceSDKError extends Error {
3757
+ constructor(message) {
3758
+ super(message);
3759
+ this.name = 'ExperienceSDKError';
3760
+ }
3761
+ }
3762
+ class ImportedComponentErrorBoundary extends React.Component {
3763
+ componentDidCatch(error, _errorInfo) {
3764
+ if (error.name === 'ImportedComponentError' || error.name === 'ExperienceSDKError') {
3765
+ // This error was already handled by a nested error boundary and should be passed upwards
3766
+ // We have to do this as we wrap every component on every layer with this error boundary and
3767
+ // thus an error deep in the tree bubbles through many layers of error boundaries.
3768
+ throw error;
4425
3769
  }
4426
- onComponentMoved({
4427
- nodeId: draggableId,
4428
- destinationIndex: destination.index,
4429
- destinationParentId: destination.droppableId,
4430
- sourceIndex: source.index,
4431
- sourceParentId: source.droppableId,
4432
- });
4433
- };
4434
- return { onAddComponent, onMoveComponent };
3770
+ // Differentiate between custom and SDK-provided components for error tracking
3771
+ const ErrorClass = isContentfulComponent(this.props.componentId)
3772
+ ? ExperienceSDKError
3773
+ : ImportedComponentError;
3774
+ const err = new ErrorClass(error.message);
3775
+ err.stack = error.stack;
3776
+ throw err;
3777
+ }
3778
+ render() {
3779
+ return this.props.children;
3780
+ }
4435
3781
  }
4436
3782
 
4437
- const TestDNDContainer = ({ onDragEnd, onBeforeDragStart, onDragStart, onDragUpdate, children, }) => {
4438
- const handleDragStart = (event) => {
4439
- const draggedItem = event.nativeEvent;
4440
- const start = {
4441
- mode: draggedItem.mode,
4442
- draggableId: draggedItem.draggableId,
4443
- type: draggedItem.type,
4444
- source: draggedItem.source,
4445
- };
4446
- onBeforeDragStart(start);
4447
- onDragStart(start, {});
4448
- };
4449
- const handleDrag = (event) => {
4450
- const draggedItem = event.nativeEvent;
4451
- const update = {
4452
- mode: draggedItem.mode,
4453
- draggableId: draggedItem.draggableId,
4454
- type: draggedItem.type,
4455
- source: draggedItem.source,
4456
- destination: draggedItem.destination,
4457
- combine: draggedItem.combine,
4458
- };
4459
- onDragUpdate(update, {});
4460
- };
4461
- const handleDragEnd = (event) => {
4462
- const draggedItem = event.nativeEvent;
4463
- const result = {
4464
- mode: draggedItem.mode,
4465
- draggableId: draggedItem.draggableId,
4466
- type: draggedItem.type,
4467
- source: draggedItem.source,
4468
- destination: draggedItem.destination,
4469
- combine: draggedItem.combine,
4470
- reason: draggedItem.reason,
4471
- };
4472
- onDragEnd(result, {});
4473
- };
4474
- return (React.createElement("div", { "data-test-id": "dnd-context-substitute", onDragStart: handleDragStart, onDrag: handleDrag, onDragEnd: handleDragEnd }, children));
3783
+ const MissingComponentPlaceholder = ({ blockId }) => {
3784
+ return (React.createElement("div", { style: {
3785
+ border: '1px solid red',
3786
+ width: '100%',
3787
+ height: '100%',
3788
+ } },
3789
+ "Missing component '",
3790
+ blockId,
3791
+ "'"));
4475
3792
  };
4476
3793
 
4477
- const DNDProvider = ({ children }) => {
4478
- const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
4479
- const draggedItem = useDraggedItemStore((state) => state.draggedItem);
4480
- const setOnBeforeCaptureId = useDraggedItemStore((state) => state.setOnBeforeCaptureId);
4481
- const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
4482
- const updateItem = useDraggedItemStore((state) => state.updateItem);
4483
- const { onAddComponent, onMoveComponent } = useCanvasInteractions();
4484
- const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
4485
- const prevSelectedNodeId = useRef(null);
4486
- const isTestRun = typeof window !== 'undefined' && Object.prototype.hasOwnProperty.call(window, 'Cypress');
4487
- const beforeDragStart = ({ source }) => {
4488
- prevSelectedNodeId.current = selectedNodeId;
4489
- // Unselect the current node when dragging and remove the outline
4490
- setSelectedNodeId('');
4491
- // Set dragging state here to make sure that DnD capture phase has completed
4492
- setDraggingOnCanvas(true);
4493
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
4494
- nodeId: '',
4495
- });
4496
- if (source.droppableId !== COMPONENT_LIST_ID) {
4497
- sendMessage(OUTGOING_EVENTS.ComponentMoveStarted, undefined);
4498
- }
4499
- };
4500
- const beforeCapture = ({ draggableId }) => {
4501
- setOnBeforeCaptureId(draggableId);
4502
- };
4503
- const dragStart = (start) => {
4504
- updateItem(start);
4505
- };
4506
- const dragUpdate = (update) => {
4507
- updateItem(update);
4508
- };
4509
- const dragEnd = (dropResult) => {
4510
- setDraggingOnCanvas(false);
4511
- setOnBeforeCaptureId('');
4512
- updateItem();
4513
- SimulateDnD$1.reset();
4514
- // If the component is being dropped onto itself, do nothing
4515
- // This can happen from an apparent race condition where the hovering zone gets set
4516
- // to the component after its dropped.
4517
- if (dropResult.destination?.droppableId === dropResult.draggableId) {
4518
- return;
4519
- }
4520
- if (!dropResult.destination) {
4521
- if (!draggedItem?.destination) {
4522
- // User cancel drag
4523
- sendMessage(OUTGOING_EVENTS.ComponentDragCanceled, undefined);
4524
- //select the previously selected node if drag was canceled
4525
- if (prevSelectedNodeId.current) {
4526
- setSelectedNodeId(prevSelectedNodeId.current);
4527
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
4528
- nodeId: prevSelectedNodeId.current,
4529
- });
4530
- prevSelectedNodeId.current = null;
4531
- }
4532
- return;
4533
- }
4534
- // Use the destination from the draggedItem (when clicking the canvas)
4535
- dropResult.destination = draggedItem.destination;
4536
- }
4537
- // New component added to canvas
4538
- if (dropResult.source.droppableId.startsWith('component-list')) {
4539
- onAddComponent(dropResult);
4540
- }
4541
- else {
4542
- onMoveComponent(dropResult);
4543
- }
4544
- // If a node was previously selected prior to dragging, re-select it
4545
- setSelectedNodeId(dropResult.draggableId);
4546
- sendMessage(OUTGOING_EVENTS.ComponentMoveEnded, undefined);
4547
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
4548
- nodeId: dropResult.draggableId,
4549
- });
4550
- };
4551
- 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)));
4552
- };
3794
+ var css_248z$1 = ".EditorBlock-module_emptySlot__za-Bi {\n min-height: 80px;\n min-width: 80px;\n}\n";
3795
+ var styles$1 = {"emptySlot":"EditorBlock-module_emptySlot__za-Bi"};
3796
+ styleInject(css_248z$1);
4553
3797
 
4554
- /**
4555
- * This hook gets the element co-ordinates of a specified element in the DOM
4556
- * and sends the DOM Rect to the client app
4557
- */
4558
- const useSelectedInstanceCoordinates = ({ node }) => {
4559
- const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
4560
- useEffect(() => {
4561
- if (selectedNodeId !== node.data.id) {
4562
- return;
3798
+ const useComponentRegistration = (node) => {
3799
+ return useMemo(() => {
3800
+ let registration = componentRegistry.get(node.data.blockId);
3801
+ if (node.type === ASSEMBLY_NODE_TYPE && !registration) {
3802
+ registration = createAssemblyRegistration({
3803
+ definitionId: node.data.blockId,
3804
+ component: Assembly,
3805
+ });
4563
3806
  }
4564
- // Allows the drop animation to finish before
4565
- // calculating the components coordinates
4566
- setTimeout(() => {
4567
- sendSelectedComponentCoordinates(node.data.id);
4568
- }, 10);
4569
- }, [node, selectedNodeId]);
4570
- const selectedElement = node.data.id
4571
- ? document.querySelector(`[data-cf-node-id="${selectedNodeId}"]`)
4572
- : undefined;
4573
- return selectedElement ? getElementCoordinates(selectedElement) : null;
3807
+ if (!registration) {
3808
+ 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.`);
3809
+ return undefined;
3810
+ }
3811
+ return registration;
3812
+ }, [node]);
4574
3813
  };
4575
3814
 
4576
3815
  /**
@@ -4618,15 +3857,13 @@ const getUnboundValues = ({ key, fallback, unboundValues, }) => {
4618
3857
  return get$1(unboundValues, lodashPath, fallback);
4619
3858
  };
4620
3859
 
4621
- const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, renderDropzone, definition, options, userIsDragging, requiresDragWrapper, }) => {
3860
+ const useComponentProps = ({ node, resolveDesignValue, definition, options, }) => {
4622
3861
  const unboundValues = useEditorStore((state) => state.unboundValues);
4623
3862
  const hyperlinkPattern = useEditorStore((state) => state.hyperLinkPattern);
4624
3863
  const locale = useEditorStore((state) => state.locale);
4625
3864
  const dataSource = useEditorStore((state) => state.dataSource);
4626
3865
  const entityStore = useEntityStore((state) => state.entityStore);
4627
- const draggingId = useDraggedItemStore((state) => state.onBeforeCaptureId);
4628
- const nodeRect = useDraggedItemStore((state) => state.domRect);
4629
- const isEmptyZone = !node.children.length;
3866
+ const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
4630
3867
  const props = useMemo(() => {
4631
3868
  const propsBase = {
4632
3869
  cfSsrClassName: node.data.props.cfSsrClassName
@@ -4704,1145 +3941,138 @@ const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, rende
4704
3941
  };
4705
3942
  }
4706
3943
  else if (variableMapping.type === 'ComponentValue') {
4707
- // We are rendering a pattern (assembly) entry. Content properties cannot be edited in this,
4708
- // so we always render the default value
4709
- return {
4710
- ...acc,
4711
- // This can either a design (style) or a content variable
4712
- [variableName]: variableDefinition.defaultValue,
4713
- };
4714
- }
4715
- else {
4716
- return { ...acc };
4717
- }
4718
- }, {});
4719
- const slotProps = {};
4720
- if (definition.slots) {
4721
- for (const slotId in definition.slots) {
4722
- slotProps[slotId] = renderDropzone(node, {
4723
- zoneId: [node.data.id, slotId].join('|'),
4724
- });
4725
- }
4726
- }
4727
- return {
4728
- ...propsBase,
4729
- ...extractedProps,
4730
- ...slotProps,
4731
- };
4732
- }, [
4733
- hyperlinkPattern,
4734
- node,
4735
- locale,
4736
- definition,
4737
- resolveDesignValue,
4738
- dataSource,
4739
- areEntitiesFetched,
4740
- unboundValues,
4741
- entityStore,
4742
- renderDropzone,
4743
- ]);
4744
- const cfStyles = useMemo(() => buildCfStyles(props), [props]);
4745
- const cfVisibility = props['cfVisibility'];
4746
- const isAssemblyBlock = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
4747
- const isSingleColumn = node?.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id;
4748
- const isStructureComponent = isContentfulStructureComponent(node?.data.blockId);
4749
- const isPatternNode = node.type === ASSEMBLY_NODE_TYPE;
4750
- const { overrideStyles, wrapperStyles } = useMemo(() => {
4751
- // Move size styles to the wrapping div and override the component styles
4752
- const overrideStyles = {};
4753
- const wrapperStyles = { width: options?.wrapContainerWidth };
4754
- if (requiresDragWrapper) {
4755
- // when element is marked by user as not-visible, on that element the node `display: none !important`
4756
- // will be set and it will disappear. However, when such a node has a wrapper div, the wrapper
4757
- // should not have any css properties (at least not ones which force size), as such div should
4758
- // simply be a zero height wrapper around element with `display: none !important`.
4759
- // Hence we guard all wrapperStyles with `cfVisibility` check.
4760
- if (cfVisibility && cfStyles.width)
4761
- wrapperStyles.width = cfStyles.width;
4762
- if (cfVisibility && cfStyles.height)
4763
- wrapperStyles.height = cfStyles.height;
4764
- if (cfVisibility && cfStyles.maxWidth)
4765
- wrapperStyles.maxWidth = cfStyles.maxWidth;
4766
- if (cfVisibility && cfStyles.margin)
4767
- wrapperStyles.margin = cfStyles.margin;
4768
- }
4769
- // Override component styles to fill the wrapper
4770
- if (wrapperStyles.width)
4771
- overrideStyles.width = '100%';
4772
- if (wrapperStyles.height)
4773
- overrideStyles.height = '100%';
4774
- if (wrapperStyles.margin)
4775
- overrideStyles.margin = '0';
4776
- if (wrapperStyles.maxWidth)
4777
- overrideStyles.maxWidth = 'none';
4778
- // Prevent the dragging element from changing sizes when it has a percentage width or height
4779
- if (draggingId === node.data.id && nodeRect) {
4780
- if (requiresDragWrapper) {
4781
- if (isPercentValue(cfStyles.width))
4782
- wrapperStyles.maxWidth = nodeRect.width;
4783
- if (isPercentValue(cfStyles.height))
4784
- wrapperStyles.maxHeight = nodeRect.height;
4785
- }
4786
- else {
4787
- if (isPercentValue(cfStyles.width))
4788
- overrideStyles.maxWidth = nodeRect.width;
4789
- if (isPercentValue(cfStyles.height))
4790
- overrideStyles.maxHeight = nodeRect.height;
4791
- }
4792
- }
4793
- return { overrideStyles, wrapperStyles };
4794
- }, [
4795
- cfStyles,
4796
- options?.wrapContainerWidth,
4797
- requiresDragWrapper,
4798
- node.data.id,
4799
- draggingId,
4800
- nodeRect,
4801
- cfVisibility,
4802
- ]);
4803
- // Styles that will be applied to the component element
4804
- // This has to be memoized to avoid recreating the styles in useEditorModeClassName on every render
4805
- const componentStyles = useMemo(() => ({
4806
- ...cfStyles,
4807
- ...overrideStyles,
4808
- ...(isEmptyZone &&
4809
- isStructureWithRelativeHeight(node?.data.blockId, cfStyles.height) && {
4810
- minHeight: EMPTY_CONTAINER_HEIGHT,
4811
- }),
4812
- ...(userIsDragging &&
4813
- isStructureComponent &&
4814
- !isSingleColumn &&
4815
- !isAssemblyBlock && {
4816
- padding: addExtraDropzonePadding(cfStyles.padding?.toString() || '0 0 0 0'),
4817
- }),
4818
- }), [
4819
- cfStyles,
4820
- isAssemblyBlock,
4821
- isEmptyZone,
4822
- isSingleColumn,
4823
- isStructureComponent,
4824
- node?.data.blockId,
4825
- overrideStyles,
4826
- userIsDragging,
4827
- ]);
4828
- const componentClass = useEditorModeClassName({
4829
- styles: componentStyles,
4830
- nodeId: node.data.id,
4831
- });
4832
- const sharedProps = {
4833
- 'data-cf-node-id': node.data.id,
4834
- 'data-cf-node-block-id': node.data.blockId,
4835
- 'data-cf-node-block-type': node.type,
4836
- className: props.cfSsrClassName ?? componentClass,
4837
- ...(definition?.children ? { children: renderDropzone(node) } : {}),
4838
- };
4839
- const customComponentProps = {
4840
- ...sharedProps,
4841
- // Allows custom components to render differently in the editor. This needs to be activated
4842
- // through options as the component has to be aware of this prop to not cause any React warnings.
4843
- ...(options?.enableCustomEditorView ? { isInExpEditorMode: true } : {}),
4844
- ...sanitizeNodeProps(props),
4845
- };
4846
- const structuralOrPatternComponentProps = {
4847
- ...sharedProps,
4848
- editorMode: true,
4849
- node,
4850
- renderDropzone,
4851
- };
4852
- return {
4853
- componentProps: isStructureComponent || isPatternNode
4854
- ? structuralOrPatternComponentProps
4855
- : customComponentProps,
4856
- componentStyles,
4857
- wrapperStyles,
4858
- };
4859
- };
4860
- const addExtraDropzonePadding = (padding) => padding
4861
- .split(' ')
4862
- .map((value) => parseFloat(value) === 0 ? `${DRAG_PADDING}px` : `calc(${value} + ${DRAG_PADDING}px)`)
4863
- .join(' ');
4864
- const isPercentValue = (value) => typeof value === 'string' && value.endsWith('%');
4865
-
4866
- class ImportedComponentError extends Error {
4867
- constructor(message) {
4868
- super(message);
4869
- this.name = 'ImportedComponentError';
4870
- }
4871
- }
4872
- class ExperienceSDKError extends Error {
4873
- constructor(message) {
4874
- super(message);
4875
- this.name = 'ExperienceSDKError';
4876
- }
4877
- }
4878
- class ImportedComponentErrorBoundary extends React.Component {
4879
- componentDidCatch(error, _errorInfo) {
4880
- if (error.name === 'ImportedComponentError' || error.name === 'ExperienceSDKError') {
4881
- // This error was already handled by a nested error boundary and should be passed upwards
4882
- // We have to do this as we wrap every component on every layer with this error boundary and
4883
- // thus an error deep in the tree bubbles through many layers of error boundaries.
4884
- throw error;
4885
- }
4886
- // Differentiate between custom and SDK-provided components for error tracking
4887
- const ErrorClass = isContentfulComponent(this.props.componentId)
4888
- ? ExperienceSDKError
4889
- : ImportedComponentError;
4890
- const err = new ErrorClass(error.message);
4891
- err.stack = error.stack;
4892
- throw err;
4893
- }
4894
- render() {
4895
- return this.props.children;
4896
- }
4897
- }
4898
-
4899
- const MissingComponentPlaceholder = ({ blockId }) => {
4900
- return (React.createElement("div", { style: {
4901
- border: '1px solid red',
4902
- width: '100%',
4903
- height: '100%',
4904
- } },
4905
- "Missing component '",
4906
- blockId,
4907
- "'"));
4908
- };
4909
-
4910
- const CircularDependencyErrorPlaceholder = forwardRef(({ wrappingPatternIds, ...props }, ref) => {
4911
- const entityStore = useEntityStore((state) => state.entityStore);
4912
- return (React.createElement("div", { ...props,
4913
- // Pass through ref to avoid DND errors being logged
4914
- ref: ref, "data-cf-node-error": "circular-pattern-dependency", style: {
4915
- border: '1px solid red',
4916
- background: 'rgba(255, 0, 0, 0.1)',
4917
- padding: '1rem 1rem 0 1rem',
4918
- width: '100%',
4919
- height: '100%',
4920
- } },
4921
- "Circular usage of patterns detected:",
4922
- React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
4923
- const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
4924
- const entry = entityStore.getEntityFromLink(entryLink);
4925
- const entryTitle = entry?.fields?.title;
4926
- const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
4927
- return React.createElement("li", { key: patternId }, text);
4928
- }))));
4929
- });
4930
- CircularDependencyErrorPlaceholder.displayName = 'CircularDependencyErrorPlaceholder';
4931
-
4932
- const useComponent = ({ node, resolveDesignValue, renderDropzone, userIsDragging, wrappingPatternIds, }) => {
4933
- const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
4934
- const tree = useTreeStore((state) => state.tree);
4935
- const componentRegistration = useMemo(() => {
4936
- let registration = componentRegistry.get(node.data.blockId);
4937
- if (node.type === ASSEMBLY_NODE_TYPE && !registration) {
4938
- registration = createAssemblyRegistration({
4939
- definitionId: node.data.blockId,
4940
- component: Assembly,
4941
- });
4942
- }
4943
- if (!registration) {
4944
- 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.`);
4945
- return undefined;
4946
- }
4947
- return registration;
4948
- }, [node]);
4949
- const componentId = node.data.id;
4950
- const isPatternNode = node.type === ASSEMBLY_NODE_TYPE;
4951
- const isPatternComponent = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
4952
- const parentComponentNode = getItem({ id: node.parentId }, tree);
4953
- const isNestedPattern = isPatternNode &&
4954
- [ASSEMBLY_BLOCK_NODE_TYPE, ASSEMBLY_NODE_TYPE].includes(parentComponentNode?.type ?? '');
4955
- const isStructureComponent = isContentfulStructureComponent(node.data.blockId);
4956
- const requiresDragWrapper = !isPatternNode && !isStructureComponent && !componentRegistration?.options?.wrapComponent;
4957
- const { componentProps, wrapperStyles } = useComponentProps({
3944
+ // We are rendering a pattern (assembly) entry. Content properties cannot be edited in this,
3945
+ // so we always render the default value
3946
+ return {
3947
+ ...acc,
3948
+ // This can either a design (style) or a content variable
3949
+ [variableName]: variableDefinition.defaultValue,
3950
+ };
3951
+ }
3952
+ else {
3953
+ return { ...acc };
3954
+ }
3955
+ }, {});
3956
+ return {
3957
+ ...propsBase,
3958
+ ...extractedProps,
3959
+ };
3960
+ }, [
3961
+ hyperlinkPattern,
4958
3962
  node,
4959
- areEntitiesFetched,
3963
+ locale,
3964
+ definition,
4960
3965
  resolveDesignValue,
4961
- renderDropzone,
4962
- definition: componentRegistration?.definition,
4963
- options: componentRegistration?.options,
4964
- userIsDragging,
4965
- requiresDragWrapper,
3966
+ dataSource,
3967
+ areEntitiesFetched,
3968
+ unboundValues,
3969
+ entityStore,
3970
+ ]);
3971
+ const cfStyles = useMemo(() => buildCfStyles(props), [props]);
3972
+ // Styles that will be applied to the component element
3973
+ const componentStyles = useMemo(() => ({
3974
+ ...cfStyles,
3975
+ ...(!node.children.length &&
3976
+ isStructureWithRelativeHeight(node.data.blockId, cfStyles.height) && {
3977
+ minHeight: EMPTY_CONTAINER_HEIGHT$1,
3978
+ }),
3979
+ }), [cfStyles, node.children.length, node.data.blockId]);
3980
+ const cfCsrClassName = useEditorModeClassName({
3981
+ styles: componentStyles,
3982
+ nodeId: node.data.id,
4966
3983
  });
4967
- const elementToRender = (props) => {
4968
- const { dragProps = {} } = props || {};
4969
- const { children, innerRef, Tag = 'div', ToolTipAndPlaceholder, style, ...rest } = dragProps;
4970
- const { 'data-cf-node-block-id': dataCfNodeBlockId, 'data-cf-node-block-type': dataCfNodeBlockType, 'data-cf-node-id': dataCfNodeId, } = componentProps;
4971
- const refCallback = (refNode) => {
4972
- if (innerRef && refNode)
4973
- innerRef(refNode);
4974
- };
4975
- if (!componentRegistration) {
4976
- return React.createElement(MissingComponentPlaceholder, { blockId: node.data.blockId });
4977
- }
4978
- if (node.data.blockId && wrappingPatternIds.has(node.data.blockId)) {
4979
- 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 }));
4980
- }
4981
- const element = React.createElement(ImportedComponentErrorBoundary, { componentId: node.data.blockId }, React.createElement(componentRegistration.component, {
4982
- ...componentProps,
4983
- dragProps,
4984
- }));
4985
- if (!requiresDragWrapper) {
4986
- return element;
4987
- }
4988
- 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 },
4989
- ToolTipAndPlaceholder,
4990
- element));
4991
- };
4992
- return {
4993
- node,
4994
- parentComponentNode,
4995
- isAssembly: isPatternNode,
4996
- isPatternNode,
4997
- isPatternComponent,
4998
- isNestedPattern,
4999
- componentId,
5000
- elementToRender,
5001
- definition: componentRegistration?.definition,
5002
- };
5003
- };
5004
-
5005
- const calcOffsetLeft = (parentElement, placeholderWidth, nodeWidth) => {
5006
- if (!parentElement) {
5007
- return 0;
5008
- }
5009
- const alignItems = window.getComputedStyle(parentElement).alignItems;
5010
- if (alignItems === 'center') {
5011
- return -(placeholderWidth - nodeWidth) / 2;
5012
- }
5013
- if (alignItems === 'end') {
5014
- return -placeholderWidth + nodeWidth + 2;
5015
- }
5016
- return 0;
5017
- };
5018
- const calcOffsetTop = (parentElement, placeholderHeight, nodeHeight) => {
5019
- if (!parentElement) {
5020
- return 0;
5021
- }
5022
- const alignItems = window.getComputedStyle(parentElement).alignItems;
5023
- if (alignItems === 'center') {
5024
- return -(placeholderHeight - nodeHeight) / 2;
5025
- }
5026
- if (alignItems === 'end') {
5027
- return -placeholderHeight + nodeHeight + 2;
5028
- }
5029
- return 0;
5030
- };
5031
- const getPaddingOffset = (element) => {
5032
- const paddingLeft = parseFloat(window.getComputedStyle(element).paddingLeft);
5033
- const paddingRight = parseFloat(window.getComputedStyle(element).paddingRight);
5034
- const paddingTop = parseFloat(window.getComputedStyle(element).paddingTop);
5035
- const paddingBottom = parseFloat(window.getComputedStyle(element).paddingBottom);
5036
- const horizontalOffset = paddingLeft + paddingRight;
5037
- const verticalOffset = paddingTop + paddingBottom;
5038
- return [horizontalOffset, verticalOffset];
5039
- };
5040
- /**
5041
- * Calculate the size and position of the dropzone indicator
5042
- * when dragging a new component onto the canvas
5043
- */
5044
- const calcNewComponentStyles = (params) => {
5045
- const { destinationIndex, elementIndex, dropzoneElementId, id, direction, totalIndexes } = params;
5046
- const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
5047
- const isHorizontal = direction === 'horizontal';
5048
- const isRightAlign = isHorizontal && isEnd;
5049
- const isBottomAlign = !isHorizontal && isEnd;
5050
- const dropzone = document.querySelector(`[data-rfd-droppable-id="${dropzoneElementId}"]`);
5051
- const element = document.querySelector(`[data-ctfl-draggable-id="${id}"]`);
5052
- if (!dropzone || !element) {
5053
- return emptyStyles;
5054
- }
5055
- const elementSizes = element.getBoundingClientRect();
5056
- const dropzoneSizes = dropzone.getBoundingClientRect();
5057
- const [horizontalPadding, verticalPadding] = getPaddingOffset(dropzone);
5058
- const width = isHorizontal ? DRAGGABLE_WIDTH : dropzoneSizes.width - horizontalPadding;
5059
- const height = isHorizontal ? dropzoneSizes.height - verticalPadding : DRAGGABLE_HEIGHT;
5060
- const top = isHorizontal
5061
- ? calcOffsetTop(element.parentElement, height, elementSizes.height)
5062
- : -height;
5063
- const left = isHorizontal
5064
- ? -width
5065
- : calcOffsetLeft(element.parentElement, width, elementSizes.width);
5066
- return {
5067
- width,
5068
- height,
5069
- top: !isBottomAlign ? top : 'unset',
5070
- right: isRightAlign ? -width : 'unset',
5071
- bottom: isBottomAlign ? -height : 'unset',
5072
- left: !isRightAlign ? left : 'unset',
5073
- };
5074
- };
5075
- /**
5076
- * Calculate the size and position of the dropzone indicator
5077
- * when moving an existing component on the canvas
5078
- */
5079
- const calcMovementStyles = (params) => {
5080
- const { destinationIndex, sourceIndex, destinationId, sourceId, elementIndex, dropzoneElementId, id, direction, totalIndexes, draggableId, } = params;
5081
- const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
5082
- const isHorizontal = direction === 'horizontal';
5083
- const isSameZone = destinationId === sourceId;
5084
- const isBelowSourceIndex = destinationIndex > sourceIndex;
5085
- const isRightAlign = isHorizontal && (isEnd || (isSameZone && isBelowSourceIndex));
5086
- const isBottomAlign = !isHorizontal && (isEnd || (isSameZone && isBelowSourceIndex));
5087
- const dropzone = document.querySelector(`[data-rfd-droppable-id="${dropzoneElementId}"]`);
5088
- const draggable = document.querySelector(`[data-rfd-draggable-id="${draggableId}"]`);
5089
- const element = document.querySelector(`[data-ctfl-draggable-id="${id}"]`);
5090
- if (!dropzone || !element || !draggable) {
5091
- return emptyStyles;
5092
- }
5093
- const elementSizes = element.getBoundingClientRect();
5094
- const dropzoneSizes = dropzone.getBoundingClientRect();
5095
- const draggableSizes = draggable.getBoundingClientRect();
5096
- const [horizontalPadding, verticalPadding] = getPaddingOffset(dropzone);
5097
- const width = isHorizontal ? draggableSizes.width : dropzoneSizes.width - horizontalPadding;
5098
- const height = isHorizontal ? dropzoneSizes.height - verticalPadding : draggableSizes.height;
5099
- const top = isHorizontal
5100
- ? calcOffsetTop(element.parentElement, height, elementSizes.height)
5101
- : -height;
5102
- const left = isHorizontal
5103
- ? -width
5104
- : calcOffsetLeft(element.parentElement, width, elementSizes.width);
5105
- return {
5106
- width,
5107
- height,
5108
- top: !isBottomAlign ? top : 'unset',
5109
- right: isRightAlign ? -width : 'unset',
5110
- bottom: isBottomAlign ? -height : 'unset',
5111
- left: !isRightAlign ? left : 'unset',
5112
- };
5113
- };
5114
- const emptyStyles = { width: 0, height: 0 };
5115
- const calcPlaceholderStyles = (params) => {
5116
- const { isDraggingOver, sourceId } = params;
5117
- if (!isDraggingOver) {
5118
- return emptyStyles;
5119
- }
5120
- if (sourceId === COMPONENT_LIST_ID) {
5121
- return calcNewComponentStyles(params);
5122
- }
5123
- return calcMovementStyles(params);
5124
- };
5125
- const Placeholder = (props) => {
5126
- const sourceIndex = useDraggedItemStore((state) => state.draggedItem?.source.index) ?? -1;
5127
- const draggableId = useDraggedItemStore((state) => state.draggedItem?.draggableId) ?? '';
5128
- const sourceId = useDraggedItemStore((state) => state.draggedItem?.source.droppableId) ?? '';
5129
- const destinationIndex = useDraggedItemStore((state) => state.draggedItem?.destination?.index) ?? -1;
5130
- const destinationId = useDraggedItemStore((state) => state.draggedItem?.destination?.droppableId) ?? '';
5131
- const { elementIndex, totalIndexes, isDraggingOver } = props;
5132
- const isActive = destinationIndex === elementIndex;
5133
- const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
5134
- const isVisible = isEnd || isActive;
5135
- const isComponentList = destinationId === COMPONENT_LIST_ID;
5136
- return (!isComponentList &&
5137
- isDraggingOver &&
5138
- isVisible && (React.createElement("div", { style: {
5139
- ...calcPlaceholderStyles({
5140
- ...props,
5141
- sourceId,
5142
- sourceIndex,
5143
- destinationId,
5144
- destinationIndex,
5145
- draggableId,
5146
- }),
5147
- backgroundColor: 'rgba(var(--exp-builder-blue300-rgb), 0.5)',
5148
- position: 'absolute',
5149
- pointerEvents: 'none',
5150
- } })));
5151
- };
5152
-
5153
- var css_248z$2 = ".styles-module_hitbox__i3wKV {\n position: fixed;\n pointer-events: all;\n}\n";
5154
- var styles$2 = {"hitbox":"styles-module_hitbox__i3wKV"};
5155
- styleInject(css_248z$2);
5156
-
5157
- const useZoneStore = create()((set) => ({
5158
- zones: {},
5159
- hoveringZone: '',
5160
- setHoveringZone(zoneId) {
5161
- set({
5162
- hoveringZone: zoneId,
5163
- });
5164
- },
5165
- upsertZone(id, data) {
5166
- set(produce((state) => {
5167
- state.zones[id] = { ...(state.zones[id] || {}), ...data };
5168
- }));
5169
- },
5170
- }));
5171
-
5172
- const { WIDTH, HEIGHT, INITIAL_OFFSET, OFFSET_INCREMENT, MIN_HEIGHT, MIN_DEPTH_HEIGHT, DEEP_ZONE } = HITBOX;
5173
- const calcOffsetDepth = (depth) => {
5174
- return INITIAL_OFFSET - OFFSET_INCREMENT * depth;
5175
- };
5176
- const getHitboxStyles = ({ direction, zoneDepth, domRect, scrollY, offsetRect, }) => {
5177
- if (!domRect) {
5178
- return {
5179
- display: 'none',
3984
+ const componentProps = useMemo(() => {
3985
+ const sharedProps = {
3986
+ 'data-cf-node-id': node.data.id,
3987
+ 'data-cf-node-block-id': node.data.blockId,
3988
+ 'data-cf-node-block-type': node.type,
3989
+ className: props.cfSsrClassName ?? cfCsrClassName,
5180
3990
  };
5181
- }
5182
- const { width, height, top, left, bottom, right } = domRect;
5183
- const { height: offsetHeight, width: offsetWidth } = offsetRect || { height: 0, width: 0 };
5184
- const MAX_SELF_HEIGHT = DRAGGABLE_HEIGHT * 2;
5185
- const isDeepZone = zoneDepth > DEEP_ZONE;
5186
- const isAboveMaxHeight = height > MAX_SELF_HEIGHT;
5187
- switch (direction) {
5188
- case HitboxDirection.TOP:
5189
- return {
5190
- width,
5191
- height: HEIGHT,
5192
- top: top + offsetHeight - calcOffsetDepth(zoneDepth) - scrollY,
5193
- left,
5194
- zIndex: 100 + zoneDepth,
5195
- };
5196
- case HitboxDirection.BOTTOM:
5197
- return {
5198
- width,
5199
- height: HEIGHT,
5200
- top: bottom + offsetHeight + calcOffsetDepth(zoneDepth) - scrollY,
5201
- left,
5202
- zIndex: 100 + zoneDepth,
5203
- };
5204
- case HitboxDirection.LEFT:
5205
- return {
5206
- width: WIDTH,
5207
- height: height - HEIGHT,
5208
- left: left + offsetWidth - calcOffsetDepth(zoneDepth) - WIDTH / 2,
5209
- top: top + HEIGHT / 2 - scrollY,
5210
- zIndex: 100 + zoneDepth,
5211
- };
5212
- case HitboxDirection.RIGHT:
5213
- return {
5214
- width: WIDTH,
5215
- height: height - HEIGHT,
5216
- left: right + offsetWidth + calcOffsetDepth(zoneDepth) - WIDTH / 2,
5217
- top: top + HEIGHT / 2 - scrollY,
5218
- zIndex: 100 + zoneDepth,
5219
- };
5220
- case HitboxDirection.SELF_VERTICAL: {
5221
- if (isAboveMaxHeight && !isDeepZone) {
5222
- return { display: 'none' };
5223
- }
5224
- const selfHeight = isDeepZone ? MIN_DEPTH_HEIGHT : MIN_HEIGHT;
5225
- return {
5226
- width,
5227
- height: selfHeight,
5228
- left,
5229
- top: top + height / 2 - selfHeight / 2 - scrollY,
5230
- zIndex: 1000 + zoneDepth,
5231
- };
5232
- }
5233
- case HitboxDirection.SELF_HORIZONTAL: {
5234
- if (width > DRAGGABLE_WIDTH) {
5235
- return { display: 'none' };
5236
- }
3991
+ // Only pass `editorMode` and `node` to structure components and assembly root nodes.
3992
+ const isStructureComponent = isContentfulStructureComponent(node.data.blockId);
3993
+ if (isStructureComponent) {
5237
3994
  return {
5238
- width: width - DRAGGABLE_WIDTH * 2,
5239
- height,
5240
- left: left + DRAGGABLE_WIDTH,
5241
- top: top - scrollY,
5242
- zIndex: 1000 + zoneDepth,
3995
+ ...sharedProps,
3996
+ editorMode: true,
3997
+ node,
5243
3998
  };
5244
3999
  }
5245
- default:
5246
- return {};
5247
- }
5248
- };
5249
-
5250
- const Hitboxes = ({ zoneId, parentZoneId, isEmptyZone }) => {
5251
- const tree = useTreeStore((state) => state.tree);
5252
- const isDraggingOnCanvas = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5253
- const scrollY = useDraggedItemStore((state) => state.scrollY);
5254
- const zoneDepth = useMemo(() => getItemDepthFromNode({ id: parentZoneId }, tree.root), [tree, parentZoneId]);
5255
- const zones = useZoneStore((state) => state.zones);
5256
- const hoveringZone = useZoneStore((state) => state.hoveringZone);
5257
- const isHoveringZone = hoveringZone === zoneId;
5258
- const hitboxContainer = useMemo(() => {
5259
- return document.querySelector('[data-ctfl-hitboxes]');
5260
- }, []);
5261
- const domRect = useMemo(() => {
5262
- if (!isDraggingOnCanvas)
5263
- return;
5264
- return document.querySelector(`[${CTFL_ZONE_ID}="${zoneId}"]`)?.getBoundingClientRect();
5265
- // eslint-disable-next-line react-hooks/exhaustive-deps
5266
- }, [zoneId, isDraggingOnCanvas]);
5267
- // Use the size of the cloned dragging element to offset the position of the hitboxes
5268
- // So that when dragging causes a dropzone to expand, the hitboxes will be in the correct position
5269
- const offsetRect = useMemo(() => {
5270
- if (!isDraggingOnCanvas || isEmptyZone || !isHoveringZone)
5271
- return;
5272
- return document.querySelector(`[${CTFL_DRAGGING_ELEMENT}]`)?.getBoundingClientRect();
5273
- // eslint-disable-next-line react-hooks/exhaustive-deps
5274
- }, [isEmptyZone, isHoveringZone, isDraggingOnCanvas]);
5275
- const zoneDirection = zones[parentZoneId]?.direction || 'vertical';
5276
- const isVertical = zoneDirection === 'vertical';
5277
- const isRoot = parentZoneId === ROOT_ID;
5278
- const { slotId: parentSlotId } = parseZoneId(parentZoneId);
5279
- const getStyles = useCallback((direction) => getHitboxStyles({
5280
- direction,
5281
- zoneDepth,
5282
- domRect,
5283
- scrollY,
5284
- offsetRect,
5285
- }), [zoneDepth, domRect, scrollY, offsetRect]);
5286
- const renderFinalRootHitbox = () => {
5287
- if (!isRoot)
5288
- return null;
5289
- return (React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(HitboxDirection.BOTTOM) }));
5290
- };
5291
- const renderSurroundingHitboxes = () => {
5292
- if (isRoot || parentSlotId)
5293
- return null;
5294
- return (React.createElement(React.Fragment, null,
5295
- React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.TOP : HitboxDirection.LEFT) }),
5296
- React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.BOTTOM : HitboxDirection.RIGHT) })));
5297
- };
5298
- const ActiveHitboxes = (React.createElement(React.Fragment, null,
5299
- React.createElement("div", { "data-ctfl-zone-id": zoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.SELF_VERTICAL : HitboxDirection.SELF_HORIZONTAL) }),
5300
- renderSurroundingHitboxes(),
5301
- renderFinalRootHitbox()));
5302
- if (!hitboxContainer) {
5303
- return null;
5304
- }
5305
- return createPortal(ActiveHitboxes, hitboxContainer);
5306
- };
5307
-
5308
- const isRelativePreviewSize = (width) => {
5309
- // For now, we solely allow 100% as relative value
5310
- return width === '100%';
5311
- };
5312
- const getTooltipPositions = ({ previewSize, tooltipRect, coordinates, }) => {
5313
- if (!coordinates || !tooltipRect) {
5314
- return { display: 'none' };
5315
- }
5316
- /**
5317
- * By default, the tooltip floats to the left of the element
5318
- */
5319
- const newTooltipStyles = { display: 'flex' };
5320
- // If the preview size is relative, we don't change the floating direction
5321
- if (!isRelativePreviewSize(previewSize)) {
5322
- const previewSizeMatch = previewSize.match(/(\d{1,})px/);
5323
- if (!previewSizeMatch) {
5324
- return { display: 'none' };
5325
- }
5326
- const previewSizePx = parseInt(previewSizeMatch[1]);
5327
- /**
5328
- * If the element is at the right edge of the canvas, and the element isn't wide enough to fit the tooltip width,
5329
- * we float the tooltip to the right of the element.
5330
- */
5331
- if (tooltipRect.width > previewSizePx - coordinates.right &&
5332
- tooltipRect.width > coordinates.width) {
5333
- newTooltipStyles['float'] = 'right';
5334
- }
5335
- }
5336
- const tooltipHeight = tooltipRect.height === 0 ? 32 : tooltipRect.height;
5337
- /**
5338
- * For elements with small heights, we don't want the tooltip covering the content in the element,
5339
- * so we show the tooltip at the top or bottom.
5340
- */
5341
- if (tooltipHeight * 2 > coordinates.height) {
5342
- /**
5343
- * If there's enough space for the tooltip at the top of the element, we show the tooltip at the top of the element,
5344
- * else we show the tooltip at the bottom.
5345
- */
5346
- if (tooltipHeight < coordinates.top) {
5347
- newTooltipStyles['bottom'] = coordinates.height;
5348
- }
5349
- else {
5350
- newTooltipStyles['top'] = coordinates.height;
5351
- }
5352
- }
5353
- /**
5354
- * If the component draws outside of the borders of the canvas to the left we move the tooltip to the right
5355
- * so that it is fully visible.
5356
- */
5357
- if (coordinates.left < 0) {
5358
- newTooltipStyles['left'] = -coordinates.left;
5359
- }
5360
- /**
5361
- * If for any reason, the element's top is negative, we show the tooltip at the bottom
5362
- */
5363
- if (coordinates.top < 0) {
5364
- newTooltipStyles['top'] = coordinates.height;
5365
- }
5366
- return newTooltipStyles;
5367
- };
5368
-
5369
- 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";
5370
- 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"};
5371
- styleInject(css_248z$1);
5372
-
5373
- const Tooltip = ({ coordinates, id, label, isAssemblyBlock, isContainer, isSelected, }) => {
5374
- const tooltipRef = useRef(null);
5375
- const previewSize = '100%'; // This should be based on breakpoints and added to usememo dependency array
5376
- const tooltipStyles = useMemo(() => {
5377
- const tooltipRect = tooltipRef.current?.getBoundingClientRect();
5378
- const draggableRect = document
5379
- .querySelector(`[data-ctfl-draggable-id="${id}"]`)
5380
- ?.getBoundingClientRect();
5381
- const newTooltipStyles = getTooltipPositions({
5382
- previewSize,
5383
- tooltipRect,
5384
- coordinates: draggableRect,
5385
- });
5386
- return newTooltipStyles;
5387
- // 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
5388
- // eslint-disable-next-line react-hooks/exhaustive-deps
5389
- }, [coordinates, id, tooltipRef.current]);
5390
- if (isSelected) {
5391
- return null;
5392
- }
5393
- return (React.createElement("div", { "data-tooltip": true, className: styles$1.tooltipWrapper },
5394
- React.createElement("div", { "data-tooltip": true, ref: tooltipRef, style: tooltipStyles, className: classNames(styles$1.overlay, {
5395
- [styles$1.overlayContainer]: isContainer,
5396
- [styles$1.overlayAssembly]: isAssemblyBlock,
5397
- }) }, label)));
4000
+ return {
4001
+ ...sharedProps,
4002
+ // Allows custom components to render differently in the editor. This needs to be activated
4003
+ // through options as the component has to be aware of this prop to not cause any React warnings.
4004
+ ...(options?.enableCustomEditorView ? { isInExpEditorMode: true } : {}),
4005
+ ...sanitizeNodeProps(props),
4006
+ };
4007
+ }, [cfCsrClassName, node, options?.enableCustomEditorView, props]);
4008
+ return { componentProps };
5398
4009
  };
5399
4010
 
5400
- function useSingleColumn(node, resolveDesignValue) {
5401
- const tree = useTreeStore((store) => store.tree);
5402
- const isSingleColumn = node.data.blockId === CONTENTFUL_COMPONENTS$1.singleColumn.id;
5403
- const isWrapped = useMemo(() => {
5404
- if (!node.parentId || !isSingleColumn) {
5405
- return false;
5406
- }
5407
- const parentNode = getItem({ id: node.parentId }, tree);
5408
- if (!parentNode || parentNode.data.blockId !== CONTENTFUL_COMPONENTS$1.columns.id) {
5409
- return false;
5410
- }
5411
- const { cfWrapColumns } = parentNode.data.props;
5412
- if (cfWrapColumns.type !== 'DesignValue') {
5413
- return false;
4011
+ function EditorBlock({ node, resolveDesignValue, wrappingPatternIds: parentWrappingPatternIds = new Set(), }) {
4012
+ const isRootAssemblyNode = node.type === ASSEMBLY_NODE_TYPE;
4013
+ const wrappingPatternIds = useMemo(() => {
4014
+ if (isRootAssemblyNode && node.data.blockId) {
4015
+ return new Set([node.data.blockId, ...parentWrappingPatternIds]);
5414
4016
  }
5415
- return resolveDesignValue(cfWrapColumns.valuesByBreakpoint);
5416
- }, [tree, node, isSingleColumn, resolveDesignValue]);
5417
- return {
5418
- isSingleColumn,
5419
- isWrapped,
5420
- };
5421
- }
5422
-
5423
- function getStyle$1(style, snapshot) {
5424
- if (!snapshot.isDropAnimating) {
5425
- return style;
5426
- }
5427
- return {
5428
- ...style,
5429
- // cannot be 0, but make it super tiny
5430
- transitionDuration: `0.001s`,
5431
- };
4017
+ return parentWrappingPatternIds;
4018
+ }, [isRootAssemblyNode, node, parentWrappingPatternIds]);
4019
+ const componentRegistration = useComponentRegistration(node);
4020
+ if (!componentRegistration) {
4021
+ return React.createElement(MissingComponentPlaceholder, { blockId: node.data.blockId });
4022
+ }
4023
+ if (isRootAssemblyNode && node.data.blockId && parentWrappingPatternIds.has(node.data.blockId)) {
4024
+ return (React.createElement(CircularDependencyErrorPlaceholder, { node: node, wrappingPatternIds: wrappingPatternIds }));
4025
+ }
4026
+ const slotNodes = {};
4027
+ for (const slotId in componentRegistration.definition.slots) {
4028
+ const nodes = node.children.filter((child) => child.data.slotId === slotId);
4029
+ slotNodes[slotId] =
4030
+ nodes.length === 0 ? (React.createElement("div", { className: styles$1.emptySlot })) : (React.createElement(React.Fragment, null, nodes.map((slotChildNode) => (React.createElement(EditorBlock, { key: slotChildNode.data.id, node: slotChildNode, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds })))));
4031
+ }
4032
+ const children = componentRegistration.definition.children
4033
+ ? node.children
4034
+ .filter((node) => node.data.slotId === undefined)
4035
+ .map((childNode) => (React.createElement(EditorBlock, { key: childNode.data.id, node: childNode, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds })))
4036
+ : null;
4037
+ return (React.createElement(RegistrationComponent, { node: node, resolveDesignValue: resolveDesignValue, componentRegistration: componentRegistration, slotNodes: slotNodes }, children));
5432
4038
  }
5433
- const EditorBlock = ({ node: rawNode, resolveDesignValue, renderDropzone, index, zoneId, userIsDragging, placeholder, wrappingPatternIds, }) => {
5434
- const { slotId } = parseZoneId(zoneId);
5435
- const ref = useRef(null);
5436
- const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
5437
- const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
5438
- const { node, componentId, elementToRender, definition, isPatternNode, isPatternComponent, isNestedPattern, } = useComponent({
5439
- node: rawNode,
4039
+ const RegistrationComponent = ({ node, resolveDesignValue, componentRegistration, slotNodes, children, }) => {
4040
+ const { componentProps } = useComponentProps({
4041
+ node,
5440
4042
  resolveDesignValue,
5441
- renderDropzone,
5442
- userIsDragging,
5443
- wrappingPatternIds,
4043
+ definition: componentRegistration.definition,
4044
+ options: componentRegistration.options,
5444
4045
  });
5445
- const { isSingleColumn, isWrapped } = useSingleColumn(node, resolveDesignValue);
5446
- const setDomRect = useDraggedItemStore((state) => state.setDomRect);
5447
- const isHoveredComponent = useDraggedItemStore((state) => state.hoveredComponentId === componentId);
5448
- const coordinates = useSelectedInstanceCoordinates({ node });
5449
- const displayName = node.data.displayName || rawNode.data.displayName || definition?.name;
5450
- const testId = `draggable-${node.data.blockId ?? 'node'}`;
5451
- const isSelected = node.data.id === selectedNodeId;
5452
- const isContainer = node.data.blockId === CONTENTFUL_COMPONENTS$1.container.id;
5453
- const isSlotComponent = Boolean(node.data.slotId);
5454
- const isDragDisabled = isNestedPattern || isPatternComponent || (isSingleColumn && isWrapped) || isSlotComponent;
5455
- const isEmptyZone = useMemo(() => {
5456
- return !node.children.filter((node) => node.data.slotId === slotId).length;
5457
- }, [node.children, slotId]);
5458
- useDraggablePosition({
5459
- draggableId: componentId,
5460
- draggableRef: ref,
5461
- position: DraggablePosition.MOUSE_POSITION,
5462
- });
5463
- const onClick = (e) => {
5464
- e.stopPropagation();
5465
- if (!userIsDragging) {
5466
- setSelectedNodeId(node.data.id);
5467
- // if it is the assembly directly we just want to select it as a normal component
5468
- if (isPatternNode) {
5469
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
5470
- nodeId: node.data.id,
5471
- });
5472
- return;
5473
- }
5474
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
5475
- assembly: node.data.assembly,
5476
- nodeId: node.data.id,
5477
- });
5478
- }
5479
- };
5480
- const onMouseOver = (e) => {
5481
- e.stopPropagation();
5482
- if (userIsDragging)
5483
- return;
5484
- sendMessage(OUTGOING_EVENTS.NewHoveredElement, {
5485
- nodeId: componentId,
5486
- });
5487
- };
5488
- const onMouseDown = (e) => {
5489
- if (isDragDisabled) {
5490
- return;
5491
- }
5492
- e.stopPropagation();
5493
- setDomRect(e.currentTarget.getBoundingClientRect());
5494
- };
5495
- const ToolTipAndPlaceholder = (React.createElement(React.Fragment, null,
5496
- React.createElement(Tooltip, { id: componentId, coordinates: coordinates, isAssemblyBlock: isPatternNode || isPatternComponent, isContainer: isContainer, isSelected: isSelected, label: displayName || 'No label specified' }),
5497
- React.createElement(Placeholder, { ...placeholder, id: componentId }),
5498
- userIsDragging && !isPatternComponent && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId, isEmptyZone: isEmptyZone }))));
5499
- return (React.createElement(Draggable, { key: componentId, draggableId: componentId, index: index, isDragDisabled: isDragDisabled, disableInteractiveElementBlocking: true }, (provided, snapshot) => elementToRender({
5500
- dragProps: {
5501
- ...provided.draggableProps,
5502
- ...provided.dragHandleProps,
5503
- 'data-ctfl-draggable-id': componentId,
5504
- 'data-test-id': testId,
5505
- innerRef: (refNode) => {
5506
- provided?.innerRef(refNode);
5507
- ref.current = refNode;
5508
- },
5509
- className: classNames(styles$1.DraggableComponent, {
5510
- [styles$1.isAssemblyBlock]: isPatternComponent || isPatternNode,
5511
- [styles$1.isDragging]: snapshot?.isDragging || userIsDragging,
5512
- [styles$1.isSelected]: isSelected,
5513
- [styles$1.isHoveringComponent]: isHoveredComponent,
5514
- }),
5515
- style: getStyle$1(provided.draggableProps.style, snapshot),
5516
- onMouseDown,
5517
- onMouseOver,
5518
- onClick,
5519
- ToolTipAndPlaceholder,
5520
- },
5521
- })));
4046
+ return React.createElement(ImportedComponentErrorBoundary, { componentId: node.data.blockId }, React.createElement(componentRegistration.component, { ...componentProps, ...slotNodes }, children));
5522
4047
  };
5523
4048
 
5524
- 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";
5525
- var styles = {"container":"EmptyContainer-module_container__XPH5b","highlight":"EmptyContainer-module_highlight__lcICy","icon":"EmptyContainer-module_icon__82-2O","label":"EmptyContainer-module_label__4TxRa"};
4049
+ var css_248z = ".EmptyCanvasMessage-module_empty-canvas-container__7K-0l {\n height: 200px;\n display: flex;\n width: 100%;\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.EmptyCanvasMessage-module_empty-canvas-icon__EztFr rect {\n fill: var(--exp-builder-gray400);\n}\n\n.EmptyCanvasMessage-module_empty-canvas-label__cbIrR {\n margin-left: var(--exp-builder-spacing-s);\n}\n";
4050
+ var styles = {"empty-canvas-container":"EmptyCanvasMessage-module_empty-canvas-container__7K-0l","empty-canvas-icon":"EmptyCanvasMessage-module_empty-canvas-icon__EztFr","empty-canvas-label":"EmptyCanvasMessage-module_empty-canvas-label__cbIrR"};
5526
4051
  styleInject(css_248z);
5527
4052
 
5528
- const EmptyContainer = ({ isDragging }) => {
5529
- return (React.createElement("div", { className: classNames(styles.container, {
5530
- [styles.highlight]: isDragging,
5531
- }), "data-type": "empty-container" },
5532
- React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "37", height: "36", fill: "none", className: styles.icon },
4053
+ const EmptyCanvasMessage = () => {
4054
+ return (React.createElement("div", { className: styles['empty-canvas-container'], "data-type": "empty-container" },
4055
+ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "37", height: "36", fill: "none", className: styles['empty-canvas-icon'] },
5533
4056
  React.createElement("rect", { width: "11.676", height: "11.676", x: "18.512", y: ".153", rx: "1.621", transform: "rotate(45 18.512 .153)" }),
5534
4057
  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)" }),
5535
4058
  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)" }),
5536
4059
  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)" }),
5537
4060
  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" })),
5538
- React.createElement("span", { className: styles.label }, "Add components to begin")));
5539
- };
5540
-
5541
- const useDropzoneDirection = ({ resolveDesignValue, node, zoneId }) => {
5542
- const zone = useZoneStore((state) => state.zones);
5543
- const upsertZone = useZoneStore((state) => state.upsertZone);
5544
- useEffect(() => {
5545
- function getDirection() {
5546
- if (!node || !node.data.blockId) {
5547
- return 'vertical';
5548
- }
5549
- if (!isContentfulStructureComponent(node.data.blockId)) {
5550
- return 'vertical';
5551
- }
5552
- if (node.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id) {
5553
- return 'horizontal';
5554
- }
5555
- const designValues = node.data.props['cfFlexDirection'];
5556
- if (!designValues || !resolveDesignValue || designValues.type !== 'DesignValue') {
5557
- return 'vertical';
5558
- }
5559
- const direction = resolveDesignValue(designValues.valuesByBreakpoint);
5560
- if (direction === 'row') {
5561
- return 'horizontal';
5562
- }
5563
- return 'vertical';
5564
- }
5565
- upsertZone(zoneId, { direction: getDirection() });
5566
- }, [node, resolveDesignValue, zoneId, upsertZone]);
5567
- return zone[zoneId]?.direction || 'vertical';
5568
- };
5569
-
5570
- function getStyle(style = {}, snapshot) {
5571
- if (!snapshot?.isDropAnimating) {
5572
- return style;
5573
- }
5574
- return {
5575
- ...style,
5576
- // cannot be 0, but make it super tiny
5577
- transitionDuration: `0.001s`,
5578
- };
5579
- }
5580
- const EditorBlockClone = ({ node: rawNode, resolveDesignValue, snapshot, provided, renderDropzone, wrappingPatternIds, }) => {
5581
- const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5582
- const { node, elementToRender } = useComponent({
5583
- node: rawNode,
5584
- resolveDesignValue,
5585
- renderDropzone,
5586
- userIsDragging,
5587
- wrappingPatternIds,
5588
- });
5589
- const isAssemblyBlock = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
5590
- return elementToRender({
5591
- dragProps: {
5592
- ...provided?.draggableProps,
5593
- ...provided?.dragHandleProps,
5594
- 'data-ctfl-dragging-element': 'true',
5595
- innerRef: provided?.innerRef,
5596
- className: classNames(styles$1.DraggableComponent, styles$1.DraggableClone, {
5597
- [styles$1.isAssemblyBlock]: isAssemblyBlock,
5598
- [styles$1.isDragging]: snapshot?.isDragging,
5599
- }),
5600
- style: getStyle(provided?.draggableProps.style, snapshot),
5601
- },
5602
- });
4061
+ React.createElement("span", { className: styles['empty-canvas-label'] }, "Add components to begin")));
5603
4062
  };
5604
4063
 
5605
- const getHtmlDragProps = (dragProps) => {
5606
- if (dragProps) {
5607
- const { ToolTipAndPlaceholder, Tag, innerRef, wrapComponent, ...htmlDragProps } = dragProps;
5608
- return htmlDragProps;
5609
- }
5610
- return {};
5611
- };
5612
- const getHtmlComponentProps = (props) => {
5613
- if (props) {
5614
- const { editorMode, renderDropzone, node, ...htmlProps } = props;
5615
- return htmlProps;
5616
- }
5617
- return {};
5618
- };
5619
-
5620
- function DropzoneClone({ node, zoneId, resolveDesignValue, WrapperComponent = 'div', renderDropzone, dragProps, wrappingPatternIds, ...rest }) {
5621
- const tree = useTreeStore((state) => state.tree);
5622
- const content = node?.children || tree.root?.children || [];
5623
- const { slotId } = parseZoneId(zoneId);
5624
- const htmlDraggableProps = getHtmlDragProps(dragProps);
5625
- const htmlProps = getHtmlComponentProps(rest);
5626
- const isRootZone = zoneId === ROOT_ID;
5627
- if (!resolveDesignValue) {
5628
- return null;
5629
- }
5630
- return (React.createElement(WrapperComponent, { ...htmlDraggableProps, ...htmlProps, className: classNames(dragProps?.className, styles$1.Dropzone, styles$1.DropzoneClone, rest.className, {
5631
- [styles$1.isRoot]: isRootZone,
5632
- [styles$1.isEmptyZone]: !content.length,
5633
- }), "data-ctfl-slot-id": slotId, ref: (refNode) => {
5634
- if (dragProps?.innerRef) {
5635
- dragProps.innerRef(refNode);
5636
- }
5637
- } }, content
5638
- .filter((node) => node.data.slotId === slotId)
5639
- .map((item) => {
5640
- const componentId = item.data.id;
5641
- return (React.createElement(EditorBlockClone, { key: componentId, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone, wrappingPatternIds: wrappingPatternIds }));
5642
- })));
5643
- }
5644
-
5645
- function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponent = 'div', dragProps, wrappingPatternIds: parentWrappingPatternIds = new Set(), ...rest }) {
5646
- const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5647
- const draggedItem = useDraggedItemStore((state) => state.draggedItem);
5648
- const isDraggingNewComponent = useDraggedItemStore((state) => Boolean(state.componentId));
5649
- const isHoveringZone = useZoneStore((state) => state.hoveringZone === zoneId);
5650
- const tree = useTreeStore((state) => state.tree);
5651
- const content = node?.children || tree.root?.children || [];
5652
- const { slotId } = parseZoneId(zoneId);
5653
- const direction = useDropzoneDirection({ resolveDesignValue, node, zoneId });
5654
- const draggedDestinationId = draggedItem && draggedItem.destination?.droppableId;
5655
- const draggedNode = useMemo(() => {
5656
- if (!draggedItem)
5657
- return;
5658
- return getItem({ id: draggedItem.draggableId }, tree);
5659
- }, [draggedItem, tree]);
5660
- const isRootZone = zoneId === ROOT_ID;
5661
- const isDestination = draggedDestinationId === zoneId;
5662
- const isEmptyCanvas = isRootZone && !content.length;
5663
- const isAssembly = ASSEMBLY_NODE_TYPES.includes(node?.type || '');
5664
- const isRootAssembly = node?.type === ASSEMBLY_NODE_TYPE;
5665
- const htmlDraggableProps = getHtmlDragProps(dragProps);
5666
- const htmlProps = getHtmlComponentProps(rest);
5667
- const wrappingPatternIds = useMemo(() => {
5668
- // On the top level, the node is not defined. If the root blockId is not the default string,
5669
- // we assume that it is the entry ID of the experience/ pattern to properly detect circular dependencies
5670
- if (!node && tree.root.data.blockId && tree.root.data.blockId !== ROOT_ID) {
5671
- return new Set([tree.root.data.blockId, ...parentWrappingPatternIds]);
5672
- }
5673
- if (isRootAssembly && node?.data.blockId) {
5674
- return new Set([node.data.blockId, ...parentWrappingPatternIds]);
5675
- }
5676
- return parentWrappingPatternIds;
5677
- }, [isRootAssembly, node, parentWrappingPatternIds, tree.root.data.blockId]);
5678
- // To avoid a circular dependency, we create the recursive rendering function here and trickle it down
5679
- const renderDropzone = useCallback((node, props) => {
5680
- return (React.createElement(Dropzone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds, ...props }));
5681
- }, [wrappingPatternIds, resolveDesignValue]);
5682
- const renderClonedDropzone = useCallback((node, props) => {
5683
- return (React.createElement(DropzoneClone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, renderDropzone: renderClonedDropzone, wrappingPatternIds: wrappingPatternIds, ...props }));
5684
- }, [resolveDesignValue, wrappingPatternIds]);
5685
- const isDropzoneEnabled = useMemo(() => {
5686
- const isColumns = node?.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id;
5687
- const isDraggingSingleColumn = draggedNode?.data.blockId === CONTENTFUL_COMPONENTS$1.singleColumn.id;
5688
- const isParentOfDraggedNode = node?.data.id === draggedNode?.parentId;
5689
- // If dragging a single column, only enable the dropzone of the parent
5690
- // columns component
5691
- if (isDraggingSingleColumn && isColumns && isParentOfDraggedNode) {
5692
- return true;
5693
- }
5694
- // If dragging a single column, disable dropzones for any component besides
5695
- // the parent of the dragged single column
5696
- if (isDraggingSingleColumn && !isParentOfDraggedNode) {
5697
- return false;
5698
- }
5699
- // Disable dropzone for Columns component
5700
- if (isColumns) {
5701
- return false;
5702
- }
5703
- // Disable dropzone for Assembly
5704
- if (isAssembly) {
5705
- return false;
5706
- }
5707
- // Enable dropzone for the non-root hovered zones if component is not allowed on root
5708
- if (!isDraggingNewComponent &&
5709
- !isComponentAllowedOnRoot({ type: draggedNode?.type, componentId: draggedNode?.data.blockId })) {
5710
- return isHoveringZone && !isRootZone;
5711
- }
5712
- // Enable dropzone for the hovered zone only
5713
- return isHoveringZone;
5714
- }, [isAssembly, isHoveringZone, isRootZone, isDraggingNewComponent, draggedNode, node]);
5715
- if (!resolveDesignValue) {
5716
- return null;
5717
- }
5718
- const isPatternWrapperComponentFullHeight = isRootAssembly
5719
- ? node.children.length === 1 &&
5720
- resolveDesignValue(node?.children[0]?.data.props.cfHeight?.valuesByBreakpoint ?? {}, 'cfHeight') === '100%'
5721
- : false;
5722
- const isPatternWrapperComponentFullWidth = isRootAssembly
5723
- ? node.children.length === 1 &&
5724
- resolveDesignValue(node?.children[0]?.data.props.cfWidth?.valuesByBreakpoint ?? {}, 'cfWidth') === '100%'
5725
- : false;
5726
- 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) => {
5727
- return (React.createElement(WrapperComponent, { ...(provided || { droppableProps: {} }).droppableProps, ...htmlDraggableProps, ...htmlProps, ref: (refNode) => {
5728
- if (dragProps?.innerRef) {
5729
- dragProps.innerRef(refNode);
5730
- }
5731
- provided?.innerRef(refNode);
5732
- }, id: zoneId, "data-ctfl-zone-id": zoneId, "data-ctfl-slot-id": slotId, className: classNames(dragProps?.className, styles$1.Dropzone, className, {
5733
- [styles$1.isEmptyCanvas]: isEmptyCanvas,
5734
- [styles$1.isDragging]: userIsDragging,
5735
- [styles$1.isDestination]: isDestination && !isAssembly,
5736
- [styles$1.isRoot]: isRootZone,
5737
- [styles$1.isEmptyZone]: !content.length,
5738
- [styles$1.isSlot]: Boolean(slotId),
5739
- [styles$1.fullHeight]: isPatternWrapperComponentFullHeight,
5740
- [styles$1.fullWidth]: isPatternWrapperComponentFullWidth,
5741
- }) },
5742
- isEmptyCanvas ? (React.createElement(EmptyContainer, { isDragging: isRootZone && userIsDragging })) : (content
5743
- .filter((node) => node.data.slotId === slotId)
5744
- .map((item, i) => (React.createElement(EditorBlock, { placeholder: {
5745
- isDraggingOver: snapshot?.isDraggingOver,
5746
- totalIndexes: content.length,
5747
- elementIndex: i,
5748
- dropzoneElementId: zoneId,
5749
- direction,
5750
- }, index: i, zoneId: zoneId, key: item.data.id, userIsDragging: userIsDragging, draggingNewComponent: isDraggingNewComponent, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone, wrappingPatternIds: wrappingPatternIds })))),
5751
- provided?.placeholder,
5752
- dragProps?.ToolTipAndPlaceholder));
5753
- }));
5754
- }
5755
-
5756
- const RootRenderer = ({ onChange }) => {
4064
+ const RootRenderer = () => {
5757
4065
  useEditorSubscriber();
5758
- const dragItem = useDraggedItemStore((state) => state.componentId);
5759
- const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5760
- const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
5761
4066
  const breakpoints = useTreeStore((state) => state.breakpoints);
5762
- const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
5763
4067
  const containerRef = useRef(null);
5764
4068
  const { resolveDesignValue } = useBreakpoints(breakpoints);
5765
- const [containerStyles, setContainerStyles] = useState({});
5766
4069
  const tree = useTreeStore((state) => state.tree);
5767
- const handleMouseOver = useCallback(() => {
5768
- // Remove hover state set by UI when mouse is over canvas
5769
- setHoveredComponentId();
5770
- // Remove hover styling from components in the layers tab
5771
- sendMessage(OUTGOING_EVENTS.NewHoveredElement, {});
5772
- }, [setHoveredComponentId]);
5773
- const handleClickOutside = useCallback((e) => {
5774
- const element = e.target;
5775
- const isRoot = element.getAttribute('data-ctfl-zone-id') === ROOT_ID;
5776
- const clickedOnCanvas = element.closest(`[data-ctfl-root]`);
5777
- if (clickedOnCanvas && !isRoot) {
5778
- return;
5779
- }
5780
- sendMessage(OUTGOING_EVENTS.OutsideCanvasClick, {
5781
- outsideCanvasClick: true,
5782
- });
5783
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
5784
- nodeId: '',
5785
- });
5786
- setSelectedNodeId('');
5787
- }, [setSelectedNodeId]);
5788
- const handleResizeCanvas = useCallback(() => {
5789
- const parentElement = containerRef.current?.parentElement;
5790
- if (!parentElement) {
5791
- return;
5792
- }
5793
- let siblingHeight = 0;
5794
- for (const child of parentElement.children) {
5795
- if (!child.hasAttribute('data-ctfl-root')) {
5796
- siblingHeight += child.getBoundingClientRect().height;
5797
- }
5798
- }
5799
- if (!siblingHeight) {
5800
- /**
5801
- * DRAGGABLE_HEIGHT is subtracted here due to an uninteded scrolling effect
5802
- * when dragging a new component onto the canvas
5803
- *
5804
- * The DRAGGABLE_HEIGHT is then added as margin bottom to offset this value
5805
- * so that visually there is no difference to the user.
5806
- */
5807
- setContainerStyles({
5808
- minHeight: `${window.innerHeight - DRAGGABLE_HEIGHT}px`,
5809
- });
5810
- return;
5811
- }
5812
- setContainerStyles({
5813
- minHeight: `${window.innerHeight - siblingHeight}px`,
5814
- });
5815
- // eslint-disable-next-line react-hooks/exhaustive-deps
5816
- }, [containerRef.current]);
5817
- useEffect(() => {
5818
- if (onChange)
5819
- onChange(tree);
5820
- }, [tree, onChange]);
5821
- useEffect(() => {
5822
- window.addEventListener('mouseover', handleMouseOver);
5823
- return () => {
5824
- window.removeEventListener('mouseover', handleMouseOver);
5825
- };
5826
- }, [handleMouseOver]);
5827
- useEffect(() => {
5828
- document.addEventListener('click', handleClickOutside);
5829
- return () => {
5830
- document.removeEventListener('click', handleClickOutside);
5831
- };
5832
- }, [handleClickOutside]);
5833
- useEffect(() => {
5834
- handleResizeCanvas();
5835
- // eslint-disable-next-line react-hooks/exhaustive-deps
5836
- }, [containerRef.current]);
5837
- return (React.createElement(DNDProvider, null,
5838
- dragItem && React.createElement(DraggableContainer, { id: dragItem }),
5839
- React.createElement("div", { "data-ctfl-root": true, className: styles$3.container, ref: containerRef, style: containerStyles },
5840
- userIsDragging && React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitbox }),
5841
- React.createElement(Dropzone, { zoneId: ROOT_ID, resolveDesignValue: resolveDesignValue }),
5842
- userIsDragging && (React.createElement(React.Fragment, null,
5843
- React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitboxLower }),
5844
- React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.canvasBottomSpacer })))),
5845
- React.createElement("div", { "data-ctfl-hitboxes": true })));
4070
+ // If the root blockId is defined but not the default string, it is the entry ID
4071
+ // of the experience/ pattern to properly detect circular dependencies.
4072
+ const rootBlockId = tree.root.data.blockId ?? ROOT_ID;
4073
+ const wrappingPatternIds = rootBlockId !== ROOT_ID ? new Set([rootBlockId]) : new Set();
4074
+ return (React.createElement(React.Fragment, null,
4075
+ React.createElement("div", { "data-ctfl-root": true, className: styles$2.rootContainer, ref: containerRef }, !tree.root.children.length ? (React.createElement(EmptyCanvasMessage, null)) : (tree.root.children.map((topLevelChildNode) => (React.createElement(EditorBlock, { key: topLevelChildNode.data.id, node: topLevelChildNode, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds })))))));
5846
4076
  };
5847
4077
 
5848
4078
  const useInitializeEditor = () => {
@@ -5886,38 +4116,11 @@ const useInitializeEditor = () => {
5886
4116
  const VisualEditorRoot = ({ experience }) => {
5887
4117
  const initialized = useInitializeEditor();
5888
4118
  const setHyperLinkPattern = useEditorStore((state) => state.setHyperLinkPattern);
5889
- const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
5890
- const setHoveringZone = useZoneStore((state) => state.setHoveringZone);
5891
4119
  useEffect(() => {
5892
4120
  if (experience?.hyperlinkPattern) {
5893
4121
  setHyperLinkPattern(experience.hyperlinkPattern);
5894
4122
  }
5895
4123
  }, [experience?.hyperlinkPattern, setHyperLinkPattern]);
5896
- useEffect(() => {
5897
- const onMouseMove = (e) => {
5898
- setMousePosition(e.clientX, e.clientY);
5899
- const target = e.target;
5900
- const zoneId = target.closest(`[${CTFL_ZONE_ID}]`)?.getAttribute(CTFL_ZONE_ID);
5901
- if (zoneId) {
5902
- setHoveringZone(zoneId);
5903
- }
5904
- if (!SimulateDnD$1.isDragging) {
5905
- return;
5906
- }
5907
- if (target.id === NEW_COMPONENT_ID) {
5908
- return;
5909
- }
5910
- SimulateDnD$1.updateDrag(e.clientX, e.clientY);
5911
- sendMessage(OUTGOING_EVENTS.MouseMove, {
5912
- clientX: e.pageX - window.scrollX,
5913
- clientY: e.pageY - window.scrollY,
5914
- });
5915
- };
5916
- document.addEventListener('mousemove', onMouseMove);
5917
- return () => {
5918
- document.removeEventListener('mousemove', onMouseMove);
5919
- };
5920
- }, [setHoveringZone, setMousePosition]);
5921
4124
  if (!initialized)
5922
4125
  return null;
5923
4126
  return React.createElement(RootRenderer, null);