@contentful/experiences-visual-editor-react 0.0.1-alpha.10

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 ADDED
@@ -0,0 +1,4529 @@
1
+ import styleInject from 'style-inject';
2
+ import React, { useRef, useMemo, useEffect, useState, forwardRef, useCallback } from 'react';
3
+ import md5 from 'md5';
4
+ import { BLOCKS } from '@contentful/rich-text-types';
5
+ import { Draggable, Droppable, DragDropContext } from '@hello-pangea/dnd';
6
+ import classNames from 'classnames';
7
+ import { create } from 'zustand';
8
+ import { isEqual, get as get$1, omit } from 'lodash-es';
9
+ import '@contentful/rich-text-react-renderer';
10
+ import { produce } from 'immer';
11
+ import { createPortal } from 'react-dom';
12
+ import { v4 } from 'uuid';
13
+
14
+ var css_248z$8 = "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";
15
+ styleInject(css_248z$8);
16
+
17
+ const INCOMING_EVENTS$1 = {
18
+ RequestEditorMode: 'requestEditorMode',
19
+ ExperienceUpdated: 'componentTreeUpdated',
20
+ ComponentDraggingChanged: 'componentDraggingChanged',
21
+ ComponentDragCanceled: 'componentDragCanceled',
22
+ ComponentDragStarted: 'componentDragStarted',
23
+ ComponentDragEnded: 'componentDragEnded',
24
+ ComponentMoveEnded: 'componentMoveEnded',
25
+ CanvasResized: 'canvasResized',
26
+ SelectComponent: 'selectComponent',
27
+ HoverComponent: 'hoverComponent',
28
+ UpdatedEntity: 'updatedEntity',
29
+ AssembliesAdded: 'assembliesAdded',
30
+ AssembliesRegistered: 'assembliesRegistered',
31
+ InitEditor: 'initEditor',
32
+ MouseMove: 'mouseMove',
33
+ };
34
+ const CONTENTFUL_COMPONENTS$1 = {
35
+ section: {
36
+ id: 'contentful-section',
37
+ name: 'Section',
38
+ },
39
+ container: {
40
+ id: 'contentful-container',
41
+ name: 'Container',
42
+ },
43
+ columns: {
44
+ id: 'contentful-columns',
45
+ name: 'Columns',
46
+ },
47
+ singleColumn: {
48
+ id: 'contentful-single-column',
49
+ name: 'Column',
50
+ },
51
+ button: {
52
+ id: 'button',
53
+ name: 'Button',
54
+ },
55
+ heading: {
56
+ id: 'heading',
57
+ name: 'Heading',
58
+ },
59
+ image: {
60
+ id: 'image',
61
+ name: 'Image',
62
+ },
63
+ richText: {
64
+ id: 'richText',
65
+ name: 'Rich Text',
66
+ },
67
+ text: {
68
+ id: 'text',
69
+ name: 'Text',
70
+ },
71
+ };
72
+ const EMPTY_CONTAINER_HEIGHT$1 = '80px';
73
+ const DEFAULT_IMAGE_WIDTH = '500px';
74
+ var PostMessageMethods$2;
75
+ (function (PostMessageMethods) {
76
+ PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
77
+ PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
78
+ })(PostMessageMethods$2 || (PostMessageMethods$2 = {}));
79
+ const SUPPORTED_IMAGE_FORMATS = ['jpg', 'png', 'webp', 'gif', 'avif'];
80
+
81
+ const structureComponents = new Set([
82
+ CONTENTFUL_COMPONENTS$1.section.id,
83
+ CONTENTFUL_COMPONENTS$1.columns.id,
84
+ CONTENTFUL_COMPONENTS$1.container.id,
85
+ CONTENTFUL_COMPONENTS$1.singleColumn.id,
86
+ ]);
87
+ const isContentfulStructureComponent = (componentId) => structureComponents.has(componentId ?? '');
88
+ const isEmptyStructureWithRelativeHeight = (children, componentId, height) => {
89
+ return (children === 0 &&
90
+ isContentfulStructureComponent(componentId) &&
91
+ !height?.toString().endsWith('px'));
92
+ };
93
+
94
+ const findOutermostCoordinates = (first, second) => {
95
+ return {
96
+ top: Math.min(first.top, second.top),
97
+ right: Math.max(first.right, second.right),
98
+ bottom: Math.max(first.bottom, second.bottom),
99
+ left: Math.min(first.left, second.left),
100
+ };
101
+ };
102
+ const getElementCoordinates = (element) => {
103
+ const rect = element.getBoundingClientRect();
104
+ /**
105
+ * If element does not have children, or element has it's own width or height,
106
+ * return the element's coordinates.
107
+ */
108
+ if (element.children.length === 0 || rect.width !== 0 || rect.height !== 0) {
109
+ return rect;
110
+ }
111
+ const rects = [];
112
+ /**
113
+ * If element has children, or element does not have it's own width and height,
114
+ * we find the cordinates of the children, and assume the outermost coordinates of the children
115
+ * as the coordinate of the element.
116
+ *
117
+ * E.g child1 => {top: 2, bottom: 3, left: 4, right: 6} & child2 => {top: 1, bottom: 8, left: 12, right: 24}
118
+ * The final assumed coordinates of the element would be => { top: 1, right: 24, bottom: 8, left: 4 }
119
+ */
120
+ for (const child of element.children) {
121
+ const childRect = getElementCoordinates(child);
122
+ if (childRect.width !== 0 || childRect.height !== 0) {
123
+ const { top, right, bottom, left } = childRect;
124
+ rects.push({ top, right, bottom, left });
125
+ }
126
+ }
127
+ if (rects.length === 0) {
128
+ return rect;
129
+ }
130
+ const { top, right, bottom, left } = rects.reduce(findOutermostCoordinates);
131
+ return DOMRect.fromRect({
132
+ x: left,
133
+ y: top,
134
+ height: bottom - top,
135
+ width: right - left,
136
+ });
137
+ };
138
+
139
+ class ParseError extends Error {
140
+ constructor(message) {
141
+ super(message);
142
+ }
143
+ }
144
+ const isValidJsonObject = (s) => {
145
+ try {
146
+ const result = JSON.parse(s);
147
+ if ('object' !== typeof result) {
148
+ return false;
149
+ }
150
+ return true;
151
+ }
152
+ catch (e) {
153
+ return false;
154
+ }
155
+ };
156
+ const doesMismatchMessageSchema = (event) => {
157
+ try {
158
+ tryParseMessage(event);
159
+ return false;
160
+ }
161
+ catch (e) {
162
+ if (e instanceof ParseError) {
163
+ return e.message;
164
+ }
165
+ throw e;
166
+ }
167
+ };
168
+ const tryParseMessage = (event) => {
169
+ if (!event.data) {
170
+ throw new ParseError('Field event.data is missing');
171
+ }
172
+ if ('string' !== typeof event.data) {
173
+ throw new ParseError(`Field event.data must be a string, instead of '${typeof event.data}'`);
174
+ }
175
+ if (!isValidJsonObject(event.data)) {
176
+ throw new ParseError('Field event.data must be a valid JSON object serialized as string');
177
+ }
178
+ const eventData = JSON.parse(event.data);
179
+ if (!eventData.source) {
180
+ throw new ParseError(`Field eventData.source must be equal to 'composability-app'`);
181
+ }
182
+ if ('composability-app' !== eventData.source) {
183
+ throw new ParseError(`Field eventData.source must be equal to 'composability-app', instead of '${eventData.source}'`);
184
+ }
185
+ // check eventData.eventType
186
+ const supportedEventTypes = Object.values(INCOMING_EVENTS$1);
187
+ if (!supportedEventTypes.includes(eventData.eventType)) {
188
+ // Expected message: This message is handled in the EntityStore to store fetched entities
189
+ if (eventData.eventType !== PostMessageMethods$2.REQUESTED_ENTITIES) {
190
+ throw new ParseError(`Field eventData.eventType must be one of the supported values: [${supportedEventTypes.join(', ')}]`);
191
+ }
192
+ }
193
+ return eventData;
194
+ };
195
+
196
+ const transformFill = (value) => (value === 'fill' ? '100%' : value);
197
+ const transformGridColumn = (span) => {
198
+ if (!span) {
199
+ return {};
200
+ }
201
+ return {
202
+ gridColumn: `span ${span}`,
203
+ };
204
+ };
205
+ const transformBorderStyle = (value) => {
206
+ if (!value)
207
+ return {};
208
+ const parts = value.split(' ');
209
+ // Just accept the passed value
210
+ if (parts.length < 3)
211
+ return { border: value };
212
+ // Replace the second part always with `solid` and set the box sizing accordingly
213
+ const [borderSize, borderStyle, ...borderColorParts] = parts;
214
+ const borderColor = borderColorParts.join(' ');
215
+ return {
216
+ border: `${borderSize} ${borderStyle} ${borderColor}`,
217
+ };
218
+ };
219
+ const transformAlignment = (cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection = 'column') => cfFlexDirection === 'row'
220
+ ? {
221
+ alignItems: cfHorizontalAlignment,
222
+ justifyContent: cfVerticalAlignment === 'center' ? `safe ${cfVerticalAlignment}` : cfVerticalAlignment,
223
+ }
224
+ : {
225
+ alignItems: cfVerticalAlignment,
226
+ justifyContent: cfHorizontalAlignment === 'center'
227
+ ? `safe ${cfHorizontalAlignment}`
228
+ : cfHorizontalAlignment,
229
+ };
230
+ const transformBackgroundImage = (cfBackgroundImageUrl, cfBackgroundImageOptions) => {
231
+ const matchBackgroundSize = (scaling) => {
232
+ if ('fill' === scaling)
233
+ return 'cover';
234
+ if ('fit' === scaling)
235
+ return 'contain';
236
+ };
237
+ const matchBackgroundPosition = (alignment) => {
238
+ if (!alignment || 'string' !== typeof alignment) {
239
+ return;
240
+ }
241
+ let [horizontalAlignment, verticalAlignment] = alignment.trim().split(/\s+/, 2);
242
+ // Special case for handling single values
243
+ // for backwards compatibility with single values 'right','left', 'center', 'top','bottom'
244
+ if (horizontalAlignment && !verticalAlignment) {
245
+ const singleValue = horizontalAlignment;
246
+ switch (singleValue) {
247
+ case 'left':
248
+ horizontalAlignment = 'left';
249
+ verticalAlignment = 'center';
250
+ break;
251
+ case 'right':
252
+ horizontalAlignment = 'right';
253
+ verticalAlignment = 'center';
254
+ break;
255
+ case 'center':
256
+ horizontalAlignment = 'center';
257
+ verticalAlignment = 'center';
258
+ break;
259
+ case 'top':
260
+ horizontalAlignment = 'center';
261
+ verticalAlignment = 'top';
262
+ break;
263
+ case 'bottom':
264
+ horizontalAlignment = 'center';
265
+ verticalAlignment = 'bottom';
266
+ break;
267
+ // just fall down to the normal validation logic for horiz and vert
268
+ }
269
+ }
270
+ const isHorizontalValid = ['left', 'right', 'center'].includes(horizontalAlignment);
271
+ const isVerticalValid = ['top', 'bottom', 'center'].includes(verticalAlignment);
272
+ horizontalAlignment = isHorizontalValid ? horizontalAlignment : 'left';
273
+ verticalAlignment = isVerticalValid ? verticalAlignment : 'top';
274
+ return `${horizontalAlignment} ${verticalAlignment}`;
275
+ };
276
+ if (!cfBackgroundImageUrl) {
277
+ return;
278
+ }
279
+ let backgroundImage;
280
+ let backgroundImageSet;
281
+ if (typeof cfBackgroundImageUrl === 'string') {
282
+ backgroundImage = `url(${cfBackgroundImageUrl})`;
283
+ }
284
+ else {
285
+ const imgSet = cfBackgroundImageUrl.srcSet?.join(',');
286
+ backgroundImage = `url(${cfBackgroundImageUrl.url})`;
287
+ backgroundImageSet = `image-set(${imgSet})`;
288
+ }
289
+ return {
290
+ backgroundImage,
291
+ backgroundImage2: backgroundImageSet,
292
+ backgroundRepeat: cfBackgroundImageOptions?.scaling === 'tile' ? 'repeat' : 'no-repeat',
293
+ backgroundPosition: matchBackgroundPosition(cfBackgroundImageOptions?.alignment),
294
+ backgroundSize: matchBackgroundSize(cfBackgroundImageOptions?.scaling),
295
+ };
296
+ };
297
+ const transformWidthSizing = ({ value, cfMargin, }) => {
298
+ if (!value || !cfMargin)
299
+ return;
300
+ const transformedValue = transformFill(value);
301
+ const marginValues = cfMargin.split(' ');
302
+ const rightMargin = marginValues[1] || '0px';
303
+ const leftMargin = marginValues[3] || '0px';
304
+ const calcValue = `calc(${transformedValue} - ${leftMargin} - ${rightMargin})`;
305
+ /**
306
+ * We want to check if the calculated value is valid CSS. If this fails,
307
+ * this means the `transformedValue` is not a calculable value (not a px, rem, or %).
308
+ * The value may instead be a string such as `min-content` or `max-content`. In
309
+ * that case we don't want to use calc and instead return the raw value.
310
+ */
311
+ if (typeof window !== 'undefined' && CSS.supports('width', calcValue)) {
312
+ return calcValue;
313
+ }
314
+ return transformedValue;
315
+ };
316
+
317
+ const toCSSAttribute = (key) => {
318
+ let val = key.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());
319
+ // Remove the number from the end of the key to allow for overrides on style properties
320
+ val = val.replace(/\d+$/, '');
321
+ return val;
322
+ };
323
+ const buildStyleTag = ({ styles, nodeId }) => {
324
+ const stylesStr = Object.entries(styles)
325
+ .filter(([, value]) => value !== undefined)
326
+ .reduce((acc, [key, value]) => `${acc}
327
+ ${toCSSAttribute(key)}: ${value};`, '');
328
+ const className = `cfstyles-${nodeId ? nodeId : md5(stylesStr)}`;
329
+ const styleRule = `.${className}{ ${stylesStr} }`;
330
+ return [className, styleRule];
331
+ };
332
+ const buildCfStyles = ({ cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection, cfFlexWrap, cfMargin, cfPadding, cfBackgroundColor, cfWidth, cfHeight, cfMaxWidth, cfBorder, cfBorderRadius, cfGap, cfBackgroundImageUrl, cfBackgroundImageOptions, cfFontSize, cfFontWeight, cfImageOptions, cfLineHeight, cfLetterSpacing, cfTextColor, cfTextAlign, cfTextTransform, cfTextBold, cfTextItalic, cfTextUnderline, cfColumnSpan, }) => {
333
+ return {
334
+ margin: cfMargin,
335
+ padding: cfPadding,
336
+ backgroundColor: cfBackgroundColor,
337
+ width: transformWidthSizing({ value: cfWidth || cfImageOptions?.width, cfMargin }),
338
+ height: transformFill(cfHeight || cfImageOptions?.height),
339
+ maxWidth: cfMaxWidth,
340
+ ...transformGridColumn(cfColumnSpan),
341
+ ...transformBorderStyle(cfBorder),
342
+ borderRadius: cfBorderRadius,
343
+ gap: cfGap,
344
+ ...transformAlignment(cfHorizontalAlignment, cfVerticalAlignment, cfFlexDirection),
345
+ flexDirection: cfFlexDirection,
346
+ flexWrap: cfFlexWrap,
347
+ ...transformBackgroundImage(cfBackgroundImageUrl, cfBackgroundImageOptions),
348
+ fontSize: cfFontSize,
349
+ fontWeight: cfTextBold ? 'bold' : cfFontWeight,
350
+ fontStyle: cfTextItalic ? 'italic' : 'normal',
351
+ lineHeight: cfLineHeight,
352
+ letterSpacing: cfLetterSpacing,
353
+ color: cfTextColor,
354
+ textAlign: cfTextAlign,
355
+ textTransform: cfTextTransform,
356
+ textDecoration: cfTextUnderline ? 'underline' : 'none',
357
+ boxSizing: 'border-box',
358
+ objectFit: cfImageOptions?.objectFit,
359
+ objectPosition: cfImageOptions?.objectPosition,
360
+ };
361
+ };
362
+ /**
363
+ * Container/section default behavior:
364
+ * Default height => height: EMPTY_CONTAINER_HEIGHT (120px)
365
+ * If a container component has children => height: 'fit-content'
366
+ */
367
+ const calculateNodeDefaultHeight = ({ blockId, children, value, }) => {
368
+ if (!blockId || !isContentfulStructureComponent(blockId) || value !== 'auto') {
369
+ return value;
370
+ }
371
+ if (children.length) {
372
+ return '100%';
373
+ }
374
+ return EMPTY_CONTAINER_HEIGHT$1;
375
+ };
376
+
377
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
378
+ function get(obj, path) {
379
+ if (!path.length) {
380
+ return obj;
381
+ }
382
+ try {
383
+ const [currentPath, ...nextPath] = path;
384
+ return get(obj[currentPath], nextPath);
385
+ }
386
+ catch (err) {
387
+ return undefined;
388
+ }
389
+ }
390
+
391
+ const getBoundValue = (entryOrAsset, path) => {
392
+ const value = get(entryOrAsset, path.split('/').slice(2, -1));
393
+ return value && typeof value == 'object' && value.url
394
+ ? value.url
395
+ : value;
396
+ };
397
+
398
+ const transformRichText = (entryOrAsset, path) => {
399
+ const value = getBoundValue(entryOrAsset, path);
400
+ if (typeof value === 'string') {
401
+ return {
402
+ data: {},
403
+ content: [
404
+ {
405
+ nodeType: BLOCKS.PARAGRAPH,
406
+ data: {},
407
+ content: [
408
+ {
409
+ data: {},
410
+ nodeType: 'text',
411
+ value: value,
412
+ marks: [],
413
+ },
414
+ ],
415
+ },
416
+ ],
417
+ nodeType: BLOCKS.DOCUMENT,
418
+ };
419
+ }
420
+ if (typeof value === 'object' && value.nodeType === BLOCKS.DOCUMENT) {
421
+ return value;
422
+ }
423
+ return undefined;
424
+ };
425
+
426
+ function getOptimizedImageUrl(url, width, quality, format) {
427
+ if (url.startsWith('//')) {
428
+ url = 'https:' + url;
429
+ }
430
+ const params = new URLSearchParams();
431
+ if (width) {
432
+ params.append('w', width.toString());
433
+ }
434
+ if (quality && quality > 0 && quality < 100) {
435
+ params.append('q', quality.toString());
436
+ }
437
+ if (format) {
438
+ params.append('fm', format);
439
+ }
440
+ const queryString = params.toString();
441
+ return `${url}${queryString ? '?' + queryString : ''}`;
442
+ }
443
+
444
+ const MAX_WIDTH_ALLOWED$1 = 2000;
445
+ const getOptimizedBackgroundImageAsset = (file, widthStyle, quality = 100, format) => {
446
+ if (!validateParams(file, quality, format)) ;
447
+ const url = file.url;
448
+ const { width1x, width2x } = getWidths(widthStyle, file);
449
+ const imageUrl1x = getOptimizedImageUrl(url, width1x, quality, format);
450
+ const imageUrl2x = getOptimizedImageUrl(url, width2x, quality, format);
451
+ const srcSet = [`url(${imageUrl1x}) 1x`, `url(${imageUrl2x}) 2x`];
452
+ const returnedUrlImageUrl = getOptimizedImageUrl(url, width2x, quality, format);
453
+ const optimizedBackgroundImageAsset = {
454
+ url: returnedUrlImageUrl,
455
+ srcSet,
456
+ file,
457
+ };
458
+ return optimizedBackgroundImageAsset;
459
+ function validateParams(file, quality, format) {
460
+ if (!file.details.image) {
461
+ throw Error('No image in file asset to transform');
462
+ }
463
+ if (quality < 0 || quality > 100) {
464
+ throw Error('Quality must be between 0 and 100');
465
+ }
466
+ if (format && !SUPPORTED_IMAGE_FORMATS.includes(format)) {
467
+ throw Error(`Format must be one of ${SUPPORTED_IMAGE_FORMATS.join(', ')}`);
468
+ }
469
+ return true;
470
+ }
471
+ };
472
+ function getWidths(widthStyle, file) {
473
+ let width1x = 0;
474
+ let width2x = 0;
475
+ const intrinsicImageWidth = file.details.image.width;
476
+ if (widthStyle.endsWith('px')) {
477
+ width1x = Math.min(Number(widthStyle.replace('px', '')), intrinsicImageWidth);
478
+ }
479
+ else {
480
+ width1x = Math.min(MAX_WIDTH_ALLOWED$1, intrinsicImageWidth);
481
+ }
482
+ width2x = Math.min(width1x * 2, intrinsicImageWidth);
483
+ return { width1x, width2x };
484
+ }
485
+
486
+ const MAX_WIDTH_ALLOWED = 4000;
487
+ const getOptimizedImageAsset = (file, sizes, quality = 100, format) => {
488
+ if (!validateParams(file, quality, format)) ;
489
+ const url = file.url;
490
+ const maxWidth = Math.min(file.details.image.width, MAX_WIDTH_ALLOWED);
491
+ const numOfParts = Math.max(2, Math.ceil(maxWidth / 500));
492
+ const widthParts = Array.from({ length: numOfParts }, (_, index) => Math.ceil((index + 1) * (maxWidth / numOfParts)));
493
+ const srcSet = sizes
494
+ ? widthParts.map((width) => `${getOptimizedImageUrl(url, width, quality, format)} ${width}w`)
495
+ : [];
496
+ const intrinsicImageWidth = file.details.image.width;
497
+ if (intrinsicImageWidth > MAX_WIDTH_ALLOWED) {
498
+ srcSet.push(`${getOptimizedImageUrl(url, undefined, quality, format)} ${intrinsicImageWidth}w`);
499
+ }
500
+ const returnedUrl = getOptimizedImageUrl(url, file.details.image.width > 2000 ? 2000 : undefined, quality, format);
501
+ const optimizedImageAsset = {
502
+ url: returnedUrl,
503
+ srcSet,
504
+ sizes,
505
+ file,
506
+ };
507
+ return optimizedImageAsset;
508
+ function validateParams(file, quality, format) {
509
+ if (!file.details.image) {
510
+ throw Error('No image in file asset to transform');
511
+ }
512
+ if (quality < 0 || quality > 100) {
513
+ throw Error('Quality must be between 0 and 100');
514
+ }
515
+ if (format && !SUPPORTED_IMAGE_FORMATS.includes(format)) {
516
+ throw Error(`Format must be one of ${SUPPORTED_IMAGE_FORMATS.join(', ')}`);
517
+ }
518
+ return true;
519
+ }
520
+ };
521
+
522
+ const transformMedia = (asset, variables, resolveDesignValue, variableName, path) => {
523
+ let value;
524
+ //TODO: this will be better served by injectable type transformers instead of if statement
525
+ if (variableName === 'cfImageAsset') {
526
+ const optionsVariableName = 'cfImageOptions';
527
+ const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
528
+ ? variables[optionsVariableName].valuesByBreakpoint
529
+ : {}, optionsVariableName);
530
+ if (!options) {
531
+ console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
532
+ return;
533
+ }
534
+ try {
535
+ value = getOptimizedImageAsset(asset.fields.file, options.targetSize, Number(options.quality), options.format);
536
+ return value;
537
+ }
538
+ catch (error) {
539
+ console.error('Error transforming image asset', error);
540
+ }
541
+ return;
542
+ }
543
+ if (variableName === 'cfBackgroundImageUrl') {
544
+ const width = resolveDesignValue(variables['cfWidth']?.type === 'DesignValue' ? variables['cfWidth'].valuesByBreakpoint : {}, 'cfWidth');
545
+ const optionsVariableName = 'cfBackgroundImageOptions';
546
+ const options = resolveDesignValue(variables[optionsVariableName]?.type === 'DesignValue'
547
+ ? variables[optionsVariableName].valuesByBreakpoint
548
+ : {}, optionsVariableName);
549
+ if (!options) {
550
+ console.error(`Error transforming image asset: Required variable [${optionsVariableName}] missing from component definition`);
551
+ return;
552
+ }
553
+ try {
554
+ value = getOptimizedBackgroundImageAsset(asset.fields.file, width, Number(options.quality), options.format);
555
+ return value;
556
+ }
557
+ catch (error) {
558
+ console.error('Error transforming image asset', error);
559
+ }
560
+ return;
561
+ }
562
+ // return getBoundValue(asset, entityStore, binding, path);
563
+ return getBoundValue(asset, path);
564
+ };
565
+
566
+ const transformBoundContentValue = (variables, entityStore, binding, resolveDesignValue, variableName, variableDefinition, path) => {
567
+ const entityOrAsset = entityStore.getEntryOrAsset(binding, path);
568
+ if (!entityOrAsset)
569
+ return;
570
+ switch (variableDefinition.type) {
571
+ case 'Media':
572
+ return transformMedia(entityOrAsset, variables, resolveDesignValue, variableName, path);
573
+ case 'RichText':
574
+ return transformRichText(entityOrAsset, path);
575
+ default:
576
+ return getBoundValue(entityOrAsset, path);
577
+ }
578
+ };
579
+
580
+ const getDataFromTree = (tree) => {
581
+ let dataSource = {};
582
+ let unboundValues = {};
583
+ const queue = [...tree.root.children];
584
+ while (queue.length) {
585
+ const node = queue.shift();
586
+ if (!node) {
587
+ continue;
588
+ }
589
+ dataSource = { ...dataSource, ...node.data.dataSource };
590
+ unboundValues = { ...unboundValues, ...node.data.unboundValues };
591
+ if (node.children.length) {
592
+ queue.push(...node.children);
593
+ }
594
+ }
595
+ return {
596
+ dataSource,
597
+ unboundValues,
598
+ };
599
+ };
600
+
601
+ const builtInStyles = {
602
+ cfVerticalAlignment: {
603
+ validations: {
604
+ in: [
605
+ {
606
+ value: 'start',
607
+ displayName: 'Align left',
608
+ },
609
+ {
610
+ value: 'center',
611
+ displayName: 'Align center',
612
+ },
613
+ {
614
+ value: 'end',
615
+ displayName: 'Align right',
616
+ },
617
+ ],
618
+ },
619
+ type: 'Text',
620
+ group: 'style',
621
+ description: 'The horizontal alignment of the section',
622
+ defaultValue: 'center',
623
+ displayName: 'Vertical alignment',
624
+ },
625
+ cfHorizontalAlignment: {
626
+ validations: {
627
+ in: [
628
+ {
629
+ value: 'start',
630
+ displayName: 'Align top',
631
+ },
632
+ {
633
+ value: 'center',
634
+ displayName: 'Align center',
635
+ },
636
+ {
637
+ value: 'end',
638
+ displayName: 'Align bottom',
639
+ },
640
+ ],
641
+ },
642
+ type: 'Text',
643
+ group: 'style',
644
+ description: 'The horizontal alignment of the section',
645
+ defaultValue: 'center',
646
+ displayName: 'Horizontal alignment',
647
+ },
648
+ cfMargin: {
649
+ displayName: 'Margin',
650
+ type: 'Text',
651
+ group: 'style',
652
+ description: 'The margin of the section',
653
+ defaultValue: '0 0 0 0',
654
+ },
655
+ cfPadding: {
656
+ displayName: 'Padding',
657
+ type: 'Text',
658
+ group: 'style',
659
+ description: 'The padding of the section',
660
+ defaultValue: '0 0 0 0',
661
+ },
662
+ cfBackgroundColor: {
663
+ displayName: 'Background color',
664
+ type: 'Text',
665
+ group: 'style',
666
+ description: 'The background color of the section',
667
+ defaultValue: 'rgba(255, 255, 255, 0)',
668
+ },
669
+ cfWidth: {
670
+ displayName: 'Width',
671
+ type: 'Text',
672
+ group: 'style',
673
+ description: 'The width of the section',
674
+ defaultValue: 'fill',
675
+ },
676
+ cfHeight: {
677
+ displayName: 'Height',
678
+ type: 'Text',
679
+ group: 'style',
680
+ description: 'The height of the section',
681
+ defaultValue: 'fit-content',
682
+ },
683
+ cfMaxWidth: {
684
+ displayName: 'Max width',
685
+ type: 'Text',
686
+ group: 'style',
687
+ description: 'The max-width of the section',
688
+ defaultValue: 'none',
689
+ },
690
+ cfFlexDirection: {
691
+ displayName: 'Direction',
692
+ type: 'Text',
693
+ group: 'style',
694
+ description: 'The orientation of the section',
695
+ defaultValue: 'column',
696
+ },
697
+ cfFlexWrap: {
698
+ displayName: 'Wrap objects',
699
+ type: 'Text',
700
+ group: 'style',
701
+ description: 'Wrap objects',
702
+ defaultValue: 'nowrap',
703
+ },
704
+ cfBorder: {
705
+ displayName: 'Border',
706
+ type: 'Text',
707
+ group: 'style',
708
+ description: 'The border of the section',
709
+ defaultValue: '0px solid rgba(0, 0, 0, 0)',
710
+ },
711
+ cfBorderRadius: {
712
+ displayName: 'Border Radius',
713
+ type: 'Text',
714
+ group: 'style',
715
+ description: 'The border radius of the section',
716
+ defaultValue: '0px',
717
+ },
718
+ cfGap: {
719
+ displayName: 'Gap',
720
+ type: 'Text',
721
+ group: 'style',
722
+ description: 'The spacing between the elements of the section',
723
+ defaultValue: '0px',
724
+ },
725
+ cfHyperlink: {
726
+ displayName: 'Hyperlink',
727
+ type: 'Text',
728
+ defaultValue: '',
729
+ validations: {
730
+ format: 'URL',
731
+ },
732
+ description: 'hyperlink for section or container',
733
+ },
734
+ cfOpenInNewTab: {
735
+ displayName: 'Hyperlink behaviour',
736
+ type: 'Boolean',
737
+ defaultValue: false,
738
+ description: 'To open hyperlink in new Tab or not',
739
+ },
740
+ };
741
+ const optionalBuiltInStyles = {
742
+ cfFontSize: {
743
+ displayName: 'Font Size',
744
+ type: 'Text',
745
+ group: 'style',
746
+ description: 'The font size of the element',
747
+ defaultValue: '16px',
748
+ },
749
+ cfFontWeight: {
750
+ validations: {
751
+ in: [
752
+ {
753
+ value: '400',
754
+ displayName: 'Normal',
755
+ },
756
+ {
757
+ value: '500',
758
+ displayName: 'Medium',
759
+ },
760
+ {
761
+ value: '600',
762
+ displayName: 'Semi Bold',
763
+ },
764
+ ],
765
+ },
766
+ displayName: 'Font Weight',
767
+ type: 'Text',
768
+ group: 'style',
769
+ description: 'The font weight of the element',
770
+ defaultValue: '400',
771
+ },
772
+ cfImageAsset: {
773
+ displayName: 'Image',
774
+ type: 'Media',
775
+ description: 'Image to display',
776
+ },
777
+ cfImageOptions: {
778
+ displayName: 'Image options',
779
+ type: 'Object',
780
+ group: 'style',
781
+ defaultValue: {
782
+ width: DEFAULT_IMAGE_WIDTH,
783
+ height: '100%',
784
+ objectFit: 'none',
785
+ objectPosition: 'center center',
786
+ quality: '100',
787
+ targetSize: DEFAULT_IMAGE_WIDTH,
788
+ },
789
+ },
790
+ cfBackgroundImageUrl: {
791
+ displayName: 'Background image',
792
+ type: 'Media',
793
+ description: 'Background image for component',
794
+ },
795
+ cfBackgroundImageOptions: {
796
+ displayName: 'Background image options',
797
+ type: 'Object',
798
+ group: 'style',
799
+ defaultValue: {
800
+ scaling: 'fill',
801
+ alignment: 'left top',
802
+ quality: '100',
803
+ targetSize: '2000px',
804
+ },
805
+ },
806
+ cfLineHeight: {
807
+ displayName: 'Line Height',
808
+ type: 'Text',
809
+ group: 'style',
810
+ description: 'The line height of the element',
811
+ defaultValue: '20px',
812
+ },
813
+ cfLetterSpacing: {
814
+ displayName: 'Letter Spacing',
815
+ type: 'Text',
816
+ group: 'style',
817
+ description: 'The letter spacing of the element',
818
+ defaultValue: '0px',
819
+ },
820
+ cfTextColor: {
821
+ displayName: 'Text Color',
822
+ type: 'Text',
823
+ group: 'style',
824
+ description: 'The text color of the element',
825
+ defaultValue: 'rgba(0, 0, 0, 1)',
826
+ },
827
+ cfTextAlign: {
828
+ validations: {
829
+ in: [
830
+ {
831
+ value: 'left',
832
+ displayName: 'Align left',
833
+ },
834
+ {
835
+ value: 'center',
836
+ displayName: 'Align center',
837
+ },
838
+ {
839
+ value: 'right',
840
+ displayName: 'Align right',
841
+ },
842
+ ],
843
+ },
844
+ displayName: 'Text Align',
845
+ type: 'Text',
846
+ group: 'style',
847
+ description: 'The text alignment of the element',
848
+ defaultValue: 'left',
849
+ },
850
+ cfTextTransform: {
851
+ validations: {
852
+ in: [
853
+ {
854
+ value: 'none',
855
+ displayName: 'Normal',
856
+ },
857
+ {
858
+ value: 'capitalize',
859
+ displayName: 'Capitalize',
860
+ },
861
+ {
862
+ value: 'uppercase',
863
+ displayName: 'Uppercase',
864
+ },
865
+ {
866
+ value: 'lowercase',
867
+ displayName: 'Lowercase',
868
+ },
869
+ ],
870
+ },
871
+ displayName: 'Text Transform',
872
+ type: 'Text',
873
+ group: 'style',
874
+ description: 'The text transform of the element',
875
+ defaultValue: 'none',
876
+ },
877
+ cfTextBold: {
878
+ displayName: 'Bold',
879
+ type: 'Boolean',
880
+ group: 'style',
881
+ description: 'The text bold of the element',
882
+ defaultValue: false,
883
+ },
884
+ cfTextItalic: {
885
+ displayName: 'Italic',
886
+ type: 'Boolean',
887
+ group: 'style',
888
+ description: 'The text italic of the element',
889
+ defaultValue: false,
890
+ },
891
+ cfTextUnderline: {
892
+ displayName: 'Underline',
893
+ type: 'Boolean',
894
+ group: 'style',
895
+ description: 'The text underline of the element',
896
+ defaultValue: false,
897
+ },
898
+ };
899
+
900
+ const designTokensRegistry = {};
901
+ /**
902
+ * Register design tokens styling
903
+ * @param designTokenDefinition - {[key:string]: Record<string, string>}
904
+ * @returns void
905
+ */
906
+ const defineDesignTokens = (designTokenDefinition) => {
907
+ Object.assign(designTokensRegistry, designTokenDefinition);
908
+ };
909
+ const templateStringRegex = /\${(.+?)}/g;
910
+ const getDesignTokenRegistration = (breakpointValue, variableName) => {
911
+ if (!breakpointValue)
912
+ return breakpointValue;
913
+ let resolvedValue = '';
914
+ for (const part of breakpointValue.split(' ')) {
915
+ const tokenValue = templateStringRegex.test(part)
916
+ ? resolveSimpleDesignToken(part, variableName)
917
+ : part;
918
+ resolvedValue += `${tokenValue} `;
919
+ }
920
+ // Not trimming would end up with a trailing space that breaks the check in `calculateNodeDefaultHeight`
921
+ return resolvedValue.trim();
922
+ };
923
+ const resolveSimpleDesignToken = (templateString, variableName) => {
924
+ const nonTemplateValue = templateString.replace(templateStringRegex, '$1');
925
+ const [tokenCategory, tokenName] = nonTemplateValue.split('.');
926
+ const tokenValues = designTokensRegistry[tokenCategory];
927
+ if (tokenValues && tokenValues[tokenName]) {
928
+ if (variableName === 'cfBorder') {
929
+ const { width, style, color } = tokenValues[tokenName];
930
+ return `${width} ${style} ${color}`;
931
+ }
932
+ return tokenValues[tokenName];
933
+ }
934
+ if (builtInStyles[variableName]) {
935
+ return builtInStyles[variableName].defaultValue;
936
+ }
937
+ if (optionalBuiltInStyles[variableName]) {
938
+ return optionalBuiltInStyles[variableName].defaultValue;
939
+ }
940
+ return '0px';
941
+ };
942
+
943
+ const MEDIA_QUERY_REGEXP = /(<|>)(\d{1,})(px|cm|mm|in|pt|pc)$/;
944
+ const toCSSMediaQuery = ({ query }) => {
945
+ if (query === '*')
946
+ return undefined;
947
+ const match = query.match(MEDIA_QUERY_REGEXP);
948
+ if (!match)
949
+ return undefined;
950
+ const [, operator, value, unit] = match;
951
+ if (operator === '<') {
952
+ const maxScreenWidth = Number(value) - 1;
953
+ return `(max-width: ${maxScreenWidth}${unit})`;
954
+ }
955
+ else if (operator === '>') {
956
+ const minScreenWidth = Number(value) + 1;
957
+ return `(min-width: ${minScreenWidth}${unit})`;
958
+ }
959
+ return undefined;
960
+ };
961
+ // Remove this helper when upgrading to TypeScript 5.0 - https://github.com/microsoft/TypeScript/issues/48829
962
+ const findLast = (array, predicate) => {
963
+ return array.reverse().find(predicate);
964
+ };
965
+ // Initialise media query matchers. This won't include the always matching fallback breakpoint.
966
+ const mediaQueryMatcher = (breakpoints) => {
967
+ const mediaQueryMatches = {};
968
+ const mediaQueryMatchers = breakpoints
969
+ .map((breakpoint) => {
970
+ const cssMediaQuery = toCSSMediaQuery(breakpoint);
971
+ if (!cssMediaQuery)
972
+ return undefined;
973
+ if (typeof window === 'undefined')
974
+ return undefined;
975
+ const mediaQueryMatcher = window.matchMedia(cssMediaQuery);
976
+ mediaQueryMatches[breakpoint.id] = mediaQueryMatcher.matches;
977
+ return { id: breakpoint.id, signal: mediaQueryMatcher };
978
+ })
979
+ .filter((matcher) => !!matcher);
980
+ return [mediaQueryMatchers, mediaQueryMatches];
981
+ };
982
+ const getActiveBreakpointIndex = (breakpoints, mediaQueryMatches, fallbackBreakpointIndex) => {
983
+ // The breakpoints are ordered (desktop-first: descending by screen width)
984
+ const breakpointsWithMatches = breakpoints.map(({ id }, index) => ({
985
+ id,
986
+ index,
987
+ // The fallback breakpoint with wildcard query will always match
988
+ isMatch: mediaQueryMatches[id] ?? index === fallbackBreakpointIndex,
989
+ }));
990
+ // Find the last breakpoint in the list that matches (desktop-first: the narrowest one)
991
+ const mostSpecificIndex = findLast(breakpointsWithMatches, ({ isMatch }) => isMatch)?.index;
992
+ return mostSpecificIndex ?? fallbackBreakpointIndex;
993
+ };
994
+ const getFallbackBreakpointIndex = (breakpoints) => {
995
+ // We assume that there will be a single breakpoint which uses the wildcard query.
996
+ // If there is none, we just take the first one in the list.
997
+ return Math.max(breakpoints.findIndex(({ query }) => query === '*'), 0);
998
+ };
999
+ const builtInStylesWithDesignTokens = [
1000
+ 'cfMargin',
1001
+ 'cfPadding',
1002
+ 'cfGap',
1003
+ 'cfWidth',
1004
+ 'cfHeight',
1005
+ 'cfBackgroundColor',
1006
+ 'cfBorder',
1007
+ 'cfBorderRadius',
1008
+ 'cfFontSize',
1009
+ 'cfLineHeight',
1010
+ 'cfLetterSpacing',
1011
+ 'cfTextColor',
1012
+ ];
1013
+ const getValueForBreakpoint = (valuesByBreakpoint, breakpoints, activeBreakpointIndex, variableName) => {
1014
+ const eventuallyResolveDesignTokens = (value) => {
1015
+ // For some built-in design propertier, we support design tokens
1016
+ if (builtInStylesWithDesignTokens.includes(variableName)) {
1017
+ return getDesignTokenRegistration(value, variableName);
1018
+ }
1019
+ // For all other properties, we just return the breakpoint-specific value
1020
+ return value;
1021
+ };
1022
+ if (valuesByBreakpoint instanceof Object) {
1023
+ // Assume that the values are sorted by media query to apply the cascading CSS logic
1024
+ for (let index = activeBreakpointIndex; index >= 0; index--) {
1025
+ const breakpointId = breakpoints[index].id;
1026
+ if (valuesByBreakpoint[breakpointId]) {
1027
+ // If the value is defined, we use it and stop the breakpoints cascade
1028
+ return eventuallyResolveDesignTokens(valuesByBreakpoint[breakpointId]);
1029
+ }
1030
+ }
1031
+ // If no breakpoint matched, we search and apply the fallback breakpoint
1032
+ const fallbackBreakpointIndex = getFallbackBreakpointIndex(breakpoints);
1033
+ const fallbackBreakpointId = breakpoints[fallbackBreakpointIndex].id;
1034
+ return eventuallyResolveDesignTokens(valuesByBreakpoint[fallbackBreakpointId]);
1035
+ }
1036
+ else {
1037
+ // Old design properties did not support breakpoints, keep for backward compatibility
1038
+ return valuesByBreakpoint;
1039
+ }
1040
+ };
1041
+
1042
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1043
+ const isLinkToAsset = (variable) => {
1044
+ if (!variable)
1045
+ return false;
1046
+ if (typeof variable !== 'object')
1047
+ return false;
1048
+ return (variable.sys?.linkType === 'Asset' &&
1049
+ typeof variable.sys?.id === 'string' &&
1050
+ !!variable.sys?.id &&
1051
+ variable.sys?.type === 'Link');
1052
+ };
1053
+
1054
+ const isLink = (maybeLink) => {
1055
+ if (maybeLink === null)
1056
+ return false;
1057
+ if (typeof maybeLink !== 'object')
1058
+ return false;
1059
+ const link = maybeLink;
1060
+ return Boolean(link.sys?.id) && link.sys?.type === 'Link';
1061
+ };
1062
+
1063
+ /**
1064
+ * This module encapsulates format of the path to a deep reference.
1065
+ */
1066
+ const parseDataSourcePathIntoFieldset = (path) => {
1067
+ const parsedPath = parseDeepPath(path);
1068
+ if (null === parsedPath) {
1069
+ throw new Error(`Cannot parse path '${path}' as deep path`);
1070
+ }
1071
+ return parsedPath.fields.map((field) => [null, field, '~locale']);
1072
+ };
1073
+ /**
1074
+ * Parse path into components, supports L1 references (one reference follow) atm.
1075
+ * @param path from data source. eg. `/uuid123/fields/image/~locale/fields/file/~locale`
1076
+ * eg. `/uuid123/fields/file/~locale/fields/title/~locale`
1077
+ * @returns
1078
+ */
1079
+ const parseDataSourcePathWithL1DeepBindings = (path) => {
1080
+ const parsedPath = parseDeepPath(path);
1081
+ if (null === parsedPath) {
1082
+ throw new Error(`Cannot parse path '${path}' as deep path`);
1083
+ }
1084
+ return {
1085
+ key: parsedPath.key,
1086
+ field: parsedPath.fields[0],
1087
+ referentField: parsedPath.fields[1],
1088
+ };
1089
+ };
1090
+ /**
1091
+ * Detects if paths is valid deep-path, like:
1092
+ * - /gV6yKXp61hfYrR7rEyKxY/fields/mainStory/~locale/fields/cover/~locale/fields/title/~locale
1093
+ * or regular, like:
1094
+ * - /6J8eA60yXwdm5eyUh9fX6/fields/mainStory/~locale
1095
+ * @returns
1096
+ */
1097
+ const isDeepPath = (deepPathCandidate) => {
1098
+ const deepPathParsed = parseDeepPath(deepPathCandidate);
1099
+ if (!deepPathParsed) {
1100
+ return false;
1101
+ }
1102
+ return deepPathParsed.fields.length > 1;
1103
+ };
1104
+ const parseDeepPath = (deepPathCandidate) => {
1105
+ // ALGORITHM:
1106
+ // We start with deep path in form:
1107
+ // /uuid123/fields/mainStory/~locale/fields/cover/~locale/fields/title/~locale
1108
+ // First turn string into array of segments
1109
+ // ['', 'uuid123', 'fields', 'mainStory', '~locale', 'fields', 'cover', '~locale', 'fields', 'title', '~locale']
1110
+ // Then group segments into intermediate represenatation - chunks, where each non-initial chunk starts with 'fields'
1111
+ // [
1112
+ // [ "", "uuid123" ],
1113
+ // [ "fields", "mainStory", "~locale" ],
1114
+ // [ "fields", "cover", "~locale" ],
1115
+ // [ "fields", "title", "~locale" ]
1116
+ // ]
1117
+ // Then check "initial" chunk for corretness
1118
+ // Then check all "field-leading" chunks for correctness
1119
+ const isValidInitialChunk = (initialChunk) => {
1120
+ // must have start with '' and have at least 2 segments, second non-empty
1121
+ // eg. /-_432uuid123123
1122
+ return /^\/([^/^~]+)$/.test(initialChunk.join('/'));
1123
+ };
1124
+ const isValidFieldChunk = (fieldChunk) => {
1125
+ // must start with 'fields' and have at least 3 segments, second non-empty and last segment must be '~locale'
1126
+ // eg. fields/-32234mainStory/~locale
1127
+ return /^fields\/[^/^~]+\/~locale$/.test(fieldChunk.join('/'));
1128
+ };
1129
+ const deepPathSegments = deepPathCandidate.split('/');
1130
+ const chunks = chunkSegments(deepPathSegments, { startNextChunkOnElementEqualTo: 'fields' });
1131
+ if (chunks.length <= 1) {
1132
+ return null; // malformed path, even regular paths have at least 2 chunks
1133
+ }
1134
+ else if (chunks.length === 2) {
1135
+ return null; // deep paths have at least 3 chunks
1136
+ }
1137
+ // With 3+ chunks we can now check for deep path correctness
1138
+ const [initialChunk, ...fieldChunks] = chunks;
1139
+ if (!isValidInitialChunk(initialChunk)) {
1140
+ return null;
1141
+ }
1142
+ if (!fieldChunks.every(isValidFieldChunk)) {
1143
+ return null;
1144
+ }
1145
+ return {
1146
+ key: initialChunk[1], // pick uuid from initial chunk ['','uuid123'],
1147
+ fields: fieldChunks.map((fieldChunk) => fieldChunk[1]), // pick only fieldName eg. from ['fields','mainStory', '~locale'] we pick `mainStory`
1148
+ };
1149
+ };
1150
+ const chunkSegments = (segments, { startNextChunkOnElementEqualTo }) => {
1151
+ const chunks = [];
1152
+ let currentChunk = [];
1153
+ const isSegmentBeginningOfChunk = (segment) => segment === startNextChunkOnElementEqualTo;
1154
+ const excludeEmptyChunks = (chunk) => chunk.length > 0;
1155
+ for (let i = 0; i < segments.length; i++) {
1156
+ const isInitialElement = i === 0;
1157
+ const segment = segments[i];
1158
+ if (isInitialElement) {
1159
+ currentChunk = [segment];
1160
+ }
1161
+ else if (isSegmentBeginningOfChunk(segment)) {
1162
+ chunks.push(currentChunk);
1163
+ currentChunk = [segment];
1164
+ }
1165
+ else {
1166
+ currentChunk.push(segment);
1167
+ }
1168
+ }
1169
+ chunks.push(currentChunk);
1170
+ return chunks.filter(excludeEmptyChunks);
1171
+ };
1172
+
1173
+ const sendMessage = (eventType, data) => {
1174
+ if (typeof window === 'undefined') {
1175
+ return;
1176
+ }
1177
+ console.debug(`[experiences-sdk-react::sendMessage] Sending message [${eventType}]`, {
1178
+ source: 'customer-app',
1179
+ eventType,
1180
+ payload: data,
1181
+ });
1182
+ window.parent?.postMessage({
1183
+ source: 'customer-app',
1184
+ eventType,
1185
+ payload: data,
1186
+ }, '*');
1187
+ };
1188
+
1189
+ /**
1190
+ * Base Store for entities
1191
+ * Can be extended for the different loading behaviours (editor, production, ..)
1192
+ */
1193
+ class EntityStoreBase {
1194
+ constructor({ entities, locale }) {
1195
+ this.entryMap = new Map();
1196
+ this.assetMap = new Map();
1197
+ this.locale = locale;
1198
+ for (const entity of entities) {
1199
+ this.addEntity(entity);
1200
+ }
1201
+ }
1202
+ get entities() {
1203
+ return [...this.entryMap.values(), ...this.assetMap.values()];
1204
+ }
1205
+ updateEntity(entity) {
1206
+ this.addEntity(entity);
1207
+ }
1208
+ getEntryOrAsset(linkOrEntryOrAsset, path) {
1209
+ if (isDeepPath(path)) {
1210
+ return this.getDeepEntry(linkOrEntryOrAsset, path);
1211
+ }
1212
+ let entity;
1213
+ if (isLink(linkOrEntryOrAsset)) {
1214
+ const resolvedEntity = linkOrEntryOrAsset.sys.linkType === 'Entry'
1215
+ ? this.entryMap.get(linkOrEntryOrAsset.sys.id)
1216
+ : this.assetMap.get(linkOrEntryOrAsset.sys.id);
1217
+ if (!resolvedEntity || resolvedEntity.sys.type !== linkOrEntryOrAsset.sys.linkType) {
1218
+ console.warn(`Experience references unresolved entity: ${JSON.stringify(linkOrEntryOrAsset)}`);
1219
+ return;
1220
+ }
1221
+ entity = resolvedEntity;
1222
+ }
1223
+ else {
1224
+ // We already have the complete entity in preview & delivery (resolved by the CMA client)
1225
+ entity = linkOrEntryOrAsset;
1226
+ }
1227
+ return entity;
1228
+ }
1229
+ /**
1230
+ * @deprecated in the base class this should be simply an abstract method
1231
+ * @param entityLink
1232
+ * @param path
1233
+ * @returns
1234
+ */
1235
+ getValue(entityLink, path) {
1236
+ const entity = this.getEntity(entityLink.sys.linkType, entityLink.sys.id);
1237
+ if (!entity) {
1238
+ // TODO: move to `debug` utils once it is extracted
1239
+ console.warn(`Unresolved entity reference: ${entityLink.sys.linkType} with ID ${entityLink.sys.id}`);
1240
+ return;
1241
+ }
1242
+ return get(entity, path);
1243
+ }
1244
+ getEntityFromLink(link) {
1245
+ const resolvedEntity = link.sys.linkType === 'Entry'
1246
+ ? this.entryMap.get(link.sys.id)
1247
+ : this.assetMap.get(link.sys.id);
1248
+ if (!resolvedEntity || resolvedEntity.sys.type !== link.sys.linkType) {
1249
+ console.warn(`Experience references unresolved entity: ${JSON.stringify(link)}`);
1250
+ return;
1251
+ }
1252
+ return resolvedEntity;
1253
+ }
1254
+ getEntitiesFromMap(type, ids) {
1255
+ const resolved = [];
1256
+ const missing = [];
1257
+ for (const id of ids) {
1258
+ const entity = this.getEntity(type, id);
1259
+ if (entity) {
1260
+ resolved.push(entity);
1261
+ }
1262
+ else {
1263
+ missing.push(id);
1264
+ }
1265
+ }
1266
+ return {
1267
+ resolved,
1268
+ missing,
1269
+ };
1270
+ }
1271
+ addEntity(entity) {
1272
+ if (this.isAsset(entity)) {
1273
+ this.assetMap.set(entity.sys.id, entity);
1274
+ }
1275
+ else {
1276
+ this.entryMap.set(entity.sys.id, entity);
1277
+ }
1278
+ }
1279
+ async fetchAsset(id) {
1280
+ const { resolved, missing } = this.getEntitiesFromMap('Asset', [id]);
1281
+ if (missing.length) {
1282
+ // TODO: move to `debug` utils once it is extracted
1283
+ console.warn(`Asset "${id}" is not in the store`);
1284
+ return;
1285
+ }
1286
+ return resolved[0];
1287
+ }
1288
+ async fetchAssets(ids) {
1289
+ const { resolved, missing } = this.getEntitiesFromMap('Asset', ids);
1290
+ if (missing.length) {
1291
+ throw new Error(`Missing assets in the store (${missing.join(',')})`);
1292
+ }
1293
+ return resolved;
1294
+ }
1295
+ async fetchEntry(id) {
1296
+ const { resolved, missing } = this.getEntitiesFromMap('Entry', [id]);
1297
+ if (missing.length) {
1298
+ // TODO: move to `debug` utils once it is extracted
1299
+ console.warn(`Entry "${id}" is not in the store`);
1300
+ return;
1301
+ }
1302
+ return resolved[0];
1303
+ }
1304
+ async fetchEntries(ids) {
1305
+ const { resolved, missing } = this.getEntitiesFromMap('Entry', ids);
1306
+ if (missing.length) {
1307
+ throw new Error(`Missing assets in the store (${missing.join(',')})`);
1308
+ }
1309
+ return resolved;
1310
+ }
1311
+ getDeepEntry(linkOrEntryOrAsset, path) {
1312
+ const resolveFieldset = (unresolvedFieldset, headEntry) => {
1313
+ const resolvedFieldset = [];
1314
+ let entityToResolveFieldsFrom = headEntry;
1315
+ for (let i = 0; i < unresolvedFieldset.length; i++) {
1316
+ const isLeaf = i === unresolvedFieldset.length - 1; // with last row, we are not expecting a link, but a value
1317
+ const row = unresolvedFieldset[i];
1318
+ const [, field, _localeQualifier] = row;
1319
+ if (!entityToResolveFieldsFrom) {
1320
+ throw new Error(`Logic Error: Cannot resolve field ${field} of a fieldset as there is no entity to resolve it from.`);
1321
+ }
1322
+ if (isLeaf) {
1323
+ resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
1324
+ break;
1325
+ }
1326
+ const fieldValue = get(entityToResolveFieldsFrom, ['fields', field]);
1327
+ if (undefined === fieldValue) {
1328
+ return {
1329
+ resolvedFieldset,
1330
+ isFullyResolved: false,
1331
+ reason: `Cannot resolve field Link<${entityToResolveFieldsFrom.sys.type}>(sys.id=${entityToResolveFieldsFrom.sys.id}).fields[${field}] as field value is not defined`,
1332
+ };
1333
+ }
1334
+ else if (isLink(fieldValue)) {
1335
+ const entity = this.getEntityFromLink(fieldValue);
1336
+ if (entity === undefined) {
1337
+ return {
1338
+ resolvedFieldset,
1339
+ isFullyResolved: false,
1340
+ reason: `Field reference Link (sys.id=${fieldValue.sys.id}) not found in the EntityStore, waiting...`,
1341
+ };
1342
+ }
1343
+ resolvedFieldset.push([entityToResolveFieldsFrom, field, _localeQualifier]);
1344
+ entityToResolveFieldsFrom = entity; // we move up
1345
+ }
1346
+ else {
1347
+ // TODO: Eg. when someone changed the schema and the field is not a link anymore, what should we return then?
1348
+ throw new Error(`LogicError: Invalid value of a field we consider a reference field. Cannot resolve field ${field} of a fieldset as it is not a link, neither undefined.`);
1349
+ }
1350
+ }
1351
+ return {
1352
+ resolvedFieldset,
1353
+ isFullyResolved: true,
1354
+ };
1355
+ };
1356
+ const headEntity = isLink(linkOrEntryOrAsset)
1357
+ ? this.getEntityFromLink(linkOrEntryOrAsset)
1358
+ : linkOrEntryOrAsset;
1359
+ if (undefined === headEntity) {
1360
+ return;
1361
+ }
1362
+ const unresolvedFieldset = parseDataSourcePathIntoFieldset(path);
1363
+ // The purpose here is to take this intermediate representation of the deep-path
1364
+ // and to follow the links to the leaf-entity and field
1365
+ // in case we can't follow till the end, we should signal that there was null-reference in the path
1366
+ const { resolvedFieldset, isFullyResolved, reason } = resolveFieldset(unresolvedFieldset, headEntity);
1367
+ if (!isFullyResolved) {
1368
+ reason &&
1369
+ console.debug(`[exp-builder.sdk::EntityStoreBased::getValueDeep()] Deep path wasn't resolved till leaf node, falling back to undefined, because: ${reason}`);
1370
+ return;
1371
+ }
1372
+ const [leafEntity] = resolvedFieldset[resolvedFieldset.length - 1];
1373
+ return leafEntity;
1374
+ }
1375
+ isAsset(entity) {
1376
+ return entity.sys.type === 'Asset';
1377
+ }
1378
+ getEntity(type, id) {
1379
+ if (type === 'Asset') {
1380
+ return this.assetMap.get(id);
1381
+ }
1382
+ return this.entryMap.get(id);
1383
+ }
1384
+ toJSON() {
1385
+ return {
1386
+ entryMap: Object.fromEntries(this.entryMap),
1387
+ assetMap: Object.fromEntries(this.assetMap),
1388
+ locale: this.locale,
1389
+ };
1390
+ }
1391
+ }
1392
+
1393
+ /**
1394
+ * EntityStore which resolves entries and assets from the editor
1395
+ * over the sendMessage and subscribe functions.
1396
+ */
1397
+ class EditorEntityStore extends EntityStoreBase {
1398
+ constructor({ entities, locale, sendMessage, subscribe, timeoutDuration = 3000, }) {
1399
+ super({ entities, locale });
1400
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
1401
+ this.requestCache = new Map();
1402
+ this.cacheIdSeperator = ',';
1403
+ this.sendMessage = sendMessage;
1404
+ this.subscribe = subscribe;
1405
+ this.timeoutDuration = timeoutDuration;
1406
+ }
1407
+ cleanupPromise(referenceId) {
1408
+ setTimeout(() => {
1409
+ this.requestCache.delete(referenceId);
1410
+ }, 300);
1411
+ }
1412
+ getCacheId(id) {
1413
+ return id.length === 1 ? id[0] : id.join(this.cacheIdSeperator);
1414
+ }
1415
+ async fetchEntity(type, ids, skipCache = false) {
1416
+ let missing;
1417
+ if (!skipCache) {
1418
+ const { missing: missingFromCache, resolved } = this.getEntitiesFromMap(type, ids);
1419
+ if (missingFromCache.length === 0) {
1420
+ // everything is already in cache
1421
+ return resolved;
1422
+ }
1423
+ missing = missingFromCache;
1424
+ }
1425
+ else {
1426
+ missing = [...ids];
1427
+ }
1428
+ const cacheId = this.getCacheId(missing);
1429
+ const openRequest = this.requestCache.get(cacheId);
1430
+ if (openRequest) {
1431
+ return openRequest;
1432
+ }
1433
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
1434
+ const newPromise = new Promise((resolve, reject) => {
1435
+ const unsubscribe = this.subscribe(PostMessageMethods$2.REQUESTED_ENTITIES, (message) => {
1436
+ const messageIds = [
1437
+ ...message.entities.map((entity) => entity.sys.id),
1438
+ ...(message.missingEntityIds ?? []),
1439
+ ];
1440
+ if (missing.every((id) => messageIds.find((entityId) => entityId === id))) {
1441
+ clearTimeout(timeout);
1442
+ resolve(message.entities);
1443
+ this.cleanupPromise(cacheId);
1444
+ ids.forEach((id) => this.cleanupPromise(id));
1445
+ unsubscribe();
1446
+ }
1447
+ else {
1448
+ console.warn('Unexpected entities received in REQUESTED_ENTITIES. Ignoring this response.');
1449
+ }
1450
+ });
1451
+ const timeout = setTimeout(() => {
1452
+ reject(new Error(`Request for entities timed out ${this.timeoutDuration}ms} for ${cacheId}`));
1453
+ this.cleanupPromise(cacheId);
1454
+ ids.forEach((id) => this.cleanupPromise(id));
1455
+ unsubscribe();
1456
+ }, this.timeoutDuration);
1457
+ this.sendMessage(PostMessageMethods$2.REQUEST_ENTITIES, {
1458
+ entityIds: missing,
1459
+ entityType: type,
1460
+ locale: this.locale,
1461
+ });
1462
+ });
1463
+ this.requestCache.set(cacheId, newPromise);
1464
+ ids.forEach((cid) => {
1465
+ this.requestCache.set(cid, newPromise);
1466
+ });
1467
+ const result = (await newPromise);
1468
+ result.forEach((value) => {
1469
+ this.addEntity(value);
1470
+ });
1471
+ return this.getEntitiesFromMap(type, ids).resolved;
1472
+ }
1473
+ async fetchAsset(id, skipCache = false) {
1474
+ try {
1475
+ return (await this.fetchAssets([id], skipCache))[0];
1476
+ }
1477
+ catch (err) {
1478
+ // TODO: move to debug utils once it is extracted
1479
+ console.warn(`Failed to request asset ${id}`);
1480
+ return undefined;
1481
+ }
1482
+ }
1483
+ fetchAssets(ids, skipCache = false) {
1484
+ return this.fetchEntity('Asset', ids, skipCache);
1485
+ }
1486
+ async fetchEntry(id, skipCache = false) {
1487
+ try {
1488
+ return (await this.fetchEntries([id], skipCache))[0];
1489
+ }
1490
+ catch (err) {
1491
+ // TODO: move to debug utils once it is extracted
1492
+ console.warn(`Failed to request entry ${id}`, err);
1493
+ return undefined;
1494
+ }
1495
+ }
1496
+ fetchEntries(ids, skipCache = false) {
1497
+ return this.fetchEntity('Entry', ids, skipCache);
1498
+ }
1499
+ }
1500
+
1501
+ // The default of 3s in the EditorEntityStore is sometimes timing out and
1502
+ // leads to not rendering bound content and assemblies.
1503
+ const REQUEST_TIMEOUT = 10000;
1504
+ class EditorModeEntityStore extends EditorEntityStore {
1505
+ constructor({ entities, locale }) {
1506
+ console.debug(`[experiences-sdk-react] Initializing editor entity store with ${entities.length} entities for locale ${locale}.`, { entities });
1507
+ const subscribe = (method, cb) => {
1508
+ const handleMessage = (event) => {
1509
+ const data = JSON.parse(event.data);
1510
+ if (typeof data !== 'object' || !data)
1511
+ return;
1512
+ if (data.source !== 'composability-app')
1513
+ return;
1514
+ if (data.eventType === method) {
1515
+ cb(data.payload);
1516
+ }
1517
+ };
1518
+ if (typeof window !== 'undefined') {
1519
+ window.addEventListener('message', handleMessage);
1520
+ }
1521
+ return () => {
1522
+ if (typeof window !== 'undefined') {
1523
+ window.removeEventListener('message', handleMessage);
1524
+ }
1525
+ };
1526
+ };
1527
+ super({ entities, sendMessage, subscribe, locale, timeoutDuration: REQUEST_TIMEOUT });
1528
+ this.locale = locale;
1529
+ }
1530
+ /**
1531
+ * This function collects and returns the list of requested entries and assets. Additionally, it checks
1532
+ * upfront whether any async fetching logic is actually happening. If not, it returns a plain `false` value, so we
1533
+ * can detect this early and avoid unnecessary re-renders.
1534
+ * @param entityLinks
1535
+ * @returns false if no async fetching is happening, otherwise a promise that resolves when all entities are fetched
1536
+ */
1537
+ async fetchEntities({ missingEntryIds, missingAssetIds, skipCache = false, }) {
1538
+ // Entries and assets will be stored in entryMap and assetMap
1539
+ await Promise.all([
1540
+ this.fetchEntries(missingEntryIds, skipCache),
1541
+ this.fetchAssets(missingAssetIds, skipCache),
1542
+ ]);
1543
+ }
1544
+ getMissingEntityIds(entityLinks) {
1545
+ const entryLinks = entityLinks.filter((link) => link.sys?.linkType === 'Entry');
1546
+ const assetLinks = entityLinks.filter((link) => link.sys?.linkType === 'Asset');
1547
+ const uniqueEntryIds = [...new Set(entryLinks.map((link) => link.sys.id))];
1548
+ const uniqueAssetIds = [...new Set(assetLinks.map((link) => link.sys.id))];
1549
+ const { missing: missingEntryIds } = this.getEntitiesFromMap('Entry', uniqueEntryIds);
1550
+ const { missing: missingAssetIds } = this.getEntitiesFromMap('Asset', uniqueAssetIds);
1551
+ return { missingEntryIds, missingAssetIds };
1552
+ }
1553
+ getValue(entityLinkOrEntity, path) {
1554
+ const entity = this.getEntryOrAsset(entityLinkOrEntity, path.join('/'));
1555
+ if (!entity) {
1556
+ return;
1557
+ }
1558
+ const fieldValue = get(entity, path);
1559
+ // walk around to render asset files
1560
+ return fieldValue && typeof fieldValue == 'object' && fieldValue.url
1561
+ ? fieldValue.url
1562
+ : fieldValue;
1563
+ }
1564
+ }
1565
+
1566
+ var VisualEditorMode;
1567
+ (function (VisualEditorMode) {
1568
+ VisualEditorMode["LazyLoad"] = "lazyLoad";
1569
+ VisualEditorMode["InjectScript"] = "injectScript";
1570
+ })(VisualEditorMode || (VisualEditorMode = {}));
1571
+
1572
+ function treeVisit$1(initialNode, onNode) {
1573
+ // returns last used index
1574
+ const _treeVisit = (currentNode, currentIndex, currentDepth) => {
1575
+ // Copy children in case of onNode removing it as we pass the node by reference
1576
+ const children = [...currentNode.children];
1577
+ onNode(currentNode, currentIndex, currentDepth);
1578
+ let nextAvailableIndex = currentIndex + 1;
1579
+ const lastUsedIndex = currentIndex;
1580
+ for (const child of children) {
1581
+ const lastUsedIndex = _treeVisit(child, nextAvailableIndex, currentDepth + 1);
1582
+ nextAvailableIndex = lastUsedIndex + 1;
1583
+ }
1584
+ return lastUsedIndex;
1585
+ };
1586
+ _treeVisit(initialNode, 0, 0);
1587
+ }
1588
+
1589
+ class DeepReference {
1590
+ constructor({ path, dataSource }) {
1591
+ const { key, field, referentField } = parseDataSourcePathWithL1DeepBindings(path);
1592
+ this.originalPath = path;
1593
+ this.entityId = dataSource[key].sys.id;
1594
+ this.entityLink = dataSource[key];
1595
+ this.field = field;
1596
+ this.referentField = referentField;
1597
+ }
1598
+ get headEntityId() {
1599
+ return this.entityId;
1600
+ }
1601
+ /**
1602
+ * Extracts referent from the path, using EntityStore as source of
1603
+ * entities during the resolution path.
1604
+ * TODO: should it be called `extractLeafReferent` ? or `followToLeafReferent`
1605
+ */
1606
+ extractReferent(entityStore) {
1607
+ const headEntity = entityStore.getEntityFromLink(this.entityLink);
1608
+ const maybeReferentLink = headEntity.fields[this.field];
1609
+ if (undefined === maybeReferentLink) {
1610
+ // field references nothing (or even field doesn't exist)
1611
+ return undefined;
1612
+ }
1613
+ if (!isLink(maybeReferentLink)) {
1614
+ // Scenario of "impostor referent", where one of the deepPath's segments is not a Link but some other type
1615
+ // Under normal circumstance we expect field to be a Link, but it could be an "impostor"
1616
+ // eg. `Text` or `Number` or anything like that; could be due to CT changes or manual path creation via CMA
1617
+ return undefined;
1618
+ }
1619
+ return maybeReferentLink;
1620
+ }
1621
+ static from(opt) {
1622
+ return new DeepReference(opt);
1623
+ }
1624
+ }
1625
+ function gatherDeepReferencesFromTree(startingNode, dataSource) {
1626
+ const deepReferences = [];
1627
+ treeVisit$1(startingNode, (node) => {
1628
+ if (!node.data.props)
1629
+ return;
1630
+ for (const [, variableMapping] of Object.entries(node.data.props)) {
1631
+ if (variableMapping.type !== 'BoundValue')
1632
+ continue;
1633
+ if (!isDeepPath(variableMapping.path))
1634
+ continue;
1635
+ deepReferences.push(DeepReference.from({
1636
+ path: variableMapping.path,
1637
+ dataSource,
1638
+ }));
1639
+ }
1640
+ });
1641
+ return deepReferences;
1642
+ }
1643
+
1644
+ var css_248z$7 = ".styles-module_DraggableComponent__m5-dA {\n pointer-events: all;\n position: relative;\n transition: outline 0.2s;\n cursor: grab;\n box-sizing: border-box;\n display: flex;\n}\n\n.styles-module_DraggableComponent__m5-dA: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 pointer-events: none;\n}\n\n.styles-module_DraggableClone__X8zTA:before {\n outline: 2px solid var(--exp-builder-blue500);\n}\n\n.styles-module_DraggableClone__X8zTA,\n.styles-module_DraggableClone__X8zTA * {\n pointer-events: none !important;\n}\n\n.styles-module_DraggableComponent__m5-dA:not(.styles-module_userIsDragging__lqbjG) :not(.styles-module_DraggableComponent__m5-dA) {\n pointer-events: none;\n}\n\n.styles-module_isDragging__WHjPU {\n overflow: hidden;\n}\n\n.styles-module_isSelected__BzICQ:before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_overlay__r4th9 {\n position: absolute;\n display: flex;\n align-items: center;\n min-width: max-content;\n height: 24px;\n z-index: 1;\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 border-radius: 0 0 2px 0;\n padding: 4px 12px 4px 12px;\n transition: opacity 0.2s;\n opacity: 0;\n text-wrap: nowrap;\n}\n\n.styles-module_overlayContainer__eiX-5 {\n opacity: 0;\n}\n\n.styles-module_overlayAssembly__tOzZU {\n background-color: var(--exp-builder-purple600);\n}\n\n.styles-module_userIsDragging__lqbjG > .styles-module_overlay__r4th9,\n.styles-module_userIsDragging__lqbjG > .styles-module_overlayContainer__eiX-5 {\n opacity: 0 !important;\n}\n\n.styles-module_userIsDragging__lqbjG:before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_DraggableComponent__m5-dA:hover:not(:has(div[data-rfd-draggable-id]:hover)) > .styles-module_overlay__r4th9 {\n opacity: 1;\n}\n\n.styles-module_DraggableComponent__m5-dA:hover:before,\n.styles-module_DraggableComponent__m5-dA:hover div[data-rfd-draggable-id]:before {\n outline: 2px dashed var(--exp-builder-gray500);\n}\n\n.styles-module_DraggableComponent__m5-dA:hover:not(:has(div[data-rfd-draggable-id]:hover)):before {\n outline: 2px solid var(--exp-builder-gray500);\n}\n\n.styles-module_isAssemblyBlock__Y3Avk:hover:before,\n.styles-module_isAssemblyBlock__Y3Avk:hover div[data-rfd-draggable-id]:before,\n.styles-module_DraggableComponent__m5-dA:hover div[data-rfd-draggable-id][data-cf-node-block-type^='assembly']:before {\n outline: 2px dashed var(--exp-builder-purple600);\n}\n\n.styles-module_isAssemblyBlock__Y3Avk:hover:not(:has(div[data-rfd-draggable-id]:hover)):before {\n outline: 2px solid var(--exp-builder-purple600);\n}\n";
1645
+ var styles$3 = {"DraggableComponent":"styles-module_DraggableComponent__m5-dA","DraggableClone":"styles-module_DraggableClone__X8zTA","userIsDragging":"styles-module_userIsDragging__lqbjG","isDragging":"styles-module_isDragging__WHjPU","isSelected":"styles-module_isSelected__BzICQ","overlay":"styles-module_overlay__r4th9","overlayContainer":"styles-module_overlayContainer__eiX-5","overlayAssembly":"styles-module_overlayAssembly__tOzZU","isAssemblyBlock":"styles-module_isAssemblyBlock__Y3Avk"};
1646
+ styleInject(css_248z$7);
1647
+
1648
+ const SCROLL_STATES = {
1649
+ Start: 'scrollStart',
1650
+ IsScrolling: 'isScrolling',
1651
+ End: 'scrollEnd',
1652
+ };
1653
+ const OUTGOING_EVENTS = {
1654
+ Connected: 'connected',
1655
+ DesignTokens: 'registerDesignTokens',
1656
+ HoveredSection: 'hoveredSection',
1657
+ MouseMove: 'mouseMove',
1658
+ NewHoveredElement: 'newHoveredElement',
1659
+ ComponentSelected: 'componentSelected',
1660
+ RegisteredComponents: 'registeredComponents',
1661
+ RequestComponentTreeUpdate: 'requestComponentTreeUpdate',
1662
+ ComponentDragCanceled: 'componentDragCanceled',
1663
+ ComponentDropped: 'componentDropped',
1664
+ ComponentMoved: 'componentMoved',
1665
+ CanvasReload: 'canvasReload',
1666
+ UpdateSelectedComponentCoordinates: 'updateSelectedComponentCoordinates',
1667
+ UpdateHoveredComponentCoordinates: 'updateHoveredComponentCoordinates',
1668
+ CanvasScroll: 'canvasScrolling',
1669
+ CanvasError: 'canvasError',
1670
+ ComponentMoveStarted: 'componentMoveStarted',
1671
+ ComponentMoveEnded: 'componentMoveEnded',
1672
+ OutsideCanvasClick: 'outsideCanvasClick',
1673
+ };
1674
+ const INCOMING_EVENTS = {
1675
+ RequestEditorMode: 'requestEditorMode',
1676
+ ExperienceUpdated: 'componentTreeUpdated',
1677
+ ComponentDraggingChanged: 'componentDraggingChanged',
1678
+ ComponentDragCanceled: 'componentDragCanceled',
1679
+ ComponentDragStarted: 'componentDragStarted',
1680
+ ComponentDragEnded: 'componentDragEnded',
1681
+ ComponentMoveEnded: 'componentMoveEnded',
1682
+ CanvasResized: 'canvasResized',
1683
+ SelectComponent: 'selectComponent',
1684
+ HoverComponent: 'hoverComponent',
1685
+ UpdatedEntity: 'updatedEntity',
1686
+ AssembliesAdded: 'assembliesAdded',
1687
+ AssembliesRegistered: 'assembliesRegistered',
1688
+ InitEditor: 'initEditor',
1689
+ MouseMove: 'mouseMove',
1690
+ };
1691
+ const INTERNAL_EVENTS = {
1692
+ ComponentsRegistered: 'cfComponentsRegistered',
1693
+ VisualEditorInitialize: 'cfVisualEditorInitialize',
1694
+ };
1695
+ const VISUAL_EDITOR_EVENTS = {
1696
+ Ready: 'cfVisualEditorReady',
1697
+ };
1698
+ const CONTENTFUL_COMPONENTS = {
1699
+ section: {
1700
+ id: 'contentful-section',
1701
+ name: 'Section',
1702
+ },
1703
+ container: {
1704
+ id: 'contentful-container',
1705
+ name: 'Container',
1706
+ },
1707
+ columns: {
1708
+ id: 'contentful-columns',
1709
+ name: 'Columns',
1710
+ },
1711
+ singleColumn: {
1712
+ id: 'contentful-single-column',
1713
+ name: 'Column',
1714
+ },
1715
+ button: {
1716
+ id: 'button',
1717
+ name: 'Button',
1718
+ },
1719
+ heading: {
1720
+ id: 'heading',
1721
+ name: 'Heading',
1722
+ },
1723
+ image: {
1724
+ id: 'image',
1725
+ name: 'Image',
1726
+ },
1727
+ richText: {
1728
+ id: 'richText',
1729
+ name: 'Rich Text',
1730
+ },
1731
+ text: {
1732
+ id: 'text',
1733
+ name: 'Text',
1734
+ },
1735
+ };
1736
+ const ASSEMBLY_NODE_TYPE = 'assembly';
1737
+ const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
1738
+ const ASSEMBLY_BLOCK_NODE_TYPE = 'assemblyBlock';
1739
+ const ASSEMBLY_NODE_TYPES = [ASSEMBLY_NODE_TYPE, ASSEMBLY_BLOCK_NODE_TYPE];
1740
+ const CF_STYLE_ATTRIBUTES = [
1741
+ 'cfHorizontalAlignment',
1742
+ 'cfVerticalAlignment',
1743
+ 'cfMargin',
1744
+ 'cfPadding',
1745
+ 'cfBackgroundColor',
1746
+ 'cfWidth',
1747
+ 'cfMaxWidth',
1748
+ 'cfHeight',
1749
+ 'cfImageAsset',
1750
+ 'cfImageOptions',
1751
+ 'cfBackgroundImageUrl',
1752
+ 'cfBackgroundImageOptions',
1753
+ 'cfFlexDirection',
1754
+ 'cfFlexWrap',
1755
+ 'cfBorder',
1756
+ 'cfBorderRadius',
1757
+ 'cfGap',
1758
+ 'cfFontSize',
1759
+ 'cfFontWeight',
1760
+ 'cfLineHeight',
1761
+ 'cfLetterSpacing',
1762
+ 'cfTextColor',
1763
+ 'cfTextAlign',
1764
+ 'cfTextTransform',
1765
+ 'cfTextBold',
1766
+ 'cfTextItalic',
1767
+ 'cfTextUnderline',
1768
+ // For backwards compatibility
1769
+ // we need to keep those in this constant array
1770
+ // so that omit() in <VisualEditorBlock> and <CompositionBlock>
1771
+ // can filter them out and not pass as props
1772
+ 'cfBackgroundImageScaling',
1773
+ 'cfBackgroundImageAlignment',
1774
+ 'cfBackgroundImageAlignmentVertical',
1775
+ 'cfBackgroundImageAlignmentHorizontal',
1776
+ ];
1777
+ const EMPTY_CONTAINER_HEIGHT = '80px';
1778
+ var PostMessageMethods$1;
1779
+ (function (PostMessageMethods) {
1780
+ PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
1781
+ PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
1782
+ })(PostMessageMethods$1 || (PostMessageMethods$1 = {}));
1783
+
1784
+ const isRelativePreviewSize = (width) => {
1785
+ // For now, we solely allow 100% as relative value
1786
+ return width === '100%';
1787
+ };
1788
+ const getTooltipPositions = ({ previewSize, tooltipRect, coordinates, }) => {
1789
+ if (!coordinates || !tooltipRect) {
1790
+ return { display: 'none' };
1791
+ }
1792
+ /**
1793
+ * By default, the tooltip floats to the left of the element
1794
+ */
1795
+ const newTooltipStyles = { display: 'flex' };
1796
+ // If the preview size is relative, we don't change the floating direction
1797
+ if (!isRelativePreviewSize(previewSize)) {
1798
+ const previewSizeMatch = previewSize.match(/(\d{1,})px/);
1799
+ if (!previewSizeMatch) {
1800
+ return { display: 'none' };
1801
+ }
1802
+ const previewSizePx = parseInt(previewSizeMatch[1]);
1803
+ /**
1804
+ * If the element is at the right edge of the canvas, and the element isn't wide enough to fit the tooltip width,
1805
+ * we float the tooltip to the right of the element.
1806
+ */
1807
+ if (tooltipRect.width > previewSizePx - coordinates.right &&
1808
+ tooltipRect.width > coordinates.width) {
1809
+ newTooltipStyles['float'] = 'right';
1810
+ }
1811
+ }
1812
+ const tooltipHeight = tooltipRect.height === 0 ? 32 : tooltipRect.height;
1813
+ /**
1814
+ * For elements with small heights, we don't want the tooltip covering the content in the element,
1815
+ * so we show the tooltip at the top or bottom.
1816
+ */
1817
+ if (tooltipHeight * 2 > coordinates.height) {
1818
+ /**
1819
+ * If there's enough space for the tooltip at the top of the element, we show the tooltip at the top of the element,
1820
+ * else we show the tooltip at the bottom.
1821
+ */
1822
+ if (tooltipHeight < coordinates.top) {
1823
+ newTooltipStyles['bottom'] = coordinates.height;
1824
+ }
1825
+ else {
1826
+ newTooltipStyles['top'] = coordinates.height;
1827
+ }
1828
+ }
1829
+ /**
1830
+ * If the component draws outside of the borders of the canvas to the left we move the tooltip to the right
1831
+ * so that it is fully visible.
1832
+ */
1833
+ if (coordinates.left < 0) {
1834
+ newTooltipStyles['left'] = -coordinates.left;
1835
+ }
1836
+ /**
1837
+ * If for any reason, the element's top is negative, we show the tooltip at the bottom
1838
+ */
1839
+ if (coordinates.top < 0) {
1840
+ newTooltipStyles['top'] = coordinates.height;
1841
+ }
1842
+ return newTooltipStyles;
1843
+ };
1844
+
1845
+ const Tooltip = ({ coordinates, id, label, isAssemblyBlock, isContainer }) => {
1846
+ const tooltipRef = useRef(null);
1847
+ const previewSize = '100%'; // This should be based on breakpoints and added to usememo dependency array
1848
+ const tooltipStyles = useMemo(() => {
1849
+ const tooltipRect = tooltipRef.current?.getBoundingClientRect();
1850
+ const draggableRect = document
1851
+ .querySelector(`[data-ctfl-draggable-id="${id}"]`)
1852
+ ?.getBoundingClientRect();
1853
+ const newTooltipStyles = getTooltipPositions({
1854
+ previewSize,
1855
+ tooltipRect,
1856
+ coordinates: draggableRect,
1857
+ });
1858
+ return newTooltipStyles;
1859
+ // 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
1860
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1861
+ }, [coordinates, id, tooltipRef.current]);
1862
+ return (React.createElement("div", { ref: tooltipRef, style: tooltipStyles, className: classNames(styles$3.overlay, {
1863
+ [styles$3.overlayContainer]: isContainer,
1864
+ [styles$3.overlayAssembly]: isAssemblyBlock,
1865
+ }) }, label));
1866
+ };
1867
+
1868
+ const useDraggedItemStore = create((set) => ({
1869
+ draggedItem: undefined,
1870
+ domRect: undefined,
1871
+ componentId: '',
1872
+ isDraggingOnCanvas: false,
1873
+ onBeforeCaptureId: '',
1874
+ mouseX: 0,
1875
+ mouseY: 0,
1876
+ scrollY: 0,
1877
+ setComponentId(id) {
1878
+ set({ componentId: id });
1879
+ },
1880
+ updateItem: (item) => {
1881
+ set({ draggedItem: item });
1882
+ },
1883
+ setDraggingOnCanvas: (isDraggingOnCanvas) => {
1884
+ set({ isDraggingOnCanvas });
1885
+ },
1886
+ setOnBeforeCaptureId: (onBeforeCaptureId) => {
1887
+ set({ onBeforeCaptureId });
1888
+ },
1889
+ setMousePosition(x, y) {
1890
+ set({ mouseX: x, mouseY: y });
1891
+ },
1892
+ setDomRect(domRect) {
1893
+ set({ domRect });
1894
+ },
1895
+ setScrollY(y) {
1896
+ set({ scrollY: y });
1897
+ },
1898
+ }));
1899
+
1900
+ const DRAGGABLE_HEIGHT = 30;
1901
+ const DRAGGABLE_WIDTH = 50;
1902
+ const DRAG_PADDING = 4;
1903
+ const ROOT_ID = 'root';
1904
+ const COMPONENT_LIST_ID = 'component-list';
1905
+ const NEW_COMPONENT_ID = 'ctfl-new-draggable';
1906
+ const CTFL_ZONE_ID = 'data-ctfl-zone-id';
1907
+ const CTFL_DRAGGING_ELEMENT = 'data-ctfl-dragging-element';
1908
+ const HITBOX = {
1909
+ WIDTH: 70,
1910
+ HEIGHT: 20,
1911
+ INITIAL_OFFSET: 10,
1912
+ OFFSET_INCREMENT: 8,
1913
+ MIN_HEIGHT: 45,
1914
+ MIN_DEPTH_HEIGHT: 20,
1915
+ DEEP_ZONE: 5,
1916
+ };
1917
+ const builtInComponents = [
1918
+ CONTENTFUL_COMPONENTS.container.id,
1919
+ CONTENTFUL_COMPONENTS.section.id,
1920
+ CONTENTFUL_COMPONENTS.columns.id,
1921
+ CONTENTFUL_COMPONENTS.singleColumn.id,
1922
+ ];
1923
+ var TreeAction;
1924
+ (function (TreeAction) {
1925
+ TreeAction[TreeAction["REMOVE_NODE"] = 0] = "REMOVE_NODE";
1926
+ TreeAction[TreeAction["ADD_NODE"] = 1] = "ADD_NODE";
1927
+ TreeAction[TreeAction["MOVE_NODE"] = 2] = "MOVE_NODE";
1928
+ TreeAction[TreeAction["UPDATE_NODE"] = 3] = "UPDATE_NODE";
1929
+ TreeAction[TreeAction["REORDER_NODE"] = 4] = "REORDER_NODE";
1930
+ TreeAction[TreeAction["REPLACE_NODE"] = 5] = "REPLACE_NODE";
1931
+ })(TreeAction || (TreeAction = {}));
1932
+ var HitboxDirection;
1933
+ (function (HitboxDirection) {
1934
+ HitboxDirection[HitboxDirection["TOP"] = 0] = "TOP";
1935
+ HitboxDirection[HitboxDirection["LEFT"] = 1] = "LEFT";
1936
+ HitboxDirection[HitboxDirection["RIGHT"] = 2] = "RIGHT";
1937
+ HitboxDirection[HitboxDirection["BOTTOM"] = 3] = "BOTTOM";
1938
+ HitboxDirection[HitboxDirection["SELF_VERTICAL"] = 4] = "SELF_VERTICAL";
1939
+ HitboxDirection[HitboxDirection["SELF_HORIZONTAL"] = 5] = "SELF_HORIZONTAL";
1940
+ })(HitboxDirection || (HitboxDirection = {}));
1941
+ var DraggablePosition;
1942
+ (function (DraggablePosition) {
1943
+ DraggablePosition[DraggablePosition["CENTERED"] = 0] = "CENTERED";
1944
+ DraggablePosition[DraggablePosition["MOUSE_POSITION"] = 1] = "MOUSE_POSITION";
1945
+ })(DraggablePosition || (DraggablePosition = {}));
1946
+
1947
+ const calcOffsetLeft = (parentElement, placeholderWidth, nodeWidth) => {
1948
+ if (!parentElement) {
1949
+ return 0;
1950
+ }
1951
+ const alignItems = window.getComputedStyle(parentElement).alignItems;
1952
+ if (alignItems === 'center') {
1953
+ return -(placeholderWidth - nodeWidth) / 2;
1954
+ }
1955
+ if (alignItems === 'end') {
1956
+ return -placeholderWidth + nodeWidth + 2;
1957
+ }
1958
+ return 0;
1959
+ };
1960
+ const calcOffsetTop = (parentElement, placeholderHeight, nodeHeight) => {
1961
+ if (!parentElement) {
1962
+ return 0;
1963
+ }
1964
+ const alignItems = window.getComputedStyle(parentElement).alignItems;
1965
+ if (alignItems === 'center') {
1966
+ return -(placeholderHeight - nodeHeight) / 2;
1967
+ }
1968
+ if (alignItems === 'end') {
1969
+ return -placeholderHeight + nodeHeight + 2;
1970
+ }
1971
+ return 0;
1972
+ };
1973
+ const getPaddingOffset = (element) => {
1974
+ const paddingLeft = parseFloat(window.getComputedStyle(element).paddingLeft);
1975
+ const paddingRight = parseFloat(window.getComputedStyle(element).paddingRight);
1976
+ const paddingTop = parseFloat(window.getComputedStyle(element).paddingTop);
1977
+ const paddingBottom = parseFloat(window.getComputedStyle(element).paddingBottom);
1978
+ const horizontalOffset = paddingLeft + paddingRight;
1979
+ const verticalOffset = paddingTop + paddingBottom;
1980
+ return [horizontalOffset, verticalOffset];
1981
+ };
1982
+ /**
1983
+ * Calculate the size and position of the dropzone indicator
1984
+ * when dragging a new component onto the canvas
1985
+ */
1986
+ const calcNewComponentStyles = (params) => {
1987
+ const { destinationIndex, elementIndex, dropzoneElementId, id, direction, totalIndexes } = params;
1988
+ const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
1989
+ const isHorizontal = direction === 'horizontal';
1990
+ const isRightAlign = isHorizontal && isEnd;
1991
+ const isBottomAlign = !isHorizontal && isEnd;
1992
+ const dropzone = document.querySelector(`[data-rfd-droppable-id="${dropzoneElementId}"]`);
1993
+ const element = document.querySelector(`[data-ctfl-draggable-id="${id}"]`);
1994
+ if (!dropzone || !element) {
1995
+ return emptyStyles;
1996
+ }
1997
+ const elementSizes = element.getBoundingClientRect();
1998
+ const dropzoneSizes = dropzone.getBoundingClientRect();
1999
+ const [horizontalPadding, verticalPadding] = getPaddingOffset(dropzone);
2000
+ const width = isHorizontal ? DRAGGABLE_WIDTH : dropzoneSizes.width - horizontalPadding;
2001
+ const height = isHorizontal ? dropzoneSizes.height - verticalPadding : DRAGGABLE_HEIGHT;
2002
+ const top = isHorizontal
2003
+ ? calcOffsetTop(element.parentElement, height, elementSizes.height)
2004
+ : -height;
2005
+ const left = isHorizontal
2006
+ ? -width
2007
+ : calcOffsetLeft(element.parentElement, width, elementSizes.width);
2008
+ return {
2009
+ width,
2010
+ height,
2011
+ top: !isBottomAlign ? top : 'unset',
2012
+ right: isRightAlign ? -width : 'unset',
2013
+ bottom: isBottomAlign ? -height : 'unset',
2014
+ left: !isRightAlign ? left : 'unset',
2015
+ };
2016
+ };
2017
+ /**
2018
+ * Calculate the size and position of the dropzone indicator
2019
+ * when moving an existing component on the canvas
2020
+ */
2021
+ const calcMovementStyles = (params) => {
2022
+ const { destinationIndex, sourceIndex, destinationId, sourceId, elementIndex, dropzoneElementId, id, direction, totalIndexes, draggableId, } = params;
2023
+ const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
2024
+ const isHorizontal = direction === 'horizontal';
2025
+ const isSameZone = destinationId === sourceId;
2026
+ const isBelowSourceIndex = destinationIndex > sourceIndex;
2027
+ const isRightAlign = isHorizontal && (isEnd || (isSameZone && isBelowSourceIndex));
2028
+ const isBottomAlign = !isHorizontal && (isEnd || (isSameZone && isBelowSourceIndex));
2029
+ const dropzone = document.querySelector(`[data-rfd-droppable-id="${dropzoneElementId}"]`);
2030
+ const draggable = document.querySelector(`[data-rfd-draggable-id="${draggableId}"]`);
2031
+ const element = document.querySelector(`[data-ctfl-draggable-id="${id}"]`);
2032
+ if (!dropzone || !element || !draggable) {
2033
+ return emptyStyles;
2034
+ }
2035
+ const elementSizes = element.getBoundingClientRect();
2036
+ const dropzoneSizes = dropzone.getBoundingClientRect();
2037
+ const draggableSizes = draggable.getBoundingClientRect();
2038
+ const [horizontalPadding, verticalPadding] = getPaddingOffset(dropzone);
2039
+ const width = isHorizontal ? draggableSizes.width : dropzoneSizes.width - horizontalPadding;
2040
+ const height = isHorizontal ? dropzoneSizes.height - verticalPadding : draggableSizes.height;
2041
+ const top = isHorizontal
2042
+ ? calcOffsetTop(element.parentElement, height, elementSizes.height)
2043
+ : -height;
2044
+ const left = isHorizontal
2045
+ ? -width
2046
+ : calcOffsetLeft(element.parentElement, width, elementSizes.width);
2047
+ return {
2048
+ width,
2049
+ height,
2050
+ top: !isBottomAlign ? top : 'unset',
2051
+ right: isRightAlign ? -width : 'unset',
2052
+ bottom: isBottomAlign ? -height : 'unset',
2053
+ left: !isRightAlign ? left : 'unset',
2054
+ };
2055
+ };
2056
+ const emptyStyles = { width: 0, height: 0 };
2057
+ const calcPlaceholderStyles = (params) => {
2058
+ const { isDraggingOver, sourceId } = params;
2059
+ if (!isDraggingOver) {
2060
+ return emptyStyles;
2061
+ }
2062
+ if (sourceId === COMPONENT_LIST_ID) {
2063
+ return calcNewComponentStyles(params);
2064
+ }
2065
+ return calcMovementStyles(params);
2066
+ };
2067
+ const Placeholder = (props) => {
2068
+ const sourceIndex = useDraggedItemStore((state) => state.draggedItem?.source.index) ?? -1;
2069
+ const draggableId = useDraggedItemStore((state) => state.draggedItem?.draggableId) ?? '';
2070
+ const sourceId = useDraggedItemStore((state) => state.draggedItem?.source.droppableId) ?? '';
2071
+ const destinationIndex = useDraggedItemStore((state) => state.draggedItem?.destination?.index) ?? -1;
2072
+ const destinationId = useDraggedItemStore((state) => state.draggedItem?.destination?.droppableId) ?? '';
2073
+ const { elementIndex, totalIndexes, isDraggingOver } = props;
2074
+ const isActive = destinationIndex === elementIndex;
2075
+ const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
2076
+ const isVisible = isEnd || isActive;
2077
+ const isComponentList = destinationId === COMPONENT_LIST_ID;
2078
+ return (!isComponentList &&
2079
+ isDraggingOver &&
2080
+ isVisible && (React.createElement("div", { style: {
2081
+ ...calcPlaceholderStyles({
2082
+ ...props,
2083
+ sourceId,
2084
+ sourceIndex,
2085
+ destinationId,
2086
+ destinationIndex,
2087
+ draggableId,
2088
+ }),
2089
+ backgroundColor: 'rgba(var(--exp-builder-blue300-rgb), 0.5)',
2090
+ position: 'absolute',
2091
+ } })));
2092
+ };
2093
+
2094
+ function useDraggablePosition({ draggableId, draggableRef, position }) {
2095
+ const isDraggingOnCanvas = useDraggedItemStore((state) => state.isDraggingOnCanvas);
2096
+ const draggingId = useDraggedItemStore((state) => state.onBeforeCaptureId);
2097
+ const preDragDomRect = useDraggedItemStore((state) => state.domRect);
2098
+ useEffect(() => {
2099
+ const el = draggableRef?.current;
2100
+ if (!isDraggingOnCanvas || draggingId !== draggableId || !el) {
2101
+ return;
2102
+ }
2103
+ const isCentered = position === DraggablePosition.CENTERED || !preDragDomRect;
2104
+ const domRect = isCentered ? el.getBoundingClientRect() : preDragDomRect;
2105
+ const { mouseX, mouseY } = useDraggedItemStore.getState();
2106
+ const top = isCentered ? mouseY - domRect.height / 2 : domRect.top;
2107
+ const left = isCentered ? mouseX - domRect.width / 2 : domRect.left;
2108
+ el.style.position = 'fixed';
2109
+ el.style.left = `${left}px`;
2110
+ el.style.top = `${top}px`;
2111
+ el.style.width = `${domRect.width}px`;
2112
+ el.style.height = `${domRect.height}px`;
2113
+ }, [draggableRef, draggableId, isDraggingOnCanvas, draggingId, position, preDragDomRect]);
2114
+ }
2115
+
2116
+ function getStyle$2(style, snapshot) {
2117
+ if (!snapshot.isDropAnimating) {
2118
+ return style;
2119
+ }
2120
+ return {
2121
+ ...style,
2122
+ // cannot be 0, but make it super tiny
2123
+ transitionDuration: `0.001s`,
2124
+ };
2125
+ }
2126
+ const DraggableComponent = ({ children, id, index, isAssemblyBlock = false, isSelected = false, onClick = () => null, coordinates, userIsDragging, style, wrapperProps, isContainer, blockId, isDragDisabled = false, placeholder, definition, ...rest }) => {
2127
+ const ref = useRef(null);
2128
+ const setDomRect = useDraggedItemStore((state) => state.setDomRect);
2129
+ useDraggablePosition({
2130
+ draggableId: id,
2131
+ draggableRef: ref,
2132
+ position: DraggablePosition.MOUSE_POSITION,
2133
+ });
2134
+ return (React.createElement(Draggable, { key: id, draggableId: id, index: index, isDragDisabled: isDragDisabled }, (provided, snapshot) => (React.createElement("div", { "data-ctfl-draggable-id": id, "data-test-id": `draggable-${blockId ?? 'node'}`, ref: (refNode) => {
2135
+ provided?.innerRef(refNode);
2136
+ ref.current = refNode;
2137
+ }, ...wrapperProps, ...provided.draggableProps, ...provided.dragHandleProps, ...rest, className: classNames(styles$3.DraggableComponent, wrapperProps.className, {
2138
+ [styles$3.isAssemblyBlock]: isAssemblyBlock,
2139
+ [styles$3.isDragging]: snapshot.isDragging,
2140
+ [styles$3.isSelected]: isSelected,
2141
+ [styles$3.userIsDragging]: userIsDragging,
2142
+ }), style: {
2143
+ ...style,
2144
+ ...getStyle$2(provided.draggableProps.style, snapshot),
2145
+ }, onMouseDown: (e) => {
2146
+ if (isDragDisabled) {
2147
+ return;
2148
+ }
2149
+ e.stopPropagation();
2150
+ setDomRect(e.currentTarget.getBoundingClientRect());
2151
+ }, onClick: onClick },
2152
+ React.createElement(Tooltip, { id: id, coordinates: coordinates, isAssemblyBlock: isAssemblyBlock, isContainer: isContainer, label: definition.name || 'No label specified' }),
2153
+ React.createElement(Placeholder, { ...placeholder, id: id }),
2154
+ children))));
2155
+ };
2156
+
2157
+ /**
2158
+ * This function gets the element co-ordinates of a specified component in the DOM and its parent
2159
+ * and sends the DOM Rect to the client app
2160
+ */
2161
+ const sendSelectedComponentCoordinates = (instanceId) => {
2162
+ if (!instanceId)
2163
+ return;
2164
+ let selectedElement = document.querySelector(`[data-cf-node-id="${instanceId}"]`);
2165
+ let selectedAssemblyChild = undefined;
2166
+ const [rootNodeId, nodeLocation] = instanceId.split('---');
2167
+ if (nodeLocation) {
2168
+ selectedAssemblyChild = selectedElement;
2169
+ selectedElement = document.querySelector(`[data-cf-node-id="${rootNodeId}"]`);
2170
+ }
2171
+ // Finds the first parent that is a VisualEditorBlock
2172
+ let parent = selectedElement?.parentElement;
2173
+ while (parent) {
2174
+ if (parent?.dataset?.cfNodeId) {
2175
+ break;
2176
+ }
2177
+ parent = parent?.parentElement;
2178
+ }
2179
+ if (selectedElement) {
2180
+ sendMessage(OUTGOING_EVENTS.UpdateSelectedComponentCoordinates, {
2181
+ selectedNodeCoordinates: getElementCoordinates(selectedElement),
2182
+ selectedAssemblyChildCoordinates: selectedAssemblyChild
2183
+ ? getElementCoordinates(selectedAssemblyChild)
2184
+ : null,
2185
+ parentCoordinates: parent ? getElementCoordinates(parent) : null,
2186
+ });
2187
+ }
2188
+ };
2189
+
2190
+ // Note: During development, the hot reloading might empty this and it
2191
+ // stays empty leading to not rendering assemblies. Ideally, this is
2192
+ // integrated into the state machine to keep track of its state.
2193
+ const assembliesRegistry = new Map([]);
2194
+ const setAssemblies = (assemblies) => {
2195
+ for (const assembly of assemblies) {
2196
+ assembliesRegistry.set(assembly.sys.id, assembly);
2197
+ }
2198
+ };
2199
+ const componentRegistry = new Map();
2200
+ const addComponentRegistration = (componentRegistration) => {
2201
+ componentRegistry.set(componentRegistration.definition.id, componentRegistration);
2202
+ };
2203
+ const createAssemblyRegistration = ({ definitionId, definitionName, component, }) => {
2204
+ const componentRegistration = componentRegistry.get(definitionId);
2205
+ if (componentRegistration) {
2206
+ return componentRegistration;
2207
+ }
2208
+ const definition = {
2209
+ id: definitionId,
2210
+ name: definitionName || 'Component',
2211
+ variables: {},
2212
+ children: true,
2213
+ category: ASSEMBLY_DEFAULT_CATEGORY,
2214
+ };
2215
+ addComponentRegistration({ component, definition });
2216
+ return componentRegistry.get(definitionId);
2217
+ };
2218
+
2219
+ const useEditorStore = create((set, get) => ({
2220
+ dataSource: {},
2221
+ unboundValues: {},
2222
+ isDragging: false,
2223
+ dragItem: '',
2224
+ selectedNodeId: null,
2225
+ locale: null,
2226
+ setSelectedNodeId: (id) => {
2227
+ set({ selectedNodeId: id });
2228
+ },
2229
+ setDataSource(data) {
2230
+ const dataSource = get().dataSource;
2231
+ const newDataSource = { ...dataSource, ...data };
2232
+ if (isEqual(dataSource, newDataSource)) {
2233
+ return;
2234
+ }
2235
+ set({ dataSource: newDataSource });
2236
+ },
2237
+ setUnboundValues(values) {
2238
+ set({ unboundValues: values });
2239
+ },
2240
+ setLocale(locale) {
2241
+ const currentLocale = get().locale;
2242
+ if (locale === currentLocale) {
2243
+ return;
2244
+ }
2245
+ set({ locale });
2246
+ },
2247
+ initializeEditor({ componentRegistry: initialRegistry, designTokens, initialLocale }) {
2248
+ initialRegistry.forEach((registration) => {
2249
+ componentRegistry.set(registration.definition.id, registration);
2250
+ });
2251
+ // Re-register the design tokens with the Visual Editor's instance of the experiences-core package
2252
+ defineDesignTokens(designTokens);
2253
+ set({ locale: initialLocale });
2254
+ },
2255
+ }));
2256
+
2257
+ /**
2258
+ * This hook gets the element co-ordinates of a specified element in the DOM
2259
+ * and sends the DOM Rect to the client app
2260
+ */
2261
+ const useSelectedInstanceCoordinates = ({ node }) => {
2262
+ const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
2263
+ useEffect(() => {
2264
+ if (selectedNodeId !== node.data.id) {
2265
+ return;
2266
+ }
2267
+ // Allows the drop animation to finish before
2268
+ // calculating the components coordinates
2269
+ setTimeout(() => {
2270
+ sendSelectedComponentCoordinates(node.data.id);
2271
+ }, 10);
2272
+ }, [node, selectedNodeId]);
2273
+ const selectedElement = node.data.id
2274
+ ? document.querySelector(`[data-cf-node-id="${selectedNodeId}"]`)
2275
+ : undefined;
2276
+ return selectedElement ? getElementCoordinates(selectedElement) : null;
2277
+ };
2278
+
2279
+ /**
2280
+ *
2281
+ * @param styles: the list of styles to apply
2282
+ * @param nodeId: [Optional] the id of node that these styles will be applied to
2283
+ * @returns className: the className that was used
2284
+ * Builds and adds a style tag in the document. Returns the className to be attached to the element.
2285
+ * In editor mode the nodeId is used as the identifier in order to avoid creating endless tags as the styles are tweeked
2286
+ * In preview/delivery mode the styles don't change oftem so we're using the md5 hash of the content of the tag
2287
+ */
2288
+ const useStyleTag = ({ styles, nodeId }) => {
2289
+ const [className, setClassName] = useState('');
2290
+ useEffect(() => {
2291
+ if (Object.keys(styles).length === 0) {
2292
+ return;
2293
+ }
2294
+ const [className, styleRule] = buildStyleTag({ styles, nodeId });
2295
+ setClassName(className);
2296
+ const existingTag = document.querySelector(`[data-cf-styles="${className}"]`);
2297
+ if (existingTag) {
2298
+ // editor mode - update existing
2299
+ if (nodeId) {
2300
+ existingTag.innerHTML = styleRule;
2301
+ }
2302
+ // preview/delivery mode - here we don't need to update the existing tag because
2303
+ // the className is based on the md5 hash of the content so it hasn't changed
2304
+ return;
2305
+ }
2306
+ const styleTag = document.createElement('style');
2307
+ styleTag.dataset['cfStyles'] = className;
2308
+ document.head.appendChild(styleTag).innerHTML = styleRule;
2309
+ }, [styles, nodeId]);
2310
+ return { className };
2311
+ };
2312
+
2313
+ const getUnboundValues = ({ key, fallback, unboundValues, }) => {
2314
+ const lodashPath = `${key}.value`;
2315
+ return get$1(unboundValues, lodashPath, fallback);
2316
+ };
2317
+
2318
+ const useEntityStore = create((set) => ({
2319
+ entityStore: new EditorModeEntityStore({ locale: 'en-US', entities: [] }),
2320
+ areEntitiesFetched: false,
2321
+ setEntitiesFetched(fetched) {
2322
+ set({ areEntitiesFetched: fetched });
2323
+ },
2324
+ resetEntityStore(locale, entities = []) {
2325
+ console.debug(`[experiences-sdk-react] Resetting entity store because the locale changed to '${locale}'.`);
2326
+ const newEntityStore = new EditorModeEntityStore({ locale, entities });
2327
+ set({
2328
+ entityStore: newEntityStore,
2329
+ areEntitiesFetched: false,
2330
+ });
2331
+ return newEntityStore;
2332
+ },
2333
+ }));
2334
+
2335
+ const useComponentProps = ({ node, areEntitiesFetched, resolveDesignValue, renderDropzone, definition, userIsDragging, }) => {
2336
+ const unboundValues = useEditorStore((state) => state.unboundValues);
2337
+ const dataSource = useEditorStore((state) => state.dataSource);
2338
+ const entityStore = useEntityStore((state) => state.entityStore);
2339
+ const props = useMemo(() => {
2340
+ // Don't enrich the assembly wrapper node with props
2341
+ if (!definition || node.type === ASSEMBLY_NODE_TYPE) {
2342
+ return {};
2343
+ }
2344
+ return Object.entries(definition.variables).reduce((acc, [variableName, variableDefinition]) => {
2345
+ const variableMapping = node.data.props[variableName];
2346
+ if (!variableMapping) {
2347
+ return {
2348
+ ...acc,
2349
+ [variableName]: variableDefinition.defaultValue,
2350
+ };
2351
+ }
2352
+ if (variableMapping.type === 'DesignValue') {
2353
+ const valueByBreakpoint = resolveDesignValue(variableMapping.valuesByBreakpoint, variableName);
2354
+ const designValue = variableName === 'cfHeight'
2355
+ ? calculateNodeDefaultHeight({
2356
+ blockId: node.data.blockId,
2357
+ children: node.children,
2358
+ value: valueByBreakpoint,
2359
+ })
2360
+ : valueByBreakpoint;
2361
+ return {
2362
+ ...acc,
2363
+ [variableName]: designValue,
2364
+ };
2365
+ }
2366
+ else if (variableMapping.type === 'BoundValue') {
2367
+ const [, uuid, path] = variableMapping.path.split('/');
2368
+ const binding = dataSource[uuid];
2369
+ const variableDefinition = definition.variables[variableName];
2370
+ let boundValue = transformBoundContentValue(node.data.props, entityStore, binding, resolveDesignValue, variableName, variableDefinition, variableMapping.path);
2371
+ // In some cases, there may be an asset linked in the path, so we need to consider this scenario:
2372
+ // If no 'boundValue' is found, we also attempt to extract the value associated with the second-to-last item in the path.
2373
+ // If successful, it means we have identified the linked asset.
2374
+ if (!boundValue) {
2375
+ const maybeBoundAsset = areEntitiesFetched
2376
+ ? entityStore.getValue(binding, path.split('/').slice(0, -2))
2377
+ : undefined;
2378
+ if (isLinkToAsset(maybeBoundAsset)) {
2379
+ boundValue = maybeBoundAsset;
2380
+ }
2381
+ }
2382
+ const value = boundValue || variableDefinition.defaultValue;
2383
+ return {
2384
+ ...acc,
2385
+ [variableName]: value,
2386
+ };
2387
+ }
2388
+ else {
2389
+ const value = getUnboundValues({
2390
+ key: variableMapping.key,
2391
+ fallback: variableDefinition.defaultValue,
2392
+ unboundValues: unboundValues || {},
2393
+ });
2394
+ return {
2395
+ ...acc,
2396
+ [variableName]: value,
2397
+ };
2398
+ }
2399
+ }, {});
2400
+ }, [
2401
+ definition,
2402
+ node.data.props,
2403
+ node.children,
2404
+ node.data.blockId,
2405
+ resolveDesignValue,
2406
+ dataSource,
2407
+ areEntitiesFetched,
2408
+ unboundValues,
2409
+ node.type,
2410
+ entityStore,
2411
+ ]);
2412
+ const cfStyles = buildCfStyles(props);
2413
+ // Separate the component styles from the editor wrapper styles
2414
+ const { margin, height, width, maxWidth, ...componentStyles } = cfStyles;
2415
+ // Styles that will be applied to the editor wrapper (draggable) element
2416
+ const { className: wrapperClass } = useStyleTag({
2417
+ styles:
2418
+ // To ensure that assembly nodes are rendered like they are rendered in
2419
+ // the assembly editor, we need to use a normal block instead of a flex box.
2420
+ node.type === ASSEMBLY_NODE_TYPE
2421
+ ? {
2422
+ display: 'block !important',
2423
+ width: '100%',
2424
+ }
2425
+ : {
2426
+ margin,
2427
+ maxWidth,
2428
+ width,
2429
+ height,
2430
+ },
2431
+ nodeId: `editor-${node.data.id}`,
2432
+ });
2433
+ // Styles that will be applied to the component element
2434
+ const { className: componentClass } = useStyleTag({
2435
+ styles: {
2436
+ ...componentStyles,
2437
+ margin: 0,
2438
+ width: '100%',
2439
+ height: '100%',
2440
+ maxWidth: 'none',
2441
+ ...(isEmptyStructureWithRelativeHeight(node.children.length, node?.data.blockId, height) && {
2442
+ minHeight: EMPTY_CONTAINER_HEIGHT,
2443
+ }),
2444
+ ...(userIsDragging &&
2445
+ isContentfulStructureComponent(node?.data.blockId) &&
2446
+ node?.data.blockId !== CONTENTFUL_COMPONENTS.columns.id && {
2447
+ padding: addExtraDropzonePadding(componentStyles.padding?.toString() || '0 0 0 0'),
2448
+ }),
2449
+ },
2450
+ nodeId: node.data.id,
2451
+ });
2452
+ const wrapperProps = {
2453
+ className: wrapperClass,
2454
+ 'data-cf-node-id': node.data.id,
2455
+ 'data-cf-node-block-id': node.data.blockId,
2456
+ 'data-cf-node-block-type': node.type,
2457
+ };
2458
+ //List explicit style props that will end up being passed to the component
2459
+ const stylesToKeep = ['cfImageAsset'];
2460
+ const stylesToRemove = CF_STYLE_ATTRIBUTES.filter((style) => !stylesToKeep.includes(style));
2461
+ const componentProps = {
2462
+ className: componentClass,
2463
+ editorMode: true,
2464
+ node,
2465
+ renderDropzone,
2466
+ ...omit(props, stylesToRemove, ['cfHyperlink', 'cfOpenInNewTab']),
2467
+ ...(definition.children ? { children: renderDropzone(node) } : {}),
2468
+ };
2469
+ return { componentProps, wrapperProps };
2470
+ };
2471
+ const addExtraDropzonePadding = (padding) => padding
2472
+ .split(' ')
2473
+ .map((value) => {
2474
+ if (value.endsWith('px')) {
2475
+ const parsedValue = parseInt(value.replace(/px$/, ''), 10);
2476
+ return (parsedValue < DRAG_PADDING ? DRAG_PADDING : parsedValue) + 'px';
2477
+ }
2478
+ return `${DRAG_PADDING}px`;
2479
+ })
2480
+ .join(' ');
2481
+
2482
+ var PostMessageMethods;
2483
+ (function (PostMessageMethods) {
2484
+ PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
2485
+ PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
2486
+ })(PostMessageMethods || (PostMessageMethods = {}));
2487
+
2488
+ var css_248z$5 = ".cf-heading {\n white-space: pre-line;\n}\n";
2489
+ styleInject(css_248z$5);
2490
+
2491
+ var css_248z$4 = ".cf-richtext {\n white-space: pre-line;\n}\n";
2492
+ styleInject(css_248z$4);
2493
+
2494
+ var css_248z$3 = ".cf-text {\n white-space: pre-line;\n}\n";
2495
+ styleInject(css_248z$3);
2496
+
2497
+ var css_248z$2$1 = ".cf-no-image {\n position: relative;\n}\n\n.cf-no-image img {\n background-color: var(--cf-color-gray100);\n outline-offset: -2px;\n outline: 2px solid rgba(var(--cf-color-gray400-rgb), 0.5);\n}\n\n[data-ctfl-draggable-id] .cf-no-image {\n width: 100%;\n height: 100%;\n}\n\n[data-ctfl-draggable-id] .cf-no-image img {\n width: 100%;\n}\n\n.cf-no-image svg {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n height: var(--cf-text-3xl);\n width: var(--cf-text-3xl);\n max-height: 100%;\n max-width: 100%;\n}\n\n.cf-no-image svg path {\n fill: var(--cf-color-gray400);\n}\n";
2498
+ styleInject(css_248z$2$1);
2499
+
2500
+ var css_248z$1$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.cf-container-label {\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: 10;\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";
2501
+ styleInject(css_248z$1$1);
2502
+
2503
+ 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) => {
2504
+ return (React.createElement("div", { id: id, ref: ref, style: {
2505
+ display: 'flex',
2506
+ flex,
2507
+ flexBasis,
2508
+ flexShrink,
2509
+ flexDirection,
2510
+ gap,
2511
+ justifyContent,
2512
+ justifyItems,
2513
+ justifySelf,
2514
+ alignItems,
2515
+ alignSelf,
2516
+ alignContent,
2517
+ order,
2518
+ flexWrap,
2519
+ flexGrow,
2520
+ ...cssStyles,
2521
+ }, className: className, onMouseEnter: onMouseEnter, onMouseUp: onMouseUp, onMouseDown: onMouseDown, onMouseLeave: onMouseLeave, onClick: onClick, ...props }, children));
2522
+ });
2523
+ Flex.displayName = 'Flex';
2524
+
2525
+ var css_248z$6 = ".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 .Columns {\n display: grid;\n }\n}\n\n.cf-single-column-wrapper {\n position: relative;\n}\n\n.cf-single-column {\n pointer-events: all;\n}\n\n.cf-single-column-label {\n pointer-events: none;\n position: absolute;\n z-index: -1;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n justify-content: center;\n align-items: center;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 12px;\n color: var(--exp-builder-gray400);\n z-index: 100;\n}\n";
2526
+ styleInject(css_248z$6);
2527
+
2528
+ const ColumnWrapper = forwardRef((props, ref) => {
2529
+ return (React.createElement("div", { ref: ref, ...props, style: {
2530
+ ...(props.style || {}),
2531
+ display: 'grid',
2532
+ gridTemplateColumns: 'repeat(12, [col-start] 1fr)',
2533
+ } }, props.children));
2534
+ });
2535
+ ColumnWrapper.displayName = 'ColumnWrapper';
2536
+
2537
+ const assemblyStyle = { display: 'contents' };
2538
+ // Feel free to do any magic as regards variable definitions for assemblies
2539
+ // Or if this isn't necessary by the time we figure that part out, we can bid this part farewell
2540
+ const Assembly = (props) => {
2541
+ if (props.editorMode) {
2542
+ const { node } = props;
2543
+ return props.renderDropzone(node, {
2544
+ ['data-test-id']: 'contentful-assembly',
2545
+ className: props.className,
2546
+ style: assemblyStyle,
2547
+ });
2548
+ }
2549
+ // Using a display contents so assembly content/children
2550
+ // can appear as if they are direct children of the div wrapper's parent
2551
+ return React.createElement("div", { "data-test-id": "assembly", ...props, style: assemblyStyle });
2552
+ };
2553
+
2554
+ const deserializeAssemblyNode = ({ node, nodeId, nodeLocation, parentId, assemblyDataSource, assemblyId, assemblyComponentId, assemblyUnboundValues, componentInstanceProps, componentInstanceUnboundValues, componentInstanceDataSource, }) => {
2555
+ const childNodeVariable = {};
2556
+ const dataSource = {};
2557
+ const unboundValues = {};
2558
+ for (const [variableName, variable] of Object.entries(node.variables)) {
2559
+ childNodeVariable[variableName] = variable;
2560
+ if (variable.type === 'ComponentValue') {
2561
+ const componentValueKey = variable.key;
2562
+ const instanceProperty = componentInstanceProps[componentValueKey];
2563
+ // For assembly, we look up the value in the assembly instance and
2564
+ // replace the componentValue with that one.
2565
+ if (instanceProperty?.type === 'UnboundValue') {
2566
+ const componentInstanceValue = componentInstanceUnboundValues[instanceProperty.key];
2567
+ unboundValues[instanceProperty.key] = componentInstanceValue;
2568
+ childNodeVariable[variableName] = {
2569
+ type: 'UnboundValue',
2570
+ key: instanceProperty.key,
2571
+ };
2572
+ }
2573
+ else if (instanceProperty?.type === 'BoundValue') {
2574
+ const [, dataSourceKey] = instanceProperty.path.split('/');
2575
+ const componentInstanceValue = componentInstanceDataSource[dataSourceKey];
2576
+ dataSource[dataSourceKey] = componentInstanceValue;
2577
+ childNodeVariable[variableName] = {
2578
+ type: 'BoundValue',
2579
+ path: instanceProperty.path,
2580
+ };
2581
+ }
2582
+ }
2583
+ }
2584
+ const isAssembly = assembliesRegistry.has(node.definitionId);
2585
+ const children = node.children.map((child, childIndex) => {
2586
+ const newNodeLocation = nodeLocation === null ? `${childIndex}` : nodeLocation + '_' + childIndex;
2587
+ return deserializeAssemblyNode({
2588
+ node: child,
2589
+ nodeId: `${assemblyComponentId}---${newNodeLocation}`,
2590
+ parentId: nodeId,
2591
+ nodeLocation: newNodeLocation,
2592
+ assemblyId,
2593
+ assemblyDataSource,
2594
+ assemblyComponentId,
2595
+ assemblyUnboundValues,
2596
+ componentInstanceProps,
2597
+ componentInstanceUnboundValues,
2598
+ componentInstanceDataSource,
2599
+ });
2600
+ });
2601
+ return {
2602
+ // separate node type identifiers for assemblies and their blocks, so we can treat them differently in as much as we want
2603
+ type: isAssembly ? ASSEMBLY_NODE_TYPE : ASSEMBLY_BLOCK_NODE_TYPE,
2604
+ parentId,
2605
+ data: {
2606
+ id: nodeId,
2607
+ assembly: {
2608
+ id: assemblyId,
2609
+ componentId: assemblyComponentId,
2610
+ nodeLocation: nodeLocation || null,
2611
+ },
2612
+ blockId: node.definitionId,
2613
+ props: childNodeVariable,
2614
+ dataSource,
2615
+ unboundValues,
2616
+ breakpoints: [],
2617
+ },
2618
+ children,
2619
+ };
2620
+ };
2621
+ const resolveAssembly = ({ node, entityStore, }) => {
2622
+ if (node.type !== ASSEMBLY_NODE_TYPE) {
2623
+ return node;
2624
+ }
2625
+ const componentId = node.data.blockId;
2626
+ const assembly = assembliesRegistry.get(componentId);
2627
+ if (!assembly) {
2628
+ console.warn(`Link to assembly with ID '${componentId}' not found`, {
2629
+ assembliesRegistry,
2630
+ });
2631
+ return node;
2632
+ }
2633
+ const componentFields = entityStore?.getValue(assembly, [
2634
+ 'fields',
2635
+ ]);
2636
+ if (!componentFields) {
2637
+ console.warn(`Entry for assembly with ID '${componentId}' not found`, { entityStore });
2638
+ return node;
2639
+ }
2640
+ if (!componentFields.componentTree?.children) {
2641
+ console.warn(`Component tree for assembly with ID '${componentId}' not found`, {
2642
+ componentFields,
2643
+ });
2644
+ }
2645
+ const deserializedNode = deserializeAssemblyNode({
2646
+ node: {
2647
+ definitionId: node.data.blockId || '',
2648
+ variables: {},
2649
+ children: componentFields.componentTree?.children ?? [],
2650
+ },
2651
+ nodeLocation: null,
2652
+ nodeId: node.data.id,
2653
+ parentId: node.parentId,
2654
+ assemblyDataSource: {},
2655
+ assemblyId: assembly.sys.id,
2656
+ assemblyComponentId: node.data.id,
2657
+ assemblyUnboundValues: componentFields.unboundValues,
2658
+ componentInstanceProps: node.data.props,
2659
+ componentInstanceUnboundValues: node.data.unboundValues,
2660
+ componentInstanceDataSource: node.data.dataSource,
2661
+ });
2662
+ return deserializedNode;
2663
+ };
2664
+
2665
+ const useComponent = ({ node: rawNode, resolveDesignValue, renderDropzone, userIsDragging, }) => {
2666
+ const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
2667
+ const entityStore = useEntityStore((state) => state.entityStore);
2668
+ const node = useMemo(() => {
2669
+ if (rawNode.type === ASSEMBLY_NODE_TYPE && areEntitiesFetched) {
2670
+ return resolveAssembly({
2671
+ node: rawNode,
2672
+ entityStore,
2673
+ });
2674
+ }
2675
+ return rawNode;
2676
+ }, [areEntitiesFetched, rawNode, entityStore]);
2677
+ const componentRegistration = useMemo(() => {
2678
+ const registration = componentRegistry.get(node.data.blockId);
2679
+ if (node.type === ASSEMBLY_NODE_TYPE && !registration) {
2680
+ return createAssemblyRegistration({
2681
+ definitionId: node.data.blockId,
2682
+ component: Assembly,
2683
+ });
2684
+ }
2685
+ else if (!registration) {
2686
+ console.warn(`[experiences-sdk-react] Component registration not found for ${node.data.blockId}`);
2687
+ }
2688
+ return registration;
2689
+ }, [node]);
2690
+ const componentId = node.data.id;
2691
+ const { componentProps, wrapperProps } = useComponentProps({
2692
+ node,
2693
+ areEntitiesFetched,
2694
+ resolveDesignValue,
2695
+ renderDropzone,
2696
+ definition: componentRegistration.definition,
2697
+ userIsDragging,
2698
+ });
2699
+ // Only pass editor props to built-in components
2700
+ const { editorMode, renderDropzone: _renderDropzone, ...otherComponentProps } = componentProps;
2701
+ const elementToRender = builtInComponents.includes(node.data.blockId || '')
2702
+ ? (dragProps) => React.createElement(componentRegistration.component, { ...dragProps, ...componentProps })
2703
+ : node.type === ASSEMBLY_NODE_TYPE
2704
+ ? // Assembly.tsx requires renderDropzone and editorMode as well
2705
+ () => React.createElement(componentRegistration.component, componentProps)
2706
+ : () => React.createElement(componentRegistration.component, otherComponentProps);
2707
+ return {
2708
+ node,
2709
+ componentId,
2710
+ elementToRender,
2711
+ wrapperProps,
2712
+ definition: componentRegistration.definition,
2713
+ };
2714
+ };
2715
+
2716
+ /**
2717
+ * This component is meant to function the same as DraggableComponent except
2718
+ * with the difference that the draggable props are passed to the underlying
2719
+ * component. This removes an extra nexted `div` in editor mode that otherwise
2720
+ * is not visible in delivery mode.
2721
+ *
2722
+ * This is helpful for `flex` or `grid` layouts. Currently used by the SingleColumn
2723
+ * component.
2724
+ */
2725
+ const DraggableChildComponent = (props) => {
2726
+ const { elementToRender, id, index, isAssemblyBlock = false, isSelected = false, onClick = () => null, coordinates, userIsDragging, style, isContainer, blockId, isDragDisabled = false, wrapperProps, definition, } = props;
2727
+ return (React.createElement(Draggable, { key: id, draggableId: id, index: index, isDragDisabled: isDragDisabled }, (provided, snapshot) => elementToRender({
2728
+ ['data-ctfl-draggable-id']: id,
2729
+ ['data-test-id']: `draggable-${blockId}`,
2730
+ innerRef: provided.innerRef,
2731
+ ...wrapperProps,
2732
+ draggableProps: provided.draggableProps,
2733
+ wrapperClassName: classNames(styles$3.DraggableComponent, wrapperProps.className, {
2734
+ [styles$3.isAssemblyBlock]: isAssemblyBlock,
2735
+ [styles$3.isDragging]: snapshot.isDragging,
2736
+ [styles$3.isSelected]: isSelected,
2737
+ [styles$3.userIsDragging]: userIsDragging,
2738
+ }),
2739
+ dragHandleProps: provided.dragHandleProps,
2740
+ style: {
2741
+ ...style,
2742
+ ...provided.draggableProps.style,
2743
+ },
2744
+ onClick,
2745
+ Tooltip: (React.createElement(Tooltip, { id: id, coordinates: coordinates, isAssemblyBlock: isAssemblyBlock, isContainer: isContainer, label: definition.name || 'No label specified' })),
2746
+ })));
2747
+ };
2748
+
2749
+ var css_248z$2 = ".styles-module_container__te-1H {\n margin-left: auto;\n margin-right: auto;\n position: relative;\n height: 100%;\n width: 100%;\n background-color: transparent;\n transition: background-color 0.2s;\n pointer-events: all !important;\n}\n\n.styles-module_container__te-1H:not(.styles-module_isRoot__5cn-i):before {\n content: '';\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n outline-offset: -1px;\n outline: 2px solid transparent;\n z-index: 1;\n transition: outline 0.2s;\n pointer-events: none;\n}\n\n.styles-module_isRoot__5cn-i,\n.styles-module_isEmptyCanvas__0XHZR {\n flex: 1;\n}\n\n.styles-module_isEmptyZone__zVpnZ {\n min-height: 80px;\n}\n\n.styles-module_isDragging__Gm8v5:not(.styles-module_isRoot__5cn-i):before {\n outline: 2px dashed var(--exp-builder-gray300);\n}\n\n.styles-module_isDestination__5sCQx:not(.styles-module_isRoot__5cn-i):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_hitbox__YQ-1Z {\n position: fixed;\n pointer-events: all !important;\n}\n\n.styles-module_hitbox__YQ-1Z {\n position: fixed;\n pointer-events: all !important;\n}\n";
2750
+ var styles$2 = {"container":"styles-module_container__te-1H","isRoot":"styles-module_isRoot__5cn-i","isEmptyCanvas":"styles-module_isEmptyCanvas__0XHZR","isEmptyZone":"styles-module_isEmptyZone__zVpnZ","isDragging":"styles-module_isDragging__Gm8v5","isDestination":"styles-module_isDestination__5sCQx","hitbox":"styles-module_hitbox__YQ-1Z"};
2751
+ styleInject(css_248z$2);
2752
+
2753
+ function getItemFromTree(id, node) {
2754
+ // Check if the current node's id matches the search id
2755
+ if (node.data.id === id) {
2756
+ return node;
2757
+ }
2758
+ // Recursively search through each child
2759
+ for (const child of node.children) {
2760
+ const foundNode = getItemFromTree(id, child);
2761
+ if (foundNode) {
2762
+ // Node found in children
2763
+ return foundNode;
2764
+ }
2765
+ }
2766
+ // If the node is not found in this branch of the tree, return undefined
2767
+ return undefined;
2768
+ }
2769
+ function findDepthById(node, id, currentDepth = 1) {
2770
+ if (node.data.id === id) {
2771
+ return currentDepth;
2772
+ }
2773
+ // If the node has children, check each one
2774
+ for (const child of node.children) {
2775
+ const childDepth = findDepthById(child, id, currentDepth + 1);
2776
+ if (childDepth !== -1) {
2777
+ return childDepth; // Found the node in a child
2778
+ }
2779
+ }
2780
+ return -1; // Node not found in this branch
2781
+ }
2782
+ const getChildFromTree = (parentId, index, node) => {
2783
+ // Check if the current node's id matches the search id
2784
+ if (node.data.id === parentId) {
2785
+ return node.children[index];
2786
+ }
2787
+ // Recursively search through each child
2788
+ for (const child of node.children) {
2789
+ const foundNode = getChildFromTree(parentId, index, child);
2790
+ if (foundNode) {
2791
+ // Node found in children
2792
+ return foundNode;
2793
+ }
2794
+ }
2795
+ // If the node is not found in this branch of the tree, return undefined
2796
+ return undefined;
2797
+ };
2798
+ const getItem = (selector, tree) => {
2799
+ return getItemFromTree(selector.id, {
2800
+ type: 'block',
2801
+ data: {
2802
+ id: ROOT_ID,
2803
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2804
+ },
2805
+ children: tree.root.children,
2806
+ });
2807
+ };
2808
+ const getItemDepthFromNode = (selector, node) => {
2809
+ return findDepthById(node, selector.id);
2810
+ };
2811
+
2812
+ function updateNode(nodeId, updatedNode, node) {
2813
+ if (node.data.id === nodeId) {
2814
+ node.data = updatedNode.data;
2815
+ return;
2816
+ }
2817
+ node.children.forEach((childNode) => updateNode(nodeId, updatedNode, childNode));
2818
+ }
2819
+ function replaceNode(indexToReplace, updatedNode, node) {
2820
+ if (node.data.id === updatedNode.parentId) {
2821
+ node.children = [
2822
+ ...node.children.slice(0, indexToReplace),
2823
+ updatedNode,
2824
+ ...node.children.slice(indexToReplace + 1),
2825
+ ];
2826
+ return;
2827
+ }
2828
+ node.children.forEach((childNode) => replaceNode(indexToReplace, updatedNode, childNode));
2829
+ }
2830
+ function removeChildNode(indexToRemove, nodeId, parentNodeId, node) {
2831
+ if (node.data.id === parentNodeId) {
2832
+ const childIndex = node.children.findIndex((child) => child.data.id === nodeId);
2833
+ node.children.splice(childIndex === -1 ? indexToRemove : childIndex, 1);
2834
+ return;
2835
+ }
2836
+ node.children.forEach((childNode) => removeChildNode(indexToRemove, nodeId, parentNodeId, childNode));
2837
+ }
2838
+ function addChildNode(indexToAdd, parentNodeId, nodeToAdd, node) {
2839
+ if (node.data.id === parentNodeId) {
2840
+ node.children = [
2841
+ ...node.children.slice(0, indexToAdd),
2842
+ nodeToAdd,
2843
+ ...node.children.slice(indexToAdd),
2844
+ ];
2845
+ return;
2846
+ }
2847
+ node.children.forEach((childNode) => addChildNode(indexToAdd, parentNodeId, nodeToAdd, childNode));
2848
+ }
2849
+ function reorderChildNode(oldIndex, newIndex, parentNodeId, node) {
2850
+ if (node.data.id === parentNodeId) {
2851
+ // Remove the child from the old position
2852
+ const [childToMove] = node.children.splice(oldIndex, 1);
2853
+ // Insert the child at the new position
2854
+ node.children.splice(newIndex, 0, childToMove);
2855
+ return;
2856
+ }
2857
+ node.children.forEach((childNode) => reorderChildNode(oldIndex, newIndex, parentNodeId, childNode));
2858
+ }
2859
+ function reparentChildNode(oldIndex, newIndex, sourceNodeId, destinationNodeId, node) {
2860
+ const nodeToMove = getChildFromTree(sourceNodeId, oldIndex, node);
2861
+ if (!nodeToMove) {
2862
+ return;
2863
+ }
2864
+ removeChildNode(oldIndex, nodeToMove.data.id, sourceNodeId, node);
2865
+ addChildNode(newIndex, destinationNodeId, nodeToMove, node);
2866
+ }
2867
+
2868
+ function missingNodeAction({ index, nodeAdded, child, tree, parentNodeId, currentNode, }) {
2869
+ if (nodeAdded) {
2870
+ return { type: TreeAction.ADD_NODE, indexToAdd: index, nodeToAdd: child, parentNodeId };
2871
+ }
2872
+ const item = getItem({ id: child.data.id }, tree);
2873
+ if (item) {
2874
+ const parentNode = getItem({ id: item.parentId }, tree);
2875
+ if (!parentNode) {
2876
+ return null;
2877
+ }
2878
+ const sourceIndex = parentNode.children.findIndex((c) => c.data.id === child.data.id);
2879
+ return { type: TreeAction.MOVE_NODE, sourceIndex, destinationIndex: index, parentNodeId };
2880
+ }
2881
+ return {
2882
+ type: TreeAction.REPLACE_NODE,
2883
+ originalId: currentNode.children[index].data.id,
2884
+ indexToReplace: index,
2885
+ node: child,
2886
+ };
2887
+ }
2888
+ function matchingNodeAction({ index, originalIndex, nodeRemoved, nodeAdded, parentNodeId, }) {
2889
+ if (index !== originalIndex && !nodeRemoved && !nodeAdded) {
2890
+ return {
2891
+ type: TreeAction.REORDER_NODE,
2892
+ sourceIndex: originalIndex,
2893
+ destinationIndex: index,
2894
+ parentNodeId,
2895
+ };
2896
+ }
2897
+ return null;
2898
+ }
2899
+ function compareNodes({ currentNode, updatedNode, originalTree, differences = [], }) {
2900
+ // In the end, this map contains the list of nodes that are not present
2901
+ // in the updated tree and must be removed
2902
+ const map = new Map();
2903
+ if (!currentNode || !updatedNode) {
2904
+ return differences;
2905
+ }
2906
+ // On each tree level, consider only the children of the current node to differentiate between added, removed, or replaced case
2907
+ const currentNodeCount = currentNode.children.length;
2908
+ const updatedNodeCount = updatedNode.children.length;
2909
+ const nodeRemoved = currentNodeCount > updatedNodeCount;
2910
+ const nodeAdded = currentNodeCount < updatedNodeCount;
2911
+ const parentNodeId = updatedNode.data.id;
2912
+ const isRoot = currentNode.data.id === ROOT_ID;
2913
+ /**
2914
+ * The data of the current node has changed, we need to update
2915
+ * this node to reflect the data change. (design, content, unbound values)
2916
+ */
2917
+ if (!isRoot && !isEqual(currentNode.data, updatedNode.data)) {
2918
+ differences.push({
2919
+ type: TreeAction.UPDATE_NODE,
2920
+ nodeId: currentNode.data.id,
2921
+ node: updatedNode,
2922
+ });
2923
+ }
2924
+ // Map children of the first tree by their ID
2925
+ currentNode.children.forEach((child, index) => map.set(child.data.id, index));
2926
+ // Compare with the second tree
2927
+ updatedNode.children.forEach((child, index) => {
2928
+ const childId = child.data.id;
2929
+ // The original tree does not have this node in the updated tree.
2930
+ if (!map.has(childId)) {
2931
+ const diff = missingNodeAction({
2932
+ index,
2933
+ child,
2934
+ nodeAdded,
2935
+ parentNodeId,
2936
+ tree: originalTree,
2937
+ currentNode,
2938
+ });
2939
+ if (diff?.type === TreeAction.REPLACE_NODE) {
2940
+ // Remove it from the deletion map to avoid adding another REMOVE_NODE action
2941
+ map.delete(diff.originalId);
2942
+ }
2943
+ return differences.push(diff);
2944
+ }
2945
+ const originalIndex = map.get(childId);
2946
+ const diff = matchingNodeAction({
2947
+ index,
2948
+ originalIndex,
2949
+ nodeAdded,
2950
+ nodeRemoved,
2951
+ parentNodeId,
2952
+ });
2953
+ differences.push(diff);
2954
+ map.delete(childId);
2955
+ compareNodes({
2956
+ currentNode: currentNode.children[originalIndex],
2957
+ updatedNode: child,
2958
+ originalTree,
2959
+ differences,
2960
+ });
2961
+ });
2962
+ map.forEach((index, key) => {
2963
+ // If the node count of the entire tree doesn't signify
2964
+ // a node was removed, don't add that as a diff
2965
+ if (!nodeRemoved) {
2966
+ return;
2967
+ }
2968
+ // Remaining nodes in the map are removed in the second tree
2969
+ differences.push({
2970
+ type: TreeAction.REMOVE_NODE,
2971
+ indexToRemove: index,
2972
+ parentNodeId,
2973
+ idToRemove: key,
2974
+ });
2975
+ });
2976
+ return differences;
2977
+ }
2978
+ function getTreeDiffs(tree1, tree2, originalTree) {
2979
+ const differences = [];
2980
+ compareNodes({
2981
+ currentNode: tree1,
2982
+ updatedNode: tree2,
2983
+ originalTree,
2984
+ differences,
2985
+ });
2986
+ return differences.filter((diff) => diff);
2987
+ }
2988
+
2989
+ /**
2990
+ * @deprecated in favor of one in 'core' package
2991
+ */
2992
+ function treeVisit(initialNode, onNode) {
2993
+ // returns last used index
2994
+ const _treeVisit = (currentNode, currentIndex, currentDepth) => {
2995
+ // Copy children in case of onNode removing it as we pass the node by reference
2996
+ const children = [...currentNode.children];
2997
+ onNode(currentNode, currentIndex, currentDepth);
2998
+ let nextAvailableIndex = currentIndex + 1;
2999
+ const lastUsedIndex = currentIndex;
3000
+ for (const child of children) {
3001
+ const lastUsedIndex = _treeVisit(child, nextAvailableIndex, currentDepth + 1);
3002
+ nextAvailableIndex = lastUsedIndex + 1;
3003
+ }
3004
+ return lastUsedIndex;
3005
+ };
3006
+ _treeVisit(initialNode, 0, 0);
3007
+ }
3008
+
3009
+ const isAssemblyNode = (node) => {
3010
+ return node.type === ASSEMBLY_NODE_TYPE;
3011
+ };
3012
+ const useTreeStore = create((set, get) => ({
3013
+ tree: {
3014
+ root: {
3015
+ children: [],
3016
+ type: 'root',
3017
+ data: {
3018
+ breakpoints: [],
3019
+ dataSource: {},
3020
+ id: ROOT_ID,
3021
+ props: {},
3022
+ unboundValues: {},
3023
+ },
3024
+ },
3025
+ },
3026
+ breakpoints: [],
3027
+ updateNodesByUpdatedEntity: (entityId) => {
3028
+ set(produce((draftState) => {
3029
+ treeVisit(draftState.tree.root, (node) => {
3030
+ if (isAssemblyNode(node) && node.data.blockId === entityId) {
3031
+ // Cannot use `structuredClone()` as node is probably a Proxy object with weird references
3032
+ updateNode(node.data.id, cloneDeepAsPOJO(node), draftState.tree.root);
3033
+ return;
3034
+ }
3035
+ const dataSourceIds = Object.values(node.data.dataSource).map((link) => link.sys.id);
3036
+ if (dataSourceIds.includes(entityId)) {
3037
+ // Cannot use `structuredClone()` as node is probably a Proxy object with weird references
3038
+ updateNode(node.data.id, cloneDeepAsPOJO(node), draftState.tree.root);
3039
+ }
3040
+ });
3041
+ }));
3042
+ },
3043
+ /**
3044
+ * NOTE: this is for debugging purposes only as it causes ugly canvas flash.
3045
+ *
3046
+ * Force updates entire tree. Usually shouldn't be used as updateTree()
3047
+ * uses smart update algorithm based on diffs. But for troubleshooting
3048
+ * you may want to force update the tree so leaving this in.
3049
+ */
3050
+ updateTreeForced: (tree) => {
3051
+ set({
3052
+ tree,
3053
+ // Breakpoints must be updated, as we receive completely new tree with possibly new breakpoints
3054
+ breakpoints: tree?.root?.data?.breakpoints || [],
3055
+ });
3056
+ },
3057
+ updateTree: (tree) => {
3058
+ const currentTree = get().tree;
3059
+ /**
3060
+ * If we simply update the tree as in:
3061
+ *
3062
+ * `state.tree = tree`
3063
+ *
3064
+ * we end up causing a lot of unnecesary rerenders which can lead to
3065
+ * flickering of the component layout. Instead, we use this function
3066
+ * to deteremine exactly which nodes in the tree changed and combined
3067
+ * with immer, we end up updating only the changed nodes instead of
3068
+ * rerendering the entire tree.
3069
+ */
3070
+ const treeDiff = getTreeDiffs({ ...currentTree.root }, { ...tree.root }, currentTree);
3071
+ // The current and updated tree are the same, no tree update required.
3072
+ if (!treeDiff.length) {
3073
+ console.debug(`[exp-builder.visual-editor::updateTree()]: During smart-diffing no diffs. Skipping tree update.`);
3074
+ return;
3075
+ }
3076
+ set(produce((state) => {
3077
+ treeDiff.map((diff) => {
3078
+ switch (diff.type) {
3079
+ case TreeAction.ADD_NODE:
3080
+ addChildNode(diff.indexToAdd, diff.parentNodeId, diff.nodeToAdd, state.tree.root);
3081
+ break;
3082
+ case TreeAction.REPLACE_NODE:
3083
+ replaceNode(diff.indexToReplace, diff.node, state.tree.root);
3084
+ break;
3085
+ case TreeAction.UPDATE_NODE:
3086
+ updateNode(diff.nodeId, diff.node, state.tree.root);
3087
+ break;
3088
+ case TreeAction.REMOVE_NODE:
3089
+ removeChildNode(diff.indexToRemove, diff.idToRemove, diff.parentNodeId, state.tree.root);
3090
+ break;
3091
+ case TreeAction.MOVE_NODE:
3092
+ case TreeAction.REORDER_NODE:
3093
+ state.tree = tree;
3094
+ break;
3095
+ }
3096
+ });
3097
+ state.breakpoints = tree?.root?.data?.breakpoints || [];
3098
+ }));
3099
+ },
3100
+ addChild: (index, parentId, node) => {
3101
+ set(produce((state) => {
3102
+ addChildNode(index, parentId, node, state.tree.root);
3103
+ }));
3104
+ },
3105
+ reorderChildren: (destinationIndex, destinationParentId, sourceIndex) => {
3106
+ set(produce((state) => {
3107
+ reorderChildNode(sourceIndex, destinationIndex, destinationParentId, state.tree.root);
3108
+ }));
3109
+ },
3110
+ reparentChild: (destinationIndex, destinationParentId, sourceIndex, sourceParentId) => {
3111
+ set(produce((state) => {
3112
+ reparentChildNode(sourceIndex, destinationIndex, sourceParentId, destinationParentId, state.tree.root);
3113
+ }));
3114
+ },
3115
+ }));
3116
+ // Serialize and deserialize an object again to remove all functions and references.
3117
+ // Some people refer to this as "Plain Old JavaScript Object" (POJO) as it solely contains plain data.
3118
+ function cloneDeepAsPOJO(obj) {
3119
+ return JSON.parse(JSON.stringify(obj));
3120
+ }
3121
+
3122
+ const useZoneStore = create()((set) => ({
3123
+ zones: {},
3124
+ hoveringZone: '',
3125
+ setHoveringZone(zoneId) {
3126
+ set({
3127
+ hoveringZone: zoneId,
3128
+ });
3129
+ },
3130
+ upsertZone(id, data) {
3131
+ set(produce((state) => {
3132
+ state.zones[id] = { ...(state.zones[id] || {}), ...data };
3133
+ }));
3134
+ },
3135
+ }));
3136
+
3137
+ const { WIDTH, HEIGHT, INITIAL_OFFSET, OFFSET_INCREMENT, MIN_HEIGHT, MIN_DEPTH_HEIGHT, DEEP_ZONE } = HITBOX;
3138
+ const calcOffsetDepth = (depth) => {
3139
+ return INITIAL_OFFSET - OFFSET_INCREMENT * depth;
3140
+ };
3141
+ const getHitboxStyles = ({ direction, zoneDepth, domRect }) => {
3142
+ if (!domRect) {
3143
+ return {
3144
+ display: 'none',
3145
+ };
3146
+ }
3147
+ const { width, height, top, left, bottom, right } = domRect;
3148
+ const MAX_SELF_HEIGHT = DRAGGABLE_HEIGHT * 2;
3149
+ const isDeepZone = zoneDepth > DEEP_ZONE;
3150
+ const isAboveMaxHeight = height > MAX_SELF_HEIGHT;
3151
+ switch (direction) {
3152
+ case HitboxDirection.TOP:
3153
+ return {
3154
+ width,
3155
+ height: HEIGHT,
3156
+ top: top - calcOffsetDepth(zoneDepth) - scrollY,
3157
+ left,
3158
+ zIndex: 100 + zoneDepth,
3159
+ };
3160
+ case HitboxDirection.BOTTOM:
3161
+ return {
3162
+ width,
3163
+ height: HEIGHT,
3164
+ top: bottom + calcOffsetDepth(zoneDepth) - scrollY,
3165
+ left,
3166
+ zIndex: 100 + zoneDepth,
3167
+ };
3168
+ case HitboxDirection.LEFT:
3169
+ return {
3170
+ width: WIDTH,
3171
+ height: height - HEIGHT,
3172
+ left: left - calcOffsetDepth(zoneDepth) - WIDTH / 2,
3173
+ top: top + HEIGHT / 2 - scrollY,
3174
+ zIndex: 100 + zoneDepth,
3175
+ };
3176
+ case HitboxDirection.RIGHT:
3177
+ return {
3178
+ width: WIDTH,
3179
+ height: height - HEIGHT,
3180
+ left: right - calcOffsetDepth(zoneDepth) - WIDTH / 2,
3181
+ top: top + HEIGHT / 2 - scrollY,
3182
+ zIndex: 100 + zoneDepth,
3183
+ };
3184
+ case HitboxDirection.SELF_VERTICAL: {
3185
+ if (isAboveMaxHeight && !isDeepZone) {
3186
+ return { display: 'none' };
3187
+ }
3188
+ const selfHeight = isDeepZone ? MIN_DEPTH_HEIGHT : MIN_HEIGHT;
3189
+ return {
3190
+ width,
3191
+ height: selfHeight,
3192
+ left,
3193
+ top: top + height / 2 - selfHeight / 2 - scrollY,
3194
+ zIndex: 1000 + zoneDepth,
3195
+ };
3196
+ }
3197
+ case HitboxDirection.SELF_HORIZONTAL: {
3198
+ if (width > DRAGGABLE_WIDTH) {
3199
+ return { display: 'none' };
3200
+ }
3201
+ return {
3202
+ width: width - DRAGGABLE_WIDTH * 2,
3203
+ height,
3204
+ left: left + (DRAGGABLE_WIDTH * 2) / 2,
3205
+ top: top - scrollY,
3206
+ zIndex: 1000 + zoneDepth,
3207
+ };
3208
+ }
3209
+ default:
3210
+ return {};
3211
+ }
3212
+ };
3213
+
3214
+ const Hitboxes = ({ zoneId, parentZoneId, enableRootHitboxes }) => {
3215
+ const tree = useTreeStore((state) => state.tree);
3216
+ const isDraggingOnCanvas = useDraggedItemStore((state) => state.isDraggingOnCanvas);
3217
+ const scrollY = useDraggedItemStore((state) => state.scrollY);
3218
+ const zoneDepth = useMemo(() => getItemDepthFromNode({ id: parentZoneId }, tree.root), [tree, parentZoneId]);
3219
+ const [fetchDomRect, setFetchDomRect] = useState(Date.now());
3220
+ useEffect(() => {
3221
+ /**
3222
+ * A bit hacky but we need to wait a very small amount
3223
+ * of time to fetch the dom getBoundingClientRect once a
3224
+ * drag starts because we need pre-drag styles like padding
3225
+ * applied before we calculate positions of hitboxes
3226
+ */
3227
+ setTimeout(() => {
3228
+ setFetchDomRect(Date.now());
3229
+ }, 50);
3230
+ }, [isDraggingOnCanvas]);
3231
+ const hitboxContainer = useMemo(() => {
3232
+ return document.querySelector('[data-ctfl-hitboxes]');
3233
+ }, []);
3234
+ const domRect = useMemo(() => {
3235
+ return document.querySelector(`[${CTFL_ZONE_ID}="${zoneId}"]`)?.getBoundingClientRect();
3236
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3237
+ }, [zoneId, fetchDomRect]);
3238
+ const zones = useZoneStore((state) => state.zones);
3239
+ const zoneDirection = zones[parentZoneId]?.direction || 'vertical';
3240
+ const isVertical = zoneDirection === 'vertical';
3241
+ const isRoot = parentZoneId === ROOT_ID;
3242
+ const showRootHitboxes = isRoot && enableRootHitboxes;
3243
+ const getStyles = useCallback((direction) => getHitboxStyles({ direction, zoneDepth, domRect, scrollY }), [zoneDepth, domRect, scrollY]);
3244
+ const ActiveHitboxes = (React.createElement(React.Fragment, null,
3245
+ React.createElement("div", { "data-ctfl-zone-id": zoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.SELF_VERTICAL : HitboxDirection.SELF_HORIZONTAL) }),
3246
+ showRootHitboxes && (React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(HitboxDirection.BOTTOM) })),
3247
+ !isRoot && (React.createElement(React.Fragment, null,
3248
+ React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.TOP : HitboxDirection.LEFT) }),
3249
+ React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.BOTTOM : HitboxDirection.RIGHT) })))));
3250
+ if (!hitboxContainer) {
3251
+ return null;
3252
+ }
3253
+ return createPortal(ActiveHitboxes, hitboxContainer);
3254
+ };
3255
+
3256
+ const EditorBlock = ({ node: rawNode, resolveDesignValue, renderDropzone, draggingNewComponent, index, zoneId, userIsDragging, placeholder, }) => {
3257
+ const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
3258
+ const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
3259
+ const { node, componentId, wrapperProps, definition, elementToRender } = useComponent({
3260
+ node: rawNode,
3261
+ resolveDesignValue,
3262
+ renderDropzone,
3263
+ userIsDragging,
3264
+ });
3265
+ const coordinates = useSelectedInstanceCoordinates({ node });
3266
+ const isContainer = node.data.blockId === CONTENTFUL_COMPONENTS.container.id;
3267
+ const isSingleColumn = node.data.blockId === CONTENTFUL_COMPONENTS.singleColumn.id;
3268
+ const isAssemblyBlock = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
3269
+ const isAssembly = node.type === ASSEMBLY_NODE_TYPE;
3270
+ const isStructureComponent = isContentfulStructureComponent(node.data.blockId);
3271
+ const isRootComponent = zoneId === ROOT_ID;
3272
+ const enableRootHitboxes = isRootComponent && !!draggingNewComponent;
3273
+ const onClick = (e) => {
3274
+ e.stopPropagation();
3275
+ if (!userIsDragging) {
3276
+ setSelectedNodeId(node.data.id);
3277
+ // if it is the assembly directly we just want to select it as a normal component
3278
+ if (isAssembly) {
3279
+ sendMessage(OUTGOING_EVENTS.ComponentSelected, {
3280
+ nodeId: node.data.id,
3281
+ });
3282
+ return;
3283
+ }
3284
+ sendMessage(OUTGOING_EVENTS.ComponentSelected, {
3285
+ assembly: node.data.assembly,
3286
+ nodeId: node.data.id,
3287
+ });
3288
+ }
3289
+ };
3290
+ if (node.data.blockId === CONTENTFUL_COMPONENTS.singleColumn.id) {
3291
+ return (React.createElement(React.Fragment, null,
3292
+ React.createElement(DraggableChildComponent, { elementToRender: elementToRender, id: componentId, index: index, isAssemblyBlock: isAssemblyBlock, isDragDisabled: isSingleColumn, isSelected: selectedNodeId === componentId, userIsDragging: userIsDragging, isContainer: isContainer, blockId: node.data.blockId, coordinates: coordinates, wrapperProps: wrapperProps, onClick: onClick, definition: definition }),
3293
+ isStructureComponent && !isSingleColumn && userIsDragging && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId, enableRootHitboxes: enableRootHitboxes }))));
3294
+ }
3295
+ return (React.createElement(DraggableComponent, { placeholder: placeholder, definition: definition, id: componentId, index: index, isAssemblyBlock: isAssemblyBlock, isDragDisabled: isAssemblyBlock, isSelected: selectedNodeId === componentId, userIsDragging: userIsDragging, isContainer: isContainer, blockId: node.data.blockId, coordinates: coordinates, wrapperProps: wrapperProps, onClick: onClick },
3296
+ elementToRender(),
3297
+ isStructureComponent && !isSingleColumn && userIsDragging && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId, enableRootHitboxes: enableRootHitboxes }))));
3298
+ };
3299
+
3300
+ var css_248z$1 = ".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";
3301
+ var styles$1 = {"container":"EmptyContainer-module_container__XPH5b","highlight":"EmptyContainer-module_highlight__lcICy","icon":"EmptyContainer-module_icon__82-2O","label":"EmptyContainer-module_label__4TxRa"};
3302
+ styleInject(css_248z$1);
3303
+
3304
+ const EmptyContainer = ({ isDragging }) => {
3305
+ return (React.createElement("div", { className: classNames(styles$1.container, {
3306
+ [styles$1.highlight]: isDragging,
3307
+ }), "data-type": "empty-container" },
3308
+ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "37", height: "36", fill: "none", className: styles$1.icon },
3309
+ React.createElement("rect", { width: "11.676", height: "11.676", x: "18.512", y: ".153", rx: "1.621", transform: "rotate(45 18.512 .153)" }),
3310
+ 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)" }),
3311
+ 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)" }),
3312
+ 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)" }),
3313
+ 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" })),
3314
+ React.createElement("span", { className: styles$1.label }, "Add components to begin")));
3315
+ };
3316
+
3317
+ const getZoneParents = (zoneId) => {
3318
+ const element = document.querySelector(`[data-rfd-droppable-id='${zoneId}']`);
3319
+ if (!element) {
3320
+ return [];
3321
+ }
3322
+ function getZonesToRoot(element, parentIds = []) {
3323
+ if (!element) {
3324
+ return parentIds;
3325
+ }
3326
+ const attribute = element.getAttribute('data-rfd-droppable-id');
3327
+ if (attribute === ROOT_ID) {
3328
+ return parentIds;
3329
+ }
3330
+ if (attribute) {
3331
+ parentIds.push(attribute);
3332
+ }
3333
+ return getZonesToRoot(element.parentElement, parentIds);
3334
+ }
3335
+ return getZonesToRoot(element);
3336
+ };
3337
+
3338
+ const useDropzoneDirection = ({ resolveDesignValue, node, zoneId }) => {
3339
+ const zone = useZoneStore((state) => state.zones);
3340
+ const upsertZone = useZoneStore((state) => state.upsertZone);
3341
+ useEffect(() => {
3342
+ function getDirection() {
3343
+ if (!node || !node.data.blockId) {
3344
+ return 'vertical';
3345
+ }
3346
+ if (!isContentfulStructureComponent(node.data.blockId)) {
3347
+ return 'vertical';
3348
+ }
3349
+ if (node.data.blockId === CONTENTFUL_COMPONENTS.columns.id) {
3350
+ return 'horizontal';
3351
+ }
3352
+ const designValues = node.data.props['cfFlexDirection'];
3353
+ if (!designValues || !resolveDesignValue || designValues.type !== 'DesignValue') {
3354
+ return 'vertical';
3355
+ }
3356
+ const direction = resolveDesignValue(designValues.valuesByBreakpoint, 'cfFlexDirection');
3357
+ if (direction === 'row') {
3358
+ return 'horizontal';
3359
+ }
3360
+ return 'vertical';
3361
+ }
3362
+ upsertZone(zoneId, { direction: getDirection() });
3363
+ }, [node, resolveDesignValue, zoneId, upsertZone]);
3364
+ return zone[zoneId]?.direction || 'vertical';
3365
+ };
3366
+
3367
+ function getStyle$1(style = {}, snapshot) {
3368
+ if (!snapshot?.isDropAnimating) {
3369
+ return style;
3370
+ }
3371
+ return {
3372
+ ...style,
3373
+ // cannot be 0, but make it super tiny
3374
+ transitionDuration: `0.001s`,
3375
+ };
3376
+ }
3377
+ const EditorBlockClone = ({ node: rawNode, resolveDesignValue, snapshot, provided, renderDropzone, }) => {
3378
+ const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
3379
+ const { node, wrapperProps, elementToRender } = useComponent({
3380
+ node: rawNode,
3381
+ resolveDesignValue,
3382
+ renderDropzone,
3383
+ userIsDragging,
3384
+ });
3385
+ const isAssemblyBlock = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
3386
+ const isSingleColumn = node.data.blockId === CONTENTFUL_COMPONENTS.singleColumn.id;
3387
+ if (isSingleColumn) {
3388
+ return elementToRender();
3389
+ }
3390
+ return (React.createElement("div", { ref: provided?.innerRef, "data-ctfl-dragging-element": true, ...wrapperProps, ...provided?.draggableProps, ...provided?.dragHandleProps, className: classNames(styles$3.DraggableComponent, wrapperProps.className, styles$3.DraggableClone, {
3391
+ [styles$3.isAssemblyBlock]: isAssemblyBlock,
3392
+ [styles$3.isDragging]: snapshot?.isDragging,
3393
+ }), style: getStyle$1(provided?.draggableProps.style, snapshot) }, elementToRender()));
3394
+ };
3395
+
3396
+ function DropzoneClone({ node, zoneId, resolveDesignValue, className, WrapperComponent = 'div', renderDropzone, ...rest }) {
3397
+ const tree = useTreeStore((state) => state.tree);
3398
+ const content = node?.children || tree.root?.children || [];
3399
+ const isRootZone = zoneId === ROOT_ID;
3400
+ if (!resolveDesignValue) {
3401
+ return null;
3402
+ }
3403
+ return (React.createElement(WrapperComponent, { className: classNames(styles$2.container, {
3404
+ [styles$2.isRoot]: isRootZone,
3405
+ [styles$2.isEmptyZone]: !content.length,
3406
+ }, className), node: node, ...rest }, content.map((item) => {
3407
+ const componentId = item.data.id;
3408
+ return (React.createElement(EditorBlockClone, { key: componentId, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone }));
3409
+ })));
3410
+ }
3411
+
3412
+ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponent = 'div', ...rest }) {
3413
+ const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
3414
+ const draggedItem = useDraggedItemStore((state) => state.draggedItem);
3415
+ const newComponentId = useDraggedItemStore((state) => state.componentId);
3416
+ const hoveringZone = useZoneStore((state) => state.hoveringZone);
3417
+ const tree = useTreeStore((state) => state.tree);
3418
+ const content = node?.children || tree.root?.children || [];
3419
+ const direction = useDropzoneDirection({ resolveDesignValue, node, zoneId });
3420
+ const draggedSourceId = draggedItem && draggedItem.source.droppableId;
3421
+ const draggedDestinationId = draggedItem && draggedItem.destination?.droppableId;
3422
+ const isDraggingNewComponent = !!newComponentId;
3423
+ const isHoveringZone = hoveringZone === zoneId;
3424
+ const isRootZone = zoneId === ROOT_ID;
3425
+ const isDestination = draggedDestinationId === zoneId;
3426
+ const isEmptyCanvas = isRootZone && !content.length;
3427
+ const isAssembly = ASSEMBLY_NODE_TYPES.includes(node?.type || '');
3428
+ // To avoid a circular dependency, we create the recursive rendering function here and trickle it down
3429
+ const renderDropzone = useCallback((node, props) => {
3430
+ return (React.createElement(Dropzone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, ...props }));
3431
+ }, [resolveDesignValue]);
3432
+ const renderClonedDropzone = useCallback((node, props) => {
3433
+ return (React.createElement(DropzoneClone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, renderDropzone: renderClonedDropzone, ...props }));
3434
+ }, [resolveDesignValue]);
3435
+ if (!resolveDesignValue) {
3436
+ return null;
3437
+ }
3438
+ /**
3439
+ * The Rules of Dropzones
3440
+ *
3441
+ * 1. A dropzone is disabled unless the mouse is hovering over it
3442
+ *
3443
+ * 2. Dragging a new component onto the canvas has no addtional rules
3444
+ * besides rule #1
3445
+ *
3446
+ * 3. Dragging a component that is a direct descendant of the root
3447
+ * (parentId === ROOT_ID) then only the Root Dropzone is enabled
3448
+ *
3449
+ * 4. Dragging a nested component (parentId !== ROOT_ID) then the Root
3450
+ * Dropzone is disabled, all other Dropzones follow rule #1
3451
+ *
3452
+ * 5. Assemblies and the SingleColumn component are always disabled
3453
+ *
3454
+ */
3455
+ const isDropzoneEnabled = () => {
3456
+ if (node?.data.blockId === CONTENTFUL_COMPONENTS.columns.id) {
3457
+ return false;
3458
+ }
3459
+ if (isAssembly) {
3460
+ return false;
3461
+ }
3462
+ if (isDraggingNewComponent) {
3463
+ return isHoveringZone;
3464
+ }
3465
+ const draggingParentIds = getZoneParents(draggedSourceId || '');
3466
+ if (!draggingParentIds.length) {
3467
+ return isRootZone;
3468
+ }
3469
+ return isHoveringZone && !isRootZone;
3470
+ };
3471
+ 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 })) }, (provided, snapshot) => {
3472
+ return (React.createElement(WrapperComponent, { ...(provided || { droppableProps: {} }).droppableProps, ref: provided?.innerRef, id: zoneId, "data-ctfl-zone-id": zoneId, className: classNames(styles$2.container, {
3473
+ [styles$2.isEmptyCanvas]: isEmptyCanvas,
3474
+ [styles$2.isDragging]: userIsDragging && !isAssembly,
3475
+ [styles$2.isDestination]: isDestination && !isAssembly,
3476
+ [styles$2.isRoot]: isRootZone,
3477
+ [styles$2.isEmptyZone]: !content.length,
3478
+ }, className), node: node, ...rest },
3479
+ isEmptyCanvas ? (React.createElement(EmptyContainer, { isDragging: isRootZone && userIsDragging })) : (content.map((item, i) => {
3480
+ const componentId = item.data.id;
3481
+ return (React.createElement(EditorBlock, { placeholder: {
3482
+ isDraggingOver: snapshot?.isDraggingOver,
3483
+ totalIndexes: content.length,
3484
+ elementIndex: i,
3485
+ dropzoneElementId: zoneId,
3486
+ direction,
3487
+ }, index: i, zoneId: zoneId, key: componentId, userIsDragging: userIsDragging, draggingNewComponent: isDraggingNewComponent, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone }));
3488
+ })),
3489
+ provided?.placeholder));
3490
+ }));
3491
+ }
3492
+
3493
+ function getStyle(style, snapshot) {
3494
+ if (!snapshot.isDropAnimating) {
3495
+ return style;
3496
+ }
3497
+ return {
3498
+ ...style,
3499
+ // cannot be 0, but make it super tiny
3500
+ transitionDuration: `0.001s`,
3501
+ };
3502
+ }
3503
+ const DraggableContainer = ({ id }) => {
3504
+ const ref = useRef(null);
3505
+ useDraggablePosition({
3506
+ draggableId: id,
3507
+ draggableRef: ref,
3508
+ position: DraggablePosition.CENTERED,
3509
+ });
3510
+ return (React.createElement("div", { id: COMPONENT_LIST_ID, style: {
3511
+ position: 'absolute',
3512
+ top: 0,
3513
+ left: 0,
3514
+ pointerEvents: 'none',
3515
+ zIndex: -1,
3516
+ } },
3517
+ React.createElement(Droppable, { droppableId: COMPONENT_LIST_ID, isDropDisabled: true }, (provided) => (React.createElement("div", { ...provided.droppableProps, ref: provided.innerRef },
3518
+ 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) => {
3519
+ provided.innerRef(node);
3520
+ ref.current = node;
3521
+ }, ...provided.draggableProps, ...provided.dragHandleProps, style: {
3522
+ ...getStyle(provided.draggableProps.style, snapshot),
3523
+ width: DRAGGABLE_WIDTH,
3524
+ height: DRAGGABLE_HEIGHT,
3525
+ pointerEvents: 'none',
3526
+ } }))),
3527
+ provided.placeholder)))));
3528
+ };
3529
+
3530
+ var css_248z = ".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: -10px;\n left: 0;\n width: 100%;\n height: 10px;\n z-index: 1000000;\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";
3531
+ var styles = {"hitbox":"render-module_hitbox__l4ysJ","hitboxLower":"render-module_hitboxLower__tgsA1","container":"render-module_container__-C3d7"};
3532
+ styleInject(css_248z);
3533
+
3534
+ // TODO: In order to support integrations without React, we should extract this heavy logic into simple
3535
+ // functions that we can reuse in other frameworks.
3536
+ /*
3537
+ * Registers media query change listeners for each breakpoint (except for "*").
3538
+ * It will always assume the last matching media query in the list. It therefore,
3539
+ * assumes that the breakpoints are sorted beginning with the default value (query: "*")
3540
+ * and then decending by screen width. For mobile-first designs, the order would be ascending
3541
+ */
3542
+ const useBreakpoints = (breakpoints) => {
3543
+ const [mediaQueryMatches, setMediaQueryMatches] = useState({});
3544
+ const fallbackBreakpointIndex = getFallbackBreakpointIndex(breakpoints);
3545
+ // Register event listeners to update the media query states
3546
+ useEffect(() => {
3547
+ const [mediaQueryMatchers, initialMediaQueryMatches] = mediaQueryMatcher(breakpoints);
3548
+ // Store the media query state in the beginning to initialise the state
3549
+ setMediaQueryMatches(initialMediaQueryMatches);
3550
+ const eventListeners = mediaQueryMatchers.map(({ id, signal }) => {
3551
+ const onChange = () => setMediaQueryMatches((prev) => ({
3552
+ ...prev,
3553
+ [id]: signal.matches,
3554
+ }));
3555
+ signal.addEventListener('change', onChange);
3556
+ return onChange;
3557
+ });
3558
+ return () => {
3559
+ eventListeners.forEach((eventListener, index) => {
3560
+ mediaQueryMatchers[index].signal.removeEventListener('change', eventListener);
3561
+ });
3562
+ };
3563
+ // Only re-setup all listeners when the breakpoint definition changed
3564
+ // eslint-disable-next-line react-hooks/exhaustive-deps
3565
+ }, [breakpoints]);
3566
+ const activeBreakpointIndex = getActiveBreakpointIndex(breakpoints, mediaQueryMatches, fallbackBreakpointIndex);
3567
+ const resolveDesignValue = useCallback((valuesByBreakpoint, variableName) => {
3568
+ return getValueForBreakpoint(valuesByBreakpoint, breakpoints, activeBreakpointIndex, variableName);
3569
+ }, [activeBreakpointIndex, breakpoints]);
3570
+ return { resolveDesignValue };
3571
+ };
3572
+
3573
+ class MouseOverHandler {
3574
+ constructor() {
3575
+ this.currentHoveredElementId = null;
3576
+ this.getMargins = (element) => {
3577
+ if (typeof window === 'undefined')
3578
+ return undefined;
3579
+ const styles = window.getComputedStyle(element);
3580
+ const top = parseInt(styles.marginTop);
3581
+ const bottom = parseInt(styles.marginBottom);
3582
+ const left = parseInt(styles.marginLeft);
3583
+ const right = parseInt(styles.marginRight);
3584
+ return { top, bottom, left, right };
3585
+ };
3586
+ this.getFullCoordinates = (element) => {
3587
+ const validChildren = Array.from(element.children).filter((child) => child instanceof HTMLElement && child.dataset.cfNodeBlockType === 'block');
3588
+ const { left, top, width, height } = this.getBoundingClientRect(element);
3589
+ const margins = this.getMargins(element);
3590
+ const childrenCoordinates = validChildren.map((child) => {
3591
+ const { left, top, width, height } = this.getBoundingClientRect(child);
3592
+ return { left, top, width, height, margins };
3593
+ });
3594
+ return {
3595
+ left,
3596
+ top,
3597
+ width,
3598
+ height,
3599
+ margins,
3600
+ childrenCoordinates,
3601
+ };
3602
+ };
3603
+ this.getClosestComponentInformation = (element) => {
3604
+ let target = element;
3605
+ // If the target is outside on the root or anywhere else on the iframes body
3606
+ if (target?.id === 'VisualEditorRoot' || target?.tagName === 'BODY') {
3607
+ const rootElement = document.getElementById('VisualEditorRoot');
3608
+ const hoveredRootElement = {
3609
+ nodeId: 'root',
3610
+ blockType: 'root',
3611
+ blockId: 'root',
3612
+ };
3613
+ return [rootElement, hoveredRootElement];
3614
+ }
3615
+ // Find the closest contentful container or direct parent that is a contentful container
3616
+ while (target) {
3617
+ if (
3618
+ // is itself a section?
3619
+ target.dataset.cfNodeId ||
3620
+ // Or a direct child of a section
3621
+ (target.parentElement && target.parentElement.dataset.cfNodeBlockId === 'ContentfulSection')) {
3622
+ const sectionId = target.dataset.cfNodeId;
3623
+ const sectionBlockId = target.dataset.cfNodeBlockId;
3624
+ const sectionBlockType = target.dataset.cfNodeBlockType;
3625
+ const hoveredElement = {
3626
+ nodeId: sectionId,
3627
+ blockId: sectionBlockId,
3628
+ blockType: sectionBlockType,
3629
+ };
3630
+ return [target, hoveredElement];
3631
+ }
3632
+ target = target.parentElement;
3633
+ }
3634
+ };
3635
+ this.getNewlyHoveredElement = (element) => {
3636
+ let parentElement = null;
3637
+ let parentSectionIndex = -1;
3638
+ const [hoveredElement, hoveredInfo] = this.getClosestComponentInformation(element) || [
3639
+ null,
3640
+ null,
3641
+ ];
3642
+ if (!hoveredElement)
3643
+ return;
3644
+ // if hovered element is already hovered and the information is already sent
3645
+ // ignore the rest and don't proceed.
3646
+ if (hoveredInfo.nodeId === this.currentHoveredElementId)
3647
+ return;
3648
+ let parentHTMLElement = hoveredElement?.parentElement || null;
3649
+ while (parentHTMLElement) {
3650
+ const parentIsRoot = parentHTMLElement.id === 'VisualEditorRoot';
3651
+ if (parentHTMLElement.dataset.cfNodeId || parentIsRoot) {
3652
+ parentElement = {
3653
+ nodeId: parentIsRoot ? 'root' : parentHTMLElement.dataset.cfNodeId,
3654
+ blockType: parentHTMLElement.dataset.cfNodeBlockType,
3655
+ blockId: parentHTMLElement.dataset.cfNodeBlockId,
3656
+ };
3657
+ const parentChildrenElements = parentHTMLElement.children;
3658
+ parentSectionIndex = Array.from(parentChildrenElements).findIndex((child) => child === hoveredElement);
3659
+ break;
3660
+ }
3661
+ parentHTMLElement = parentHTMLElement.parentElement;
3662
+ }
3663
+ const coordinates = this.getFullCoordinates(hoveredElement);
3664
+ return { coordinates, hoveredElement: hoveredInfo, parentElement, parentSectionIndex };
3665
+ };
3666
+ this.handleMouseMove = (target) => {
3667
+ const hoveredElementInfo = this.getNewlyHoveredElement(target);
3668
+ if (!hoveredElementInfo) {
3669
+ return;
3670
+ }
3671
+ const { coordinates, hoveredElement, parentElement, parentSectionIndex } = hoveredElementInfo;
3672
+ this.currentHoveredElementId = hoveredElementInfo.hoveredElement.nodeId || null;
3673
+ sendMessage(OUTGOING_EVENTS.NewHoveredElement, {
3674
+ hoveredElement,
3675
+ parentElement,
3676
+ parentSectionIndex,
3677
+ coordinates,
3678
+ });
3679
+ };
3680
+ this.onMouseMove = (event) => {
3681
+ const target = event.target;
3682
+ this.handleMouseMove(target);
3683
+ };
3684
+ this.onMouseLeave = () => {
3685
+ this.currentHoveredElementId = null;
3686
+ };
3687
+ }
3688
+ getBoundingClientRect(element) {
3689
+ const isAssembly = element.getAttribute('data-cf-node-block-type') === ASSEMBLY_NODE_TYPE;
3690
+ if (!isAssembly) {
3691
+ return element.getBoundingClientRect();
3692
+ }
3693
+ else {
3694
+ // As we use `display: contents` for assemblies, there is no real "block"
3695
+ // in the DOM and thus the browser fails to calculate the bounding rect.
3696
+ // Instead, we calculate it for each child and add it up:
3697
+ if (!element.firstElementChild) {
3698
+ return { left: 0, top: 0, width: 0, height: 0 };
3699
+ }
3700
+ const firstChildRect = element.firstElementChild.getBoundingClientRect();
3701
+ let fullHeight = firstChildRect.height;
3702
+ let nextChild = element.firstElementChild.nextElementSibling;
3703
+ while (nextChild) {
3704
+ const nextChildRect = nextChild.getBoundingClientRect();
3705
+ fullHeight += nextChildRect.height;
3706
+ nextChild = nextChild.nextElementSibling;
3707
+ }
3708
+ // The root of a assembly positions its first level containers vertically.
3709
+ // So we just need to add up the height and use the remaining properties from the first child.
3710
+ return {
3711
+ left: firstChildRect.left,
3712
+ top: firstChildRect.top,
3713
+ width: firstChildRect.width,
3714
+ height: fullHeight,
3715
+ };
3716
+ }
3717
+ }
3718
+ attachEvent() {
3719
+ document.addEventListener('mousemove', this.onMouseMove);
3720
+ document.addEventListener('mouseout', this.onMouseLeave);
3721
+ }
3722
+ detachEvent() {
3723
+ document.removeEventListener('mousemove', this.onMouseMove);
3724
+ document.removeEventListener('mouseout', this.onMouseLeave);
3725
+ }
3726
+ }
3727
+
3728
+ /**
3729
+ * This function gets the element co-ordinates of a specified component in the DOM and its parent
3730
+ * and sends the DOM Rect to the client app
3731
+ */
3732
+ const sendHoveredComponentCoordinates = (instanceId) => {
3733
+ const selectedElement = instanceId
3734
+ ? document.querySelector(`[data-cf-node-id="${instanceId}"]`)
3735
+ : undefined;
3736
+ const mouseOverHandler = new MouseOverHandler();
3737
+ mouseOverHandler.handleMouseMove(selectedElement || null);
3738
+ };
3739
+
3740
+ class DragState {
3741
+ constructor() {
3742
+ this.isDragStartedOnParent = false;
3743
+ this.isDraggingItem = false;
3744
+ }
3745
+ get isDragging() {
3746
+ return this.isDraggingItem;
3747
+ }
3748
+ get isDraggingOnParent() {
3749
+ return this.isDragStartedOnParent;
3750
+ }
3751
+ updateIsDragging(isDraggingItem) {
3752
+ this.isDraggingItem = isDraggingItem;
3753
+ }
3754
+ updateIsDragStartedOnParent(isDragStartedOnParent) {
3755
+ this.isDragStartedOnParent = isDragStartedOnParent;
3756
+ }
3757
+ resetState() {
3758
+ this.isDraggingItem = false;
3759
+ this.isDragStartedOnParent = false;
3760
+ }
3761
+ }
3762
+
3763
+ class SimulateDnD extends DragState {
3764
+ constructor() {
3765
+ super();
3766
+ this.draggingElement = null;
3767
+ }
3768
+ setupDrag() {
3769
+ this.updateIsDragStartedOnParent(true);
3770
+ }
3771
+ startDrag(coordX, coordY) {
3772
+ this.draggingElement = document.getElementById(NEW_COMPONENT_ID);
3773
+ this.updateIsDragging(true);
3774
+ this.simulateMouseEvent(coordX, coordY, 'mousedown');
3775
+ }
3776
+ updateDrag(coordX, coordY) {
3777
+ if (!this.draggingElement) {
3778
+ this.draggingElement = document.querySelector(`[${CTFL_DRAGGING_ELEMENT}]`);
3779
+ }
3780
+ this.simulateMouseEvent(coordX, coordY);
3781
+ }
3782
+ endDrag(coordX, coordY) {
3783
+ this.simulateMouseEvent(coordX, coordY, 'mouseup');
3784
+ this.reset();
3785
+ }
3786
+ reset() {
3787
+ this.draggingElement = null;
3788
+ this.resetState();
3789
+ }
3790
+ simulateMouseEvent(coordX, coordY, eventName = 'mousemove') {
3791
+ if (!this.draggingElement) {
3792
+ return;
3793
+ }
3794
+ const options = {
3795
+ bubbles: true,
3796
+ cancelable: true,
3797
+ view: window,
3798
+ pageX: 0,
3799
+ pageY: 0,
3800
+ clientX: coordX,
3801
+ clientY: coordY,
3802
+ };
3803
+ const event = new MouseEvent(eventName, options);
3804
+ this.draggingElement.dispatchEvent(event);
3805
+ }
3806
+ }
3807
+ var SimulateDnD$1 = new SimulateDnD();
3808
+
3809
+ function useEditorSubscriber() {
3810
+ const entityStore = useEntityStore((state) => state.entityStore);
3811
+ const areEntitiesFetched = useEntityStore((state) => state.areEntitiesFetched);
3812
+ const setEntitiesFetched = useEntityStore((state) => state.setEntitiesFetched);
3813
+ const { updateTree, updateNodesByUpdatedEntity } = useTreeStore((state) => ({
3814
+ updateTree: state.updateTree,
3815
+ updateNodesByUpdatedEntity: state.updateNodesByUpdatedEntity,
3816
+ }));
3817
+ const unboundValues = useEditorStore((state) => state.unboundValues);
3818
+ const dataSource = useEditorStore((state) => state.dataSource);
3819
+ const setLocale = useEditorStore((state) => state.setLocale);
3820
+ const setUnboundValues = useEditorStore((state) => state.setUnboundValues);
3821
+ const setDataSource = useEditorStore((state) => state.setDataSource);
3822
+ const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
3823
+ const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
3824
+ const resetEntityStore = useEntityStore((state) => state.resetEntityStore);
3825
+ const setComponentId = useDraggedItemStore((state) => state.setComponentId);
3826
+ const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
3827
+ const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
3828
+ const setScrollY = useDraggedItemStore((state) => state.setScrollY);
3829
+ // TODO: As we have disabled the useEffect, we can remove these states
3830
+ const [, /* isFetchingEntities */ setFetchingEntities] = useState(false);
3831
+ const reloadApp = () => {
3832
+ sendMessage(OUTGOING_EVENTS.CanvasReload, {});
3833
+ // Wait a moment to ensure that the message was sent
3834
+ setTimeout(() => {
3835
+ // Received a hot reload message from webpack dev server -> reload the canvas
3836
+ window.location.reload();
3837
+ }, 50);
3838
+ };
3839
+ useEffect(() => {
3840
+ sendMessage(OUTGOING_EVENTS.RequestComponentTreeUpdate);
3841
+ }, []);
3842
+ /**
3843
+ * Fills up entityStore with entities from newDataSource and from the tree.
3844
+ * Also manages "entity status" variables (areEntitiesFetched, isFetchingEntities)
3845
+ */
3846
+ const fetchMissingEntities = useCallback(async (entityStore, newDataSource, tree) => {
3847
+ // if we realize that there's nothing missing and nothing to fill-fetch before we do any async call,
3848
+ // then we can simply return and not lock the EntityStore at all.
3849
+ const startFetching = () => {
3850
+ setEntitiesFetched(false);
3851
+ setFetchingEntities(true);
3852
+ };
3853
+ const endFetching = () => {
3854
+ setEntitiesFetched(true);
3855
+ setFetchingEntities(false);
3856
+ };
3857
+ // Prepare L1 entities and deepReferences
3858
+ const entityLinksL1 = [
3859
+ ...Object.values(newDataSource),
3860
+ ...assembliesRegistry.values(), // we count assemblies here as "L1 entities", for convenience. Even though they're not headEntities.
3861
+ ];
3862
+ const deepReferences = gatherDeepReferencesFromTree(tree.root, newDataSource);
3863
+ /**
3864
+ * Checks only for _missing_ L1 entities
3865
+ * WARNING: Does NOT check for entity staleness/versions. If an entity is stale, it will NOT be considered missing.
3866
+ * If ExperienceBuilder wants to update stale entities, it should post `▼UPDATED_ENTITY` message to SDK.
3867
+ */
3868
+ const isMissingL1Entities = (entityLinks) => {
3869
+ const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(entityLinks);
3870
+ return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
3871
+ };
3872
+ /**
3873
+ * PRECONDITION: all L1 entities are fetched
3874
+ */
3875
+ const isMissingL2Entities = (deepReferences) => {
3876
+ const referentLinks = deepReferences
3877
+ .map((deepReference) => deepReference.extractReferent(entityStore))
3878
+ .filter(isLink);
3879
+ const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
3880
+ return Boolean(missingAssetIds.length) || Boolean(missingEntryIds.length);
3881
+ };
3882
+ /**
3883
+ * POST_CONDITION: entityStore is has all L1 entities (aka headEntities)
3884
+ */
3885
+ const fillupL1 = async ({ entityLinksL1, }) => {
3886
+ const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(entityLinksL1);
3887
+ await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
3888
+ };
3889
+ /**
3890
+ * PRECONDITION: all L1 entites are fetched
3891
+ */
3892
+ const fillupL2 = async ({ deepReferences }) => {
3893
+ const referentLinks = deepReferences
3894
+ .map((deepReference) => deepReference.extractReferent(entityStore))
3895
+ .filter(isLink);
3896
+ const { missingAssetIds, missingEntryIds } = entityStore.getMissingEntityIds(referentLinks);
3897
+ await entityStore.fetchEntities({ missingAssetIds, missingEntryIds });
3898
+ };
3899
+ try {
3900
+ if (isMissingL1Entities(entityLinksL1)) {
3901
+ startFetching();
3902
+ await fillupL1({ entityLinksL1 });
3903
+ }
3904
+ if (isMissingL2Entities(deepReferences)) {
3905
+ startFetching();
3906
+ await fillupL2({ deepReferences });
3907
+ }
3908
+ }
3909
+ catch (error) {
3910
+ console.error('[experiences-sdk-react] Failed fetching entities');
3911
+ console.error(error);
3912
+ throw error; // TODO: The original catch didn't let's rethrow; for the moment throw to see if we have any errors
3913
+ }
3914
+ finally {
3915
+ endFetching();
3916
+ }
3917
+ }, [setEntitiesFetched /* setFetchingEntities, assembliesRegistry */]);
3918
+ useEffect(() => {
3919
+ const onMessage = async (event) => {
3920
+ let reason;
3921
+ if ((reason = doesMismatchMessageSchema(event))) {
3922
+ if (event.origin.startsWith('http://localhost') &&
3923
+ `${event.data}`.includes('webpackHotUpdate')) {
3924
+ reloadApp();
3925
+ }
3926
+ else {
3927
+ console.warn(`[experiences-sdk-react::onMessage] Ignoring alien incoming message from origin [${event.origin}], due to: [${reason}]`, event);
3928
+ }
3929
+ return;
3930
+ }
3931
+ const eventData = tryParseMessage(event);
3932
+ if (eventData.eventType === PostMessageMethods$1.REQUESTED_ENTITIES) {
3933
+ // Expected message: This message is handled in the EntityStore to store fetched entities
3934
+ return;
3935
+ }
3936
+ console.debug(`[experiences-sdk-react::onMessage] Received message [${eventData.eventType}]`, eventData);
3937
+ const { payload } = eventData;
3938
+ switch (eventData.eventType) {
3939
+ case INCOMING_EVENTS.ExperienceUpdated: {
3940
+ const { tree, locale, changedNode, changedValueType, assemblies, } = payload;
3941
+ // Make sure to first store the assemblies before setting the tree and thus triggering a rerender
3942
+ if (assemblies) {
3943
+ setAssemblies(assemblies);
3944
+ // If the assemblyEntry is not yet fetched, this will be done below by
3945
+ // the imperative calls to fetchMissingEntities.
3946
+ }
3947
+ let newEntityStore = entityStore;
3948
+ if (entityStore.locale !== locale) {
3949
+ newEntityStore = resetEntityStore(locale);
3950
+ setLocale(locale);
3951
+ }
3952
+ // Below are mutually exclusive cases
3953
+ if (changedNode) {
3954
+ /**
3955
+ * On single node updates, we want to skip the process of getting the data (datasource and unbound values)
3956
+ * from tree. Since we know the updated node, we can skip that recursion everytime the tree updates and
3957
+ * just update the relevant data we need from the relevant node.
3958
+ *
3959
+ * We still update the tree here so we don't have a stale "tree"
3960
+ */
3961
+ if (changedValueType === 'BoundValue') {
3962
+ const newDataSource = { ...dataSource, ...changedNode.data.dataSource };
3963
+ setDataSource(newDataSource);
3964
+ await fetchMissingEntities(newEntityStore, newDataSource, tree);
3965
+ }
3966
+ else if (changedValueType === 'UnboundValue') {
3967
+ setUnboundValues({
3968
+ ...unboundValues,
3969
+ ...changedNode.data.unboundValues,
3970
+ });
3971
+ }
3972
+ }
3973
+ else {
3974
+ const { dataSource, unboundValues } = getDataFromTree(tree);
3975
+ setDataSource(dataSource);
3976
+ setUnboundValues(unboundValues);
3977
+ await fetchMissingEntities(newEntityStore, dataSource, tree);
3978
+ }
3979
+ // Update the tree when all necessary data is fetched and ready for rendering.
3980
+ updateTree(tree);
3981
+ break;
3982
+ }
3983
+ case INCOMING_EVENTS.AssembliesRegistered: {
3984
+ const { assemblies } = payload;
3985
+ assemblies.forEach((definition) => {
3986
+ addComponentRegistration({
3987
+ component: Assembly,
3988
+ definition,
3989
+ });
3990
+ });
3991
+ break;
3992
+ }
3993
+ case INCOMING_EVENTS.AssembliesAdded: {
3994
+ const { assembly, assemblyDefinition, } = payload;
3995
+ entityStore.updateEntity(assembly);
3996
+ // Using a Map here to avoid setting state and rerending all existing assemblies when a new assembly is added
3997
+ // TODO: Figure out if we can extend this love to data source and unbound values. Maybe that'll solve the blink
3998
+ // of all bound and unbound values when new values are added
3999
+ assembliesRegistry.set(assembly.sys.id, {
4000
+ sys: { id: assembly.sys.id, linkType: 'Entry', type: 'Link' },
4001
+ });
4002
+ if (assemblyDefinition) {
4003
+ addComponentRegistration({
4004
+ component: Assembly,
4005
+ definition: assemblyDefinition,
4006
+ });
4007
+ }
4008
+ break;
4009
+ }
4010
+ case INCOMING_EVENTS.CanvasResized: {
4011
+ const { selectedNodeId } = payload;
4012
+ if (selectedNodeId) {
4013
+ sendSelectedComponentCoordinates(selectedNodeId);
4014
+ }
4015
+ break;
4016
+ }
4017
+ case INCOMING_EVENTS.HoverComponent: {
4018
+ const { hoveredNodeId } = payload;
4019
+ sendHoveredComponentCoordinates(hoveredNodeId);
4020
+ break;
4021
+ }
4022
+ case INCOMING_EVENTS.ComponentDraggingChanged: {
4023
+ const { isDragging } = payload;
4024
+ if (!isDragging) {
4025
+ setComponentId('');
4026
+ setDraggingOnCanvas(false);
4027
+ SimulateDnD$1.reset();
4028
+ }
4029
+ break;
4030
+ }
4031
+ case INCOMING_EVENTS.UpdatedEntity: {
4032
+ const { entity: updatedEntity, shouldRerender } = payload;
4033
+ if (updatedEntity) {
4034
+ const storedEntity = entityStore.entities.find((entity) => entity.sys.id === updatedEntity.sys.id);
4035
+ const didEntityChange = storedEntity?.sys.version !== updatedEntity.sys.version;
4036
+ entityStore.updateEntity(updatedEntity);
4037
+ // We traverse the whole tree, so this is a opt-in feature to only use it when required.
4038
+ if (shouldRerender && didEntityChange) {
4039
+ updateNodesByUpdatedEntity(updatedEntity.sys.id);
4040
+ }
4041
+ }
4042
+ break;
4043
+ }
4044
+ case INCOMING_EVENTS.RequestEditorMode: {
4045
+ break;
4046
+ }
4047
+ case INCOMING_EVENTS.ComponentDragCanceled: {
4048
+ if (SimulateDnD$1.isDragging) {
4049
+ //simulate a mouseup event to cancel the drag
4050
+ SimulateDnD$1.endDrag(0, 0);
4051
+ }
4052
+ break;
4053
+ }
4054
+ case INCOMING_EVENTS.ComponentDragStarted: {
4055
+ SimulateDnD$1.setupDrag();
4056
+ setComponentId(payload.id || '');
4057
+ setDraggingOnCanvas(true);
4058
+ sendMessage(OUTGOING_EVENTS.ComponentSelected, {
4059
+ nodeId: '',
4060
+ });
4061
+ break;
4062
+ }
4063
+ case INCOMING_EVENTS.ComponentDragEnded: {
4064
+ SimulateDnD$1.reset();
4065
+ setComponentId('');
4066
+ setDraggingOnCanvas(false);
4067
+ break;
4068
+ }
4069
+ case INCOMING_EVENTS.SelectComponent: {
4070
+ const { selectedNodeId: nodeId } = payload;
4071
+ setSelectedNodeId(nodeId);
4072
+ sendSelectedComponentCoordinates(nodeId);
4073
+ break;
4074
+ }
4075
+ case INCOMING_EVENTS.MouseMove: {
4076
+ const { mouseX, mouseY } = payload;
4077
+ setMousePosition(mouseX, mouseY);
4078
+ if (SimulateDnD$1.isDraggingOnParent && !SimulateDnD$1.isDragging) {
4079
+ SimulateDnD$1.startDrag(mouseX, mouseY);
4080
+ }
4081
+ else {
4082
+ SimulateDnD$1.updateDrag(mouseX, mouseY);
4083
+ }
4084
+ break;
4085
+ }
4086
+ case INCOMING_EVENTS.ComponentMoveEnded: {
4087
+ const { mouseX, mouseY } = payload;
4088
+ SimulateDnD$1.endDrag(mouseX, mouseY);
4089
+ break;
4090
+ }
4091
+ default:
4092
+ console.error(`[experiences-sdk-react::onMessage] Logic error, unsupported eventType: [${eventData.eventType}]`);
4093
+ }
4094
+ };
4095
+ window.addEventListener('message', onMessage);
4096
+ return () => {
4097
+ window.removeEventListener('message', onMessage);
4098
+ };
4099
+ }, [
4100
+ entityStore,
4101
+ setComponentId,
4102
+ setDraggingOnCanvas,
4103
+ setDataSource,
4104
+ setLocale,
4105
+ setSelectedNodeId,
4106
+ dataSource,
4107
+ areEntitiesFetched,
4108
+ fetchMissingEntities,
4109
+ setUnboundValues,
4110
+ unboundValues,
4111
+ updateTree,
4112
+ updateNodesByUpdatedEntity,
4113
+ setMousePosition,
4114
+ resetEntityStore,
4115
+ ]);
4116
+ /*
4117
+ * Handles on scroll business
4118
+ */
4119
+ useEffect(() => {
4120
+ let timeoutId = 0;
4121
+ let isScrolling = false;
4122
+ const onScroll = () => {
4123
+ setScrollY(window.scrollY);
4124
+ if (isScrolling === false) {
4125
+ sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.Start);
4126
+ }
4127
+ sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.IsScrolling);
4128
+ isScrolling = true;
4129
+ clearTimeout(timeoutId);
4130
+ timeoutId = window.setTimeout(() => {
4131
+ if (isScrolling === false) {
4132
+ return;
4133
+ }
4134
+ isScrolling = false;
4135
+ sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.End);
4136
+ /**
4137
+ * On scroll end, send new co-ordinates of selected node
4138
+ */
4139
+ if (selectedNodeId) {
4140
+ sendSelectedComponentCoordinates(selectedNodeId);
4141
+ }
4142
+ }, 150);
4143
+ };
4144
+ window.addEventListener('scroll', onScroll, { capture: true, passive: true });
4145
+ return () => {
4146
+ window.removeEventListener('scroll', onScroll, { capture: true });
4147
+ clearTimeout(timeoutId);
4148
+ };
4149
+ }, [selectedNodeId, setScrollY]);
4150
+ }
4151
+
4152
+ const onComponentMoved = (options) => {
4153
+ sendMessage(OUTGOING_EVENTS.ComponentMoved, options);
4154
+ };
4155
+
4156
+ const generateId = (type) => `${type}-${v4()}`;
4157
+
4158
+ const createTreeNode = ({ blockId, parentId }) => {
4159
+ const node = {
4160
+ type: 'block',
4161
+ data: {
4162
+ id: generateId(blockId),
4163
+ blockId,
4164
+ props: {},
4165
+ dataSource: {},
4166
+ breakpoints: [],
4167
+ unboundValues: {},
4168
+ },
4169
+ parentId,
4170
+ children: [],
4171
+ };
4172
+ return node;
4173
+ };
4174
+
4175
+ const onComponentDropped = ({ node, index, parentBlockId, parentType, parentId, }) => {
4176
+ sendMessage(OUTGOING_EVENTS.ComponentDropped, {
4177
+ node,
4178
+ index: index ?? node.children.length,
4179
+ parentNode: {
4180
+ type: parentType,
4181
+ data: {
4182
+ blockId: parentBlockId,
4183
+ id: parentId,
4184
+ },
4185
+ },
4186
+ });
4187
+ };
4188
+
4189
+ const onDrop = ({ destinationIndex, componentType, destinationZoneId, data, }) => {
4190
+ const parentId = destinationZoneId;
4191
+ const parentNode = getItem({ id: parentId }, data);
4192
+ const parentIsRoot = parentId === ROOT_ID;
4193
+ const emptyComponentData = {
4194
+ type: 'block',
4195
+ parentId,
4196
+ children: [],
4197
+ data: {
4198
+ blockId: componentType,
4199
+ id: generateId(componentType),
4200
+ breakpoints: [],
4201
+ dataSource: {},
4202
+ props: {},
4203
+ unboundValues: {},
4204
+ },
4205
+ };
4206
+ onComponentDropped({
4207
+ node: emptyComponentData,
4208
+ index: destinationIndex,
4209
+ parentType: parentIsRoot ? 'root' : parentNode?.type,
4210
+ parentBlockId: parentNode?.data.blockId,
4211
+ parentId: parentIsRoot ? 'root' : parentId,
4212
+ });
4213
+ };
4214
+
4215
+ function useCanvasInteractions() {
4216
+ const tree = useTreeStore((state) => state.tree);
4217
+ const reorderChildren = useTreeStore((state) => state.reorderChildren);
4218
+ const reparentChild = useTreeStore((state) => state.reparentChild);
4219
+ const addChild = useTreeStore((state) => state.addChild);
4220
+ const onAddComponent = (droppedItem) => {
4221
+ const { destination, draggableId } = droppedItem;
4222
+ if (!destination) {
4223
+ return;
4224
+ }
4225
+ const droppingOnRoot = destination.droppableId === ROOT_ID;
4226
+ const isValidRootComponent = draggableId === CONTENTFUL_COMPONENTS.container.id;
4227
+ let node = createTreeNode({ blockId: draggableId, parentId: destination.droppableId });
4228
+ if (droppingOnRoot && !isValidRootComponent) {
4229
+ const wrappingContainer = createTreeNode({
4230
+ blockId: CONTENTFUL_COMPONENTS.container.id,
4231
+ parentId: destination.droppableId,
4232
+ });
4233
+ const childNode = createTreeNode({
4234
+ blockId: draggableId,
4235
+ parentId: wrappingContainer.data.id,
4236
+ });
4237
+ node = wrappingContainer;
4238
+ node.children = [childNode];
4239
+ }
4240
+ addChild(destination.index, destination.droppableId, node);
4241
+ onDrop({
4242
+ data: tree,
4243
+ componentType: draggableId,
4244
+ destinationIndex: destination.index,
4245
+ destinationZoneId: destination.droppableId,
4246
+ });
4247
+ };
4248
+ const onMoveComponent = (droppedItem) => {
4249
+ const { destination, source, draggableId } = droppedItem;
4250
+ if (!destination || !source) {
4251
+ return;
4252
+ }
4253
+ if (destination.droppableId === source.droppableId) {
4254
+ reorderChildren(destination.index, destination.droppableId, source.index);
4255
+ }
4256
+ if (destination.droppableId !== source.droppableId) {
4257
+ reparentChild(destination.index, destination.droppableId, source.index, source.droppableId);
4258
+ }
4259
+ onComponentMoved({
4260
+ nodeId: draggableId,
4261
+ destinationIndex: destination.index,
4262
+ destinationParentId: destination.droppableId,
4263
+ sourceIndex: source.index,
4264
+ sourceParentId: source.droppableId,
4265
+ });
4266
+ };
4267
+ return { onAddComponent, onMoveComponent };
4268
+ }
4269
+
4270
+ const TestDNDContainer = ({ onDragEnd, onBeforeDragStart, onDragUpdate, children, }) => {
4271
+ const handleDragStart = (event) => {
4272
+ const draggedItem = event.nativeEvent;
4273
+ const start = {
4274
+ mode: draggedItem.mode,
4275
+ draggableId: draggedItem.draggableId,
4276
+ type: draggedItem.type,
4277
+ source: draggedItem.source,
4278
+ };
4279
+ onBeforeDragStart(start);
4280
+ };
4281
+ const handleDrag = (event) => {
4282
+ const draggedItem = event.nativeEvent;
4283
+ const update = {
4284
+ mode: draggedItem.mode,
4285
+ draggableId: draggedItem.draggableId,
4286
+ type: draggedItem.type,
4287
+ source: draggedItem.source,
4288
+ destination: draggedItem.destination,
4289
+ combine: draggedItem.combine,
4290
+ };
4291
+ onDragUpdate(update, {});
4292
+ };
4293
+ const handleDragEnd = (event) => {
4294
+ const draggedItem = event.nativeEvent;
4295
+ const result = {
4296
+ mode: draggedItem.mode,
4297
+ draggableId: draggedItem.draggableId,
4298
+ type: draggedItem.type,
4299
+ source: draggedItem.source,
4300
+ destination: draggedItem.destination,
4301
+ combine: draggedItem.combine,
4302
+ reason: draggedItem.reason,
4303
+ };
4304
+ onDragEnd(result, {});
4305
+ };
4306
+ return (React.createElement("div", { "data-test-id": "dnd-context-substitute", onDragStart: handleDragStart, onDrag: handleDrag, onDragEnd: handleDragEnd }, children));
4307
+ };
4308
+
4309
+ const DNDProvider = ({ children }) => {
4310
+ const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
4311
+ const draggedItem = useDraggedItemStore((state) => state.draggedItem);
4312
+ const setOnBeforeCaptureId = useDraggedItemStore((state) => state.setOnBeforeCaptureId);
4313
+ const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
4314
+ const updateItem = useDraggedItemStore((state) => state.updateItem);
4315
+ const { onAddComponent, onMoveComponent } = useCanvasInteractions();
4316
+ const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
4317
+ const prevSelectedNodeId = useRef(null);
4318
+ const isTestRun = typeof window !== 'undefined' && Object.prototype.hasOwnProperty.call(window, 'Cypress');
4319
+ const dragStart = ({ source }) => {
4320
+ prevSelectedNodeId.current = selectedNodeId;
4321
+ //Unselect the current node when dragging and remove the outline
4322
+ setSelectedNodeId('');
4323
+ sendMessage(OUTGOING_EVENTS.ComponentSelected, {
4324
+ nodeId: '',
4325
+ });
4326
+ if (source.droppableId !== COMPONENT_LIST_ID) {
4327
+ sendMessage(OUTGOING_EVENTS.ComponentMoveStarted);
4328
+ }
4329
+ };
4330
+ const beforeCapture = ({ draggableId }) => {
4331
+ setDraggingOnCanvas(true);
4332
+ setOnBeforeCaptureId(draggableId);
4333
+ };
4334
+ const dragUpdate = (update) => {
4335
+ updateItem(update);
4336
+ };
4337
+ const dragEnd = (dropResult) => {
4338
+ setDraggingOnCanvas(false);
4339
+ setOnBeforeCaptureId('');
4340
+ updateItem(undefined);
4341
+ SimulateDnD$1.reset();
4342
+ if (!dropResult.destination) {
4343
+ if (!draggedItem?.destination) {
4344
+ // User cancel drag
4345
+ sendMessage(OUTGOING_EVENTS.ComponentDragCanceled);
4346
+ //select the previously selected node if drag was canceled
4347
+ if (prevSelectedNodeId.current) {
4348
+ setSelectedNodeId(prevSelectedNodeId.current);
4349
+ sendMessage(OUTGOING_EVENTS.ComponentSelected, {
4350
+ nodeId: prevSelectedNodeId.current,
4351
+ });
4352
+ prevSelectedNodeId.current = null;
4353
+ }
4354
+ return;
4355
+ }
4356
+ // Use the destination from the draggedItem (when clicking the canvas)
4357
+ dropResult.destination = draggedItem.destination;
4358
+ }
4359
+ // New component added to canvas
4360
+ if (dropResult.source.droppableId.startsWith('component-list')) {
4361
+ onAddComponent(dropResult);
4362
+ }
4363
+ else {
4364
+ onMoveComponent(dropResult);
4365
+ }
4366
+ // If a node was previously selected prior to dragging, re-select it
4367
+ setSelectedNodeId(dropResult.draggableId);
4368
+ sendMessage(OUTGOING_EVENTS.ComponentMoveEnded);
4369
+ sendMessage(OUTGOING_EVENTS.ComponentSelected, {
4370
+ nodeId: dropResult.draggableId,
4371
+ });
4372
+ };
4373
+ return (React.createElement(DragDropContext, { onBeforeCapture: beforeCapture, onDragUpdate: dragUpdate, onBeforeDragStart: dragStart, onDragEnd: dragEnd }, isTestRun ? (React.createElement(TestDNDContainer, { onDragEnd: dragEnd, onBeforeDragStart: dragStart, onDragUpdate: dragUpdate }, children)) : (children)));
4374
+ };
4375
+
4376
+ const RootRenderer = ({ onChange }) => {
4377
+ useEditorSubscriber();
4378
+ const dragItem = useDraggedItemStore((state) => state.componentId);
4379
+ const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
4380
+ const breakpoints = useTreeStore((state) => state.breakpoints);
4381
+ const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
4382
+ const draggableSourceId = useDraggedItemStore((state) => state.draggedItem?.source.droppableId);
4383
+ const draggingNewComponent = !!draggableSourceId?.startsWith(COMPONENT_LIST_ID);
4384
+ const containerRef = useRef(null);
4385
+ const { resolveDesignValue } = useBreakpoints(breakpoints);
4386
+ const [containerStyles, setContainerStyles] = useState({});
4387
+ const tree = useTreeStore((state) => state.tree);
4388
+ const handleClickOutside = useCallback((e) => {
4389
+ const element = e.target;
4390
+ const isRoot = element.getAttribute('data-ctfl-zone-id') === ROOT_ID;
4391
+ const clickedOnCanvas = element.closest(`[data-ctfl-root]`);
4392
+ if (clickedOnCanvas && !isRoot) {
4393
+ return;
4394
+ }
4395
+ sendMessage(OUTGOING_EVENTS.OutsideCanvasClick, {
4396
+ outsideCanvasClick: true,
4397
+ });
4398
+ sendMessage(OUTGOING_EVENTS.ComponentSelected, {
4399
+ selectedId: '',
4400
+ });
4401
+ setSelectedNodeId('');
4402
+ }, [setSelectedNodeId]);
4403
+ const handleResizeCanvas = useCallback(() => {
4404
+ const parentElement = containerRef.current?.parentElement;
4405
+ if (!parentElement) {
4406
+ return;
4407
+ }
4408
+ let siblingHeight = 0;
4409
+ for (const child of parentElement.children) {
4410
+ if (!child.hasAttribute('data-ctfl-root')) {
4411
+ siblingHeight += child.getBoundingClientRect().height;
4412
+ }
4413
+ }
4414
+ if (!siblingHeight) {
4415
+ /**
4416
+ * DRAGGABLE_HEIGHT is subtracted here due to an uninteded scrolling effect
4417
+ * when dragging a new component onto the canvas
4418
+ *
4419
+ * The DRAGGABLE_HEIGHT is then added as margin bottom to offset this value
4420
+ * so that visually there is no difference to the user.
4421
+ */
4422
+ setContainerStyles({
4423
+ minHeight: `${window.innerHeight - DRAGGABLE_HEIGHT}px`,
4424
+ });
4425
+ return;
4426
+ }
4427
+ setContainerStyles({
4428
+ minHeight: `${window.innerHeight - siblingHeight}px`,
4429
+ });
4430
+ // eslint-disable-next-line react-hooks/exhaustive-deps
4431
+ }, [containerRef.current]);
4432
+ useEffect(() => {
4433
+ if (onChange)
4434
+ onChange(tree);
4435
+ }, [tree, onChange]);
4436
+ useEffect(() => {
4437
+ document.addEventListener('click', handleClickOutside);
4438
+ return () => {
4439
+ document.removeEventListener('click', handleClickOutside);
4440
+ };
4441
+ }, [handleClickOutside]);
4442
+ useEffect(() => {
4443
+ handleResizeCanvas();
4444
+ // eslint-disable-next-line react-hooks/exhaustive-deps
4445
+ }, [containerRef.current]);
4446
+ return (React.createElement(DNDProvider, null,
4447
+ dragItem && React.createElement(DraggableContainer, { id: dragItem }),
4448
+ React.createElement("div", { "data-ctfl-root": true, className: styles.container, ref: containerRef, style: containerStyles },
4449
+ userIsDragging && draggingNewComponent && (React.createElement("div", { className: styles.hitbox, "data-ctfl-zone-id": ROOT_ID })),
4450
+ React.createElement(Dropzone, { zoneId: ROOT_ID, resolveDesignValue: resolveDesignValue }),
4451
+ userIsDragging && draggingNewComponent && (React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles.hitboxLower }))),
4452
+ React.createElement("div", { "data-ctfl-hitboxes": true })));
4453
+ };
4454
+
4455
+ const useInitializeEditor = () => {
4456
+ const initializeEditor = useEditorStore((state) => state.initializeEditor);
4457
+ const [initialized, setInitialized] = useState(false);
4458
+ const resetEntityStore = useEntityStore((state) => state.resetEntityStore);
4459
+ useEffect(() => {
4460
+ const onVisualEditorInitialize = (event) => {
4461
+ if (!event.detail)
4462
+ return;
4463
+ const { componentRegistry, designTokens, locale: initialLocale,
4464
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
4465
+ entities, } = event.detail;
4466
+ initializeEditor({
4467
+ initialLocale,
4468
+ componentRegistry,
4469
+ designTokens,
4470
+ });
4471
+ // if entities is set to [], then everything will still work as EntityStore will
4472
+ // request entities on demand via ▲REQUEST_ENTITY
4473
+ resetEntityStore(initialLocale, entities);
4474
+ setInitialized(true);
4475
+ };
4476
+ // Listen for VisualEditorComponents internal event
4477
+ window.addEventListener(INTERNAL_EVENTS.VisualEditorInitialize, onVisualEditorInitialize);
4478
+ // Clean up the event listener
4479
+ return () => {
4480
+ window.removeEventListener(INTERNAL_EVENTS.VisualEditorInitialize, onVisualEditorInitialize);
4481
+ };
4482
+ }, [initializeEditor, resetEntityStore]);
4483
+ useEffect(() => {
4484
+ if (initialized) {
4485
+ return;
4486
+ }
4487
+ // Dispatch Visual Editor Ready event
4488
+ window.dispatchEvent(new CustomEvent(VISUAL_EDITOR_EVENTS.Ready));
4489
+ }, [initialized]);
4490
+ return initialized;
4491
+ };
4492
+
4493
+ const VisualEditorRoot = () => {
4494
+ const initialized = useInitializeEditor();
4495
+ const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
4496
+ const setHoveringZone = useZoneStore((state) => state.setHoveringZone);
4497
+ useEffect(() => {
4498
+ const onMouseMove = (e) => {
4499
+ setMousePosition(e.clientX, e.clientY);
4500
+ const target = e.target;
4501
+ const zoneId = target.closest(`[${CTFL_ZONE_ID}]`)?.getAttribute(CTFL_ZONE_ID);
4502
+ if (zoneId) {
4503
+ setHoveringZone(zoneId);
4504
+ }
4505
+ if (!SimulateDnD$1.isDragging) {
4506
+ return;
4507
+ }
4508
+ if (target.id === NEW_COMPONENT_ID) {
4509
+ return;
4510
+ }
4511
+ SimulateDnD$1.updateDrag(e.clientX, e.clientY);
4512
+ sendMessage(OUTGOING_EVENTS.MouseMove, {
4513
+ clientX: e.pageX,
4514
+ clientY: e.pageY - window.scrollY,
4515
+ });
4516
+ };
4517
+ document.addEventListener('mousemove', onMouseMove);
4518
+ return () => {
4519
+ document.removeEventListener('mousemove', onMouseMove);
4520
+ };
4521
+ // eslint-disable-next-line react-hooks/exhaustive-deps
4522
+ }, []);
4523
+ if (!initialized)
4524
+ return null;
4525
+ return React.createElement(RootRenderer, null);
4526
+ };
4527
+
4528
+ export { VisualEditorRoot as default };
4529
+ //# sourceMappingURL=index.js.map