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