@contentful/experiences-visual-editor-react 2.0.3-dev-20250731T1506-4b21110.0 → 2.0.3-dev-20250806T1104-2992bd9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,35 +1,41 @@
1
1
  import styleInject from 'style-inject';
2
- import React, { useEffect, useRef, useState, useCallback, forwardRef, useLayoutEffect, useMemo } from 'react';
2
+ import React, { useState, useEffect, useCallback, forwardRef, useMemo, useLayoutEffect, useRef } from 'react';
3
3
  import { z } from 'zod';
4
- import { omit, isArray, isEqual, get as get$2 } from 'lodash-es';
4
+ import { omit, isArray, isEqual, get as get$2, debounce } from 'lodash-es';
5
5
  import md5 from 'md5';
6
6
  import { BLOCKS } from '@contentful/rich-text-types';
7
7
  import { create, useStore } from 'zustand';
8
- import { Droppable, Draggable, DragDropContext } from '@hello-pangea/dnd';
9
8
  import { produce } from 'immer';
10
9
  import '@contentful/rich-text-react-renderer';
11
- import { v4 } from 'uuid';
12
- import { createPortal } from 'react-dom';
13
- import classNames from 'classnames';
14
10
 
15
11
  var css_248z$b = "html,\nbody {\n margin: 0;\n padding: 0;\n}\n\n/*\n * All of these variables are tokens from Forma-36 and should not be adjusted as these\n * are global variables that may affect multiple places.\n * As our customers may use other design libraries, we try to avoid overlapping global\n * variables by always using the prefix `--exp-builder-` inside this SDK.\n */\n\n:root {\n /* Color tokens from Forma 36: https://f36.contentful.com/tokens/color-system */\n --exp-builder-blue100: #e8f5ff;\n --exp-builder-blue200: #ceecff;\n --exp-builder-blue300: #98cbff;\n --exp-builder-blue400: #40a0ff;\n --exp-builder-blue500: #036fe3;\n --exp-builder-blue600: #0059c8;\n --exp-builder-blue700: #0041ab;\n --exp-builder-blue800: #003298;\n --exp-builder-blue900: #002a8e;\n --exp-builder-gray100: #f7f9fa;\n --exp-builder-gray200: #e7ebee;\n --exp-builder-gray300: #cfd9e0;\n --exp-builder-gray400: #aec1cc;\n --exp-builder-gray500: #67728a;\n --exp-builder-gray600: #5a657c;\n --exp-builder-gray700: #414d63;\n --exp-builder-gray800: #1b273a;\n --exp-builder-gray900: #111b2b;\n --exp-builder-purple600: #6c3ecf;\n --exp-builder-red200: #ffe0e0;\n --exp-builder-red800: #7f0010;\n --exp-builder-color-white: #ffffff;\n --exp-builder-glow-primary: 0px 0px 0px 3px #e8f5ff;\n\n /* RGB colors for applying opacity */\n --exp-builder-blue100-rgb: 232, 245, 255;\n --exp-builder-blue300-rgb: 152, 203, 255;\n\n /* Spacing tokens from Forma 36: https://f36.contentful.com/tokens/spacing */\n --exp-builder-spacing-s: 0.75rem;\n --exp-builder-spacing-2xs: 0.25rem;\n\n /* Typography tokens from Forma 36: https://f36.contentful.com/tokens/typography */\n --exp-builder-font-size-l: 1rem;\n --exp-builder-font-size-m: 0.875rem;\n --exp-builder-font-stack-primary: -apple-system, BlinkMacSystemFont, Segoe UI, Helvetica, Arial,\n sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;\n --exp-builder-line-height-condensed: 1.25;\n}\n";
16
12
  styleInject(css_248z$b);
17
13
 
14
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
18
15
  const INCOMING_EVENTS$1 = {
19
16
  RequestEditorMode: 'requestEditorMode',
20
17
  RequestReadOnlyMode: 'requestReadOnlyMode',
21
18
  ExperienceUpdated: 'componentTreeUpdated',
19
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
22
20
  ComponentDraggingChanged: 'componentDraggingChanged',
21
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
23
22
  ComponentDragCanceled: 'componentDragCanceled',
23
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
24
24
  ComponentDragStarted: 'componentDragStarted',
25
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
25
26
  ComponentDragEnded: 'componentDragEnded',
27
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
26
28
  ComponentMoveEnded: 'componentMoveEnded',
29
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
27
30
  CanvasResized: 'canvasResized',
31
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
28
32
  SelectComponent: 'selectComponent',
33
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
29
34
  HoverComponent: 'hoverComponent',
30
35
  UpdatedEntity: 'updatedEntity',
31
36
  AssembliesAdded: 'assembliesAdded',
32
37
  AssembliesRegistered: 'assembliesRegistered',
38
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
33
39
  MouseMove: 'mouseMove',
34
40
  RequestedEntities: 'REQUESTED_ENTITIES',
35
41
  };
@@ -44,7 +50,7 @@ var StudioCanvasMode$3;
44
50
  StudioCanvasMode["EDITOR"] = "editorMode";
45
51
  StudioCanvasMode["NONE"] = "none";
46
52
  })(StudioCanvasMode$3 || (StudioCanvasMode$3 = {}));
47
- const CONTENTFUL_COMPONENTS$2 = {
53
+ const CONTENTFUL_COMPONENTS$1 = {
48
54
  section: {
49
55
  id: 'contentful-section',
50
56
  name: 'Section',
@@ -90,9 +96,6 @@ const CONTENTFUL_COMPONENTS$2 = {
90
96
  name: 'Carousel',
91
97
  },
92
98
  };
93
- const ASSEMBLY_NODE_TYPE$1 = 'assembly';
94
- const ASSEMBLY_DEFAULT_CATEGORY$1 = 'Assemblies';
95
- const ASSEMBLY_BLOCK_NODE_TYPE$1 = 'assemblyBlock';
96
99
  const CF_STYLE_ATTRIBUTES = [
97
100
  'cfVisibility',
98
101
  'cfHorizontalAlignment',
@@ -127,7 +130,7 @@ const CF_STYLE_ATTRIBUTES = [
127
130
  'cfTextItalic',
128
131
  'cfTextUnderline',
129
132
  ];
130
- const EMPTY_CONTAINER_HEIGHT$1 = '80px';
133
+ const EMPTY_CONTAINER_SIZE$1 = '80px';
131
134
  const DEFAULT_IMAGE_WIDTH = '500px';
132
135
  var PostMessageMethods$3;
133
136
  (function (PostMessageMethods) {
@@ -137,20 +140,14 @@ var PostMessageMethods$3;
137
140
  const SUPPORTED_IMAGE_FORMATS = ['jpg', 'png', 'webp', 'gif', 'avif'];
138
141
 
139
142
  const structureComponentIds = new Set([
140
- CONTENTFUL_COMPONENTS$2.section.id,
141
- CONTENTFUL_COMPONENTS$2.columns.id,
142
- CONTENTFUL_COMPONENTS$2.container.id,
143
- CONTENTFUL_COMPONENTS$2.singleColumn.id,
143
+ CONTENTFUL_COMPONENTS$1.section.id,
144
+ CONTENTFUL_COMPONENTS$1.columns.id,
145
+ CONTENTFUL_COMPONENTS$1.container.id,
146
+ CONTENTFUL_COMPONENTS$1.singleColumn.id,
144
147
  ]);
145
- const patternTypes = new Set([ASSEMBLY_NODE_TYPE$1, ASSEMBLY_BLOCK_NODE_TYPE$1]);
146
- const allContentfulComponentIds = new Set(Object.values(CONTENTFUL_COMPONENTS$2).map((component) => component.id));
147
- const isPatternComponent = (type) => patternTypes.has(type ?? '');
148
+ const allContentfulComponentIds = new Set(Object.values(CONTENTFUL_COMPONENTS$1).map((component) => component.id));
148
149
  const isContentfulStructureComponent = (componentId) => structureComponentIds.has((componentId ?? ''));
149
150
  const isContentfulComponent = (componentId) => allContentfulComponentIds.has((componentId ?? ''));
150
- const isComponentAllowedOnRoot = ({ type, category, componentId }) => isPatternComponent(type) ||
151
- category === ASSEMBLY_DEFAULT_CATEGORY$1 ||
152
- isContentfulStructureComponent(componentId) ||
153
- componentId === CONTENTFUL_COMPONENTS$2.divider.id;
154
151
  const isStructureWithRelativeHeight = (componentId, height) => {
155
152
  return isContentfulStructureComponent(componentId) && !height?.toString().endsWith('px');
156
153
  };
@@ -418,16 +415,16 @@ const optionalBuiltInStyles = {
418
415
  validations: {
419
416
  in: [
420
417
  {
421
- value: 'left',
422
- displayName: 'Align left',
418
+ value: 'start',
419
+ displayName: 'Align start',
423
420
  },
424
421
  {
425
422
  value: 'center',
426
423
  displayName: 'Align center',
427
424
  },
428
425
  {
429
- value: 'right',
430
- displayName: 'Align right',
426
+ value: 'end',
427
+ displayName: 'Align end',
431
428
  },
432
429
  ],
433
430
  },
@@ -435,7 +432,7 @@ const optionalBuiltInStyles = {
435
432
  type: 'Text',
436
433
  group: 'style',
437
434
  description: 'The text alignment of the element',
438
- defaultValue: 'left',
435
+ defaultValue: 'start',
439
436
  },
440
437
  cfTextTransform: {
441
438
  validations: {
@@ -1193,6 +1190,10 @@ const findOutermostCoordinates = (first, second) => {
1193
1190
  left: Math.min(first.left, second.left),
1194
1191
  };
1195
1192
  };
1193
+ const isElementHidden = (rect) => {
1194
+ /** if the rect has no size and position, its element is not rendered in the DOM */
1195
+ return rect.width === 0 && rect.height === 0 && rect.x === 0 && rect.y === 0;
1196
+ };
1196
1197
  const getElementCoordinates = (element) => {
1197
1198
  const rect = element.getBoundingClientRect();
1198
1199
  /**
@@ -1432,7 +1433,14 @@ function buildTemplate({ template, context, }) {
1432
1433
 
1433
1434
  const stylesToKeep = ['cfImageAsset'];
1434
1435
  const stylesToRemove = CF_STYLE_ATTRIBUTES.filter((style) => !stylesToKeep.includes(style));
1435
- const propsToRemove = ['cfHyperlink', 'cfOpenInNewTab', 'cfSsrClassName'];
1436
+ // cfWrapColumns & cfWrapColumnsCount are no real style attributes as they are handled on the editor side
1437
+ const propsToRemove = [
1438
+ 'cfHyperlink',
1439
+ 'cfOpenInNewTab',
1440
+ 'cfSsrClassName',
1441
+ 'cfWrapColumns',
1442
+ 'cfWrapColumnsCount',
1443
+ ];
1436
1444
  const sanitizeNodeProps = (nodeProps) => {
1437
1445
  return omit(nodeProps, stylesToRemove, propsToRemove);
1438
1446
  };
@@ -1605,7 +1613,7 @@ const buildCfStyles = (values) => {
1605
1613
  };
1606
1614
  /**
1607
1615
  * Container/section default behavior:
1608
- * Default height => height: EMPTY_CONTAINER_HEIGHT
1616
+ * Default height => height: EMPTY_CONTAINER_SIZE
1609
1617
  * If a container component has children => height: 'fit-content'
1610
1618
  */
1611
1619
  const calculateNodeDefaultHeight = ({ blockId, children, value, }) => {
@@ -1615,7 +1623,7 @@ const calculateNodeDefaultHeight = ({ blockId, children, value, }) => {
1615
1623
  if (children.length) {
1616
1624
  return '100%';
1617
1625
  }
1618
- return EMPTY_CONTAINER_HEIGHT$1;
1626
+ return EMPTY_CONTAINER_SIZE$1;
1619
1627
  };
1620
1628
 
1621
1629
  function getOptimizedImageUrl(url, width, quality, format) {
@@ -2648,180 +2656,7 @@ function gatherDeepReferencesFromTree(startingNode, dataSource) {
2648
2656
  return deepReferences;
2649
2657
  }
2650
2658
 
2651
- const useDraggedItemStore = create((set) => ({
2652
- draggedItem: undefined,
2653
- hoveredComponentId: undefined,
2654
- domRect: undefined,
2655
- componentId: '',
2656
- isDraggingOnCanvas: false,
2657
- onBeforeCaptureId: '',
2658
- mouseX: 0,
2659
- mouseY: 0,
2660
- scrollY: 0,
2661
- setComponentId(id) {
2662
- set({ componentId: id });
2663
- },
2664
- setHoveredComponentId(id) {
2665
- set({ hoveredComponentId: id });
2666
- },
2667
- updateItem: (item) => {
2668
- set({ draggedItem: item });
2669
- },
2670
- setDraggingOnCanvas: (isDraggingOnCanvas) => {
2671
- set({ isDraggingOnCanvas });
2672
- },
2673
- setOnBeforeCaptureId: (onBeforeCaptureId) => {
2674
- set({ onBeforeCaptureId });
2675
- },
2676
- setMousePosition(x, y) {
2677
- set({ mouseX: x, mouseY: y });
2678
- },
2679
- setDomRect(domRect) {
2680
- set({ domRect });
2681
- },
2682
- setScrollY(y) {
2683
- set({ scrollY: y });
2684
- },
2685
- }));
2686
-
2687
- const SCROLL_STATES = {
2688
- Start: 'scrollStart',
2689
- IsScrolling: 'isScrolling',
2690
- End: 'scrollEnd',
2691
- };
2692
- const OUTGOING_EVENTS = {
2693
- Connected: 'connected',
2694
- DesignTokens: 'registerDesignTokens',
2695
- RegisteredBreakpoints: 'registeredBreakpoints',
2696
- MouseMove: 'mouseMove',
2697
- NewHoveredElement: 'newHoveredElement',
2698
- ComponentSelected: 'componentSelected',
2699
- RegisteredComponents: 'registeredComponents',
2700
- RequestComponentTreeUpdate: 'requestComponentTreeUpdate',
2701
- ComponentDragCanceled: 'componentDragCanceled',
2702
- ComponentDropped: 'componentDropped',
2703
- ComponentMoved: 'componentMoved',
2704
- CanvasReload: 'canvasReload',
2705
- UpdateSelectedComponentCoordinates: 'updateSelectedComponentCoordinates',
2706
- CanvasScroll: 'canvasScrolling',
2707
- CanvasError: 'canvasError',
2708
- ComponentMoveStarted: 'componentMoveStarted',
2709
- ComponentMoveEnded: 'componentMoveEnded',
2710
- OutsideCanvasClick: 'outsideCanvasClick',
2711
- SDKFeatures: 'sdkFeatures',
2712
- RequestEntities: 'REQUEST_ENTITIES',
2713
- };
2714
- const INCOMING_EVENTS = {
2715
- RequestEditorMode: 'requestEditorMode',
2716
- RequestReadOnlyMode: 'requestReadOnlyMode',
2717
- ExperienceUpdated: 'componentTreeUpdated',
2718
- ComponentDraggingChanged: 'componentDraggingChanged',
2719
- ComponentDragCanceled: 'componentDragCanceled',
2720
- ComponentDragStarted: 'componentDragStarted',
2721
- ComponentDragEnded: 'componentDragEnded',
2722
- ComponentMoveEnded: 'componentMoveEnded',
2723
- CanvasResized: 'canvasResized',
2724
- SelectComponent: 'selectComponent',
2725
- HoverComponent: 'hoverComponent',
2726
- UpdatedEntity: 'updatedEntity',
2727
- AssembliesAdded: 'assembliesAdded',
2728
- AssembliesRegistered: 'assembliesRegistered',
2729
- MouseMove: 'mouseMove',
2730
- RequestedEntities: 'REQUESTED_ENTITIES',
2731
- };
2732
- const INTERNAL_EVENTS = {
2733
- ComponentsRegistered: 'cfComponentsRegistered',
2734
- VisualEditorInitialize: 'cfVisualEditorInitialize',
2735
- };
2736
- const VISUAL_EDITOR_EVENTS = {
2737
- Ready: 'cfVisualEditorReady',
2738
- };
2739
- /**
2740
- * These modes are ONLY intended to be internally used within the context of
2741
- * editing an experience inside of Contentful Studio. i.e. these modes
2742
- * intentionally do not include preview/delivery modes.
2743
- */
2744
- var StudioCanvasMode$2;
2745
- (function (StudioCanvasMode) {
2746
- StudioCanvasMode["READ_ONLY"] = "readOnlyMode";
2747
- StudioCanvasMode["EDITOR"] = "editorMode";
2748
- StudioCanvasMode["NONE"] = "none";
2749
- })(StudioCanvasMode$2 || (StudioCanvasMode$2 = {}));
2750
- const CONTENTFUL_COMPONENTS$1 = {
2751
- section: {
2752
- id: 'contentful-section',
2753
- name: 'Section',
2754
- },
2755
- container: {
2756
- id: 'contentful-container',
2757
- name: 'Container',
2758
- },
2759
- columns: {
2760
- id: 'contentful-columns',
2761
- name: 'Columns',
2762
- },
2763
- singleColumn: {
2764
- id: 'contentful-single-column',
2765
- name: 'Column',
2766
- },
2767
- button: {
2768
- id: 'contentful-button',
2769
- name: 'Button',
2770
- },
2771
- heading: {
2772
- id: 'contentful-heading',
2773
- name: 'Heading',
2774
- },
2775
- image: {
2776
- id: 'contentful-image',
2777
- name: 'Image',
2778
- },
2779
- richText: {
2780
- id: 'contentful-richText',
2781
- name: 'Rich Text',
2782
- },
2783
- text: {
2784
- id: 'contentful-text',
2785
- name: 'Text',
2786
- },
2787
- divider: {
2788
- id: 'contentful-divider',
2789
- name: 'Divider',
2790
- },
2791
- carousel: {
2792
- id: 'contentful-carousel',
2793
- name: 'Carousel',
2794
- },
2795
- };
2796
- const ASSEMBLY_NODE_TYPE = 'assembly';
2797
- const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
2798
- const ASSEMBLY_BLOCK_NODE_TYPE = 'assemblyBlock';
2799
- const ASSEMBLY_NODE_TYPES = [ASSEMBLY_NODE_TYPE, ASSEMBLY_BLOCK_NODE_TYPE];
2800
- const EMPTY_CONTAINER_HEIGHT = '80px';
2801
- const HYPERLINK_DEFAULT_PATTERN = `/{locale}/{entry.fields.slug}/`;
2802
- var PostMessageMethods$2;
2803
- (function (PostMessageMethods) {
2804
- PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
2805
- PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
2806
- })(PostMessageMethods$2 || (PostMessageMethods$2 = {}));
2807
-
2808
- const DRAGGABLE_HEIGHT = 30;
2809
- const DRAGGABLE_WIDTH = 50;
2810
- const DRAG_PADDING = 4;
2811
2659
  const ROOT_ID = 'root';
2812
- const COMPONENT_LIST_ID = 'component-list';
2813
- const NEW_COMPONENT_ID = 'ctfl-new-draggable';
2814
- const CTFL_ZONE_ID = 'data-ctfl-zone-id';
2815
- const CTFL_DRAGGING_ELEMENT = 'data-ctfl-dragging-element';
2816
- const HITBOX = {
2817
- WIDTH: 70,
2818
- HEIGHT: 20,
2819
- INITIAL_OFFSET: 10,
2820
- OFFSET_INCREMENT: 8,
2821
- MIN_HEIGHT: 45,
2822
- MIN_DEPTH_HEIGHT: 20,
2823
- DEEP_ZONE: 5,
2824
- };
2825
2660
  var TreeAction;
2826
2661
  (function (TreeAction) {
2827
2662
  TreeAction[TreeAction["REMOVE_NODE"] = 0] = "REMOVE_NODE";
@@ -2831,139 +2666,6 @@ var TreeAction;
2831
2666
  TreeAction[TreeAction["REORDER_NODE"] = 4] = "REORDER_NODE";
2832
2667
  TreeAction[TreeAction["REPLACE_NODE"] = 5] = "REPLACE_NODE";
2833
2668
  })(TreeAction || (TreeAction = {}));
2834
- var HitboxDirection;
2835
- (function (HitboxDirection) {
2836
- HitboxDirection[HitboxDirection["TOP"] = 0] = "TOP";
2837
- HitboxDirection[HitboxDirection["LEFT"] = 1] = "LEFT";
2838
- HitboxDirection[HitboxDirection["RIGHT"] = 2] = "RIGHT";
2839
- HitboxDirection[HitboxDirection["BOTTOM"] = 3] = "BOTTOM";
2840
- HitboxDirection[HitboxDirection["SELF_VERTICAL"] = 4] = "SELF_VERTICAL";
2841
- HitboxDirection[HitboxDirection["SELF_HORIZONTAL"] = 5] = "SELF_HORIZONTAL";
2842
- })(HitboxDirection || (HitboxDirection = {}));
2843
- var DraggablePosition;
2844
- (function (DraggablePosition) {
2845
- DraggablePosition[DraggablePosition["CENTERED"] = 0] = "CENTERED";
2846
- DraggablePosition[DraggablePosition["MOUSE_POSITION"] = 1] = "MOUSE_POSITION";
2847
- })(DraggablePosition || (DraggablePosition = {}));
2848
-
2849
- function useDraggablePosition({ draggableId, draggableRef, position }) {
2850
- const isDraggingOnCanvas = useDraggedItemStore((state) => state.isDraggingOnCanvas);
2851
- const draggingId = useDraggedItemStore((state) => state.onBeforeCaptureId);
2852
- const preDragDomRect = useDraggedItemStore((state) => state.domRect);
2853
- useEffect(() => {
2854
- const el = draggableRef?.current ??
2855
- document.querySelector(`[${CTFL_DRAGGING_ELEMENT}][data-cf-node-id="${draggableId}"]`);
2856
- if (!isDraggingOnCanvas || draggingId !== draggableId || !el) {
2857
- return;
2858
- }
2859
- const isCentered = position === DraggablePosition.CENTERED || !preDragDomRect;
2860
- const domRect = isCentered ? el.getBoundingClientRect() : preDragDomRect;
2861
- const { mouseX, mouseY } = useDraggedItemStore.getState();
2862
- const top = isCentered ? mouseY - domRect.height / 2 : domRect.top;
2863
- const left = isCentered ? mouseX - domRect.width / 2 : domRect.left;
2864
- el.style.position = 'fixed';
2865
- el.style.left = `${left}px`;
2866
- el.style.top = `${top}px`;
2867
- el.style.width = `${domRect.width}px`;
2868
- el.style.height = `${domRect.height}px`;
2869
- }, [draggableRef, draggableId, isDraggingOnCanvas, draggingId, position, preDragDomRect]);
2870
- }
2871
-
2872
- function getStyle$2(style, snapshot) {
2873
- if (!snapshot.isDropAnimating) {
2874
- return style;
2875
- }
2876
- return {
2877
- ...style,
2878
- // cannot be 0, but make it super tiny
2879
- transitionDuration: `0.001s`,
2880
- };
2881
- }
2882
- const DraggableContainer = ({ id }) => {
2883
- const ref = useRef(null);
2884
- useDraggablePosition({
2885
- draggableId: id,
2886
- draggableRef: ref,
2887
- position: DraggablePosition.CENTERED,
2888
- });
2889
- return (React.createElement("div", { id: COMPONENT_LIST_ID, style: {
2890
- position: 'absolute',
2891
- top: 0,
2892
- left: 0,
2893
- pointerEvents: 'none',
2894
- zIndex: -1,
2895
- } },
2896
- React.createElement(Droppable, { droppableId: COMPONENT_LIST_ID, isDropDisabled: true }, (provided) => (React.createElement("div", { ...provided.droppableProps, ref: provided.innerRef },
2897
- 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) => {
2898
- provided.innerRef(node);
2899
- ref.current = node;
2900
- }, ...provided.draggableProps, ...provided.dragHandleProps, style: {
2901
- ...getStyle$2(provided.draggableProps.style, snapshot),
2902
- width: DRAGGABLE_WIDTH,
2903
- height: DRAGGABLE_HEIGHT,
2904
- pointerEvents: 'none',
2905
- } }))),
2906
- provided.placeholder)))));
2907
- };
2908
-
2909
- function getItemFromTree(id, node) {
2910
- // Check if the current node's id matches the search id
2911
- if (node.data.id === id) {
2912
- return node;
2913
- }
2914
- // Recursively search through each child
2915
- for (const child of node.children) {
2916
- const foundNode = getItemFromTree(id, child);
2917
- if (foundNode) {
2918
- // Node found in children
2919
- return foundNode;
2920
- }
2921
- }
2922
- // If the node is not found in this branch of the tree, return undefined
2923
- return undefined;
2924
- }
2925
- function findDepthById(node, id, currentDepth = 1) {
2926
- if (node.data.id === id) {
2927
- return currentDepth;
2928
- }
2929
- // If the node has children, check each one
2930
- for (const child of node.children) {
2931
- const childDepth = findDepthById(child, id, currentDepth + 1);
2932
- if (childDepth !== -1) {
2933
- return childDepth; // Found the node in a child
2934
- }
2935
- }
2936
- return -1; // Node not found in this branch
2937
- }
2938
- const getChildFromTree = (parentId, index, node) => {
2939
- // Check if the current node's id matches the search id
2940
- if (node.data.id === parentId) {
2941
- return node.children[index];
2942
- }
2943
- // Recursively search through each child
2944
- for (const child of node.children) {
2945
- const foundNode = getChildFromTree(parentId, index, child);
2946
- if (foundNode) {
2947
- // Node found in children
2948
- return foundNode;
2949
- }
2950
- }
2951
- // If the node is not found in this branch of the tree, return undefined
2952
- return undefined;
2953
- };
2954
- const getItem = (selector, tree) => {
2955
- return getItemFromTree(selector.id, {
2956
- type: 'block',
2957
- data: {
2958
- id: ROOT_ID,
2959
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
2960
- },
2961
- children: tree.root.children,
2962
- });
2963
- };
2964
- const getItemDepthFromNode = (selector, node) => {
2965
- return findDepthById(node, selector.id);
2966
- };
2967
2669
 
2968
2670
  function updateNode(nodeId, updatedNode, node) {
2969
2671
  if (node.data.id === nodeId) {
@@ -3002,24 +2704,33 @@ function addChildNode(indexToAdd, parentNodeId, nodeToAdd, node) {
3002
2704
  }
3003
2705
  node.children.forEach((childNode) => addChildNode(indexToAdd, parentNodeId, nodeToAdd, childNode));
3004
2706
  }
3005
- function reorderChildNode(oldIndex, newIndex, parentNodeId, node) {
3006
- if (node.data.id === parentNodeId) {
3007
- // Remove the child from the old position
3008
- const [childToMove] = node.children.splice(oldIndex, 1);
3009
- // Insert the child at the new position
3010
- node.children.splice(newIndex, 0, childToMove);
3011
- return;
2707
+
2708
+ function getItemFromTree(id, node) {
2709
+ // Check if the current node's id matches the search id
2710
+ if (node.data.id === id) {
2711
+ return node;
3012
2712
  }
3013
- node.children.forEach((childNode) => reorderChildNode(oldIndex, newIndex, parentNodeId, childNode));
3014
- }
3015
- function reparentChildNode(oldIndex, newIndex, sourceNodeId, destinationNodeId, node) {
3016
- const nodeToMove = getChildFromTree(sourceNodeId, oldIndex, node);
3017
- if (!nodeToMove) {
3018
- return;
2713
+ // Recursively search through each child
2714
+ for (const child of node.children) {
2715
+ const foundNode = getItemFromTree(id, child);
2716
+ if (foundNode) {
2717
+ // Node found in children
2718
+ return foundNode;
2719
+ }
3019
2720
  }
3020
- removeChildNode(oldIndex, nodeToMove.data.id, sourceNodeId, node);
3021
- addChildNode(newIndex, destinationNodeId, nodeToMove, node);
2721
+ // If the node is not found in this branch of the tree, return undefined
2722
+ return undefined;
3022
2723
  }
2724
+ const getItem = (selector, tree) => {
2725
+ return getItemFromTree(selector.id, {
2726
+ type: 'block',
2727
+ data: {
2728
+ id: ROOT_ID,
2729
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2730
+ },
2731
+ children: tree.root.children,
2732
+ });
2733
+ };
3023
2734
 
3024
2735
  function missingNodeAction({ index, nodeAdded, child, tree, parentNodeId, currentNode, }) {
3025
2736
  if (nodeAdded) {
@@ -3136,54 +2847,119 @@ function getTreeDiffs(tree1, tree2, originalTree) {
3136
2847
  return differences.filter((diff) => diff);
3137
2848
  }
3138
2849
 
3139
- const useTreeStore = create((set, get) => ({
3140
- tree: {
3141
- root: {
3142
- children: [],
3143
- type: 'root',
3144
- data: {
3145
- breakpoints: [],
3146
- dataSource: {},
3147
- id: ROOT_ID,
3148
- props: {},
3149
- unboundValues: {},
3150
- },
3151
- },
3152
- },
3153
- breakpoints: [],
3154
- updateNodesByUpdatedEntity: (entityId) => {
3155
- set(produce((draftState) => {
3156
- treeVisit(draftState.tree.root, (node) => {
3157
- if (isAssemblyNode(node) && node.data.blockId === entityId) {
3158
- // Cannot use `structuredClone()` as node is probably a Proxy object with weird references
3159
- updateNode(node.data.id, cloneDeepAsPOJO(node), draftState.tree.root);
3160
- return;
3161
- }
3162
- const dataSourceIds = Object.values(node.data.dataSource).map((link) => link.sys.id);
3163
- if (dataSourceIds.includes(entityId)) {
3164
- // Cannot use `structuredClone()` as node is probably a Proxy object with weird references
3165
- updateNode(node.data.id, cloneDeepAsPOJO(node), draftState.tree.root);
3166
- }
3167
- });
3168
- }));
3169
- },
3170
- /**
3171
- * NOTE: this is for debugging purposes only as it causes ugly canvas flash.
3172
- *
3173
- * Force updates entire tree. Usually shouldn't be used as updateTree()
3174
- * uses smart update algorithm based on diffs. But for troubleshooting
3175
- * you may want to force update the tree so leaving this in.
3176
- */
3177
- updateTreeForced: (tree) => {
3178
- set({
3179
- tree,
3180
- // Breakpoints must be updated, as we receive completely new tree with possibly new breakpoints
3181
- breakpoints: tree?.root?.data?.breakpoints || [],
3182
- });
3183
- },
3184
- updateTree: (tree) => {
3185
- const currentTree = get().tree;
3186
- /**
2850
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2851
+ const OUTGOING_EVENTS = {
2852
+ Connected: 'connected',
2853
+ DesignTokens: 'registerDesignTokens',
2854
+ RegisteredBreakpoints: 'registeredBreakpoints',
2855
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2856
+ MouseMove: 'mouseMove',
2857
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2858
+ ComponentSelected: 'componentSelected',
2859
+ RegisteredComponents: 'registeredComponents',
2860
+ RequestComponentTreeUpdate: 'requestComponentTreeUpdate',
2861
+ CanvasReload: 'canvasReload',
2862
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2863
+ UpdateSelectedComponentCoordinates: 'updateSelectedComponentCoordinates',
2864
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2865
+ CanvasScroll: 'canvasScrolling',
2866
+ CanvasError: 'canvasError',
2867
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2868
+ OutsideCanvasClick: 'outsideCanvasClick',
2869
+ SDKFeatures: 'sdkFeatures',
2870
+ RequestEntities: 'REQUEST_ENTITIES',
2871
+ CanvasGeometryUpdated: 'canvasGeometryUpdated',
2872
+ };
2873
+ const INCOMING_EVENTS = {
2874
+ RequestEditorMode: 'requestEditorMode',
2875
+ RequestReadOnlyMode: 'requestReadOnlyMode',
2876
+ ExperienceUpdated: 'componentTreeUpdated',
2877
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2878
+ ComponentDraggingChanged: 'componentDraggingChanged',
2879
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2880
+ ComponentDragCanceled: 'componentDragCanceled',
2881
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2882
+ ComponentDragStarted: 'componentDragStarted',
2883
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2884
+ ComponentDragEnded: 'componentDragEnded',
2885
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2886
+ ComponentMoveEnded: 'componentMoveEnded',
2887
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2888
+ CanvasResized: 'canvasResized',
2889
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2890
+ SelectComponent: 'selectComponent',
2891
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2892
+ HoverComponent: 'hoverComponent',
2893
+ UpdatedEntity: 'updatedEntity',
2894
+ AssembliesAdded: 'assembliesAdded',
2895
+ AssembliesRegistered: 'assembliesRegistered',
2896
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
2897
+ MouseMove: 'mouseMove',
2898
+ RequestedEntities: 'REQUESTED_ENTITIES',
2899
+ };
2900
+ const INTERNAL_EVENTS = {
2901
+ ComponentsRegistered: 'cfComponentsRegistered',
2902
+ VisualEditorInitialize: 'cfVisualEditorInitialize',
2903
+ };
2904
+ const VISUAL_EDITOR_EVENTS = {
2905
+ Ready: 'cfVisualEditorReady',
2906
+ };
2907
+ /**
2908
+ * These modes are ONLY intended to be internally used within the context of
2909
+ * editing an experience inside of Contentful Studio. i.e. these modes
2910
+ * intentionally do not include preview/delivery modes.
2911
+ */
2912
+ var StudioCanvasMode$2;
2913
+ (function (StudioCanvasMode) {
2914
+ StudioCanvasMode["READ_ONLY"] = "readOnlyMode";
2915
+ StudioCanvasMode["EDITOR"] = "editorMode";
2916
+ StudioCanvasMode["NONE"] = "none";
2917
+ })(StudioCanvasMode$2 || (StudioCanvasMode$2 = {}));
2918
+ const ASSEMBLY_NODE_TYPE = 'assembly';
2919
+ const ASSEMBLY_DEFAULT_CATEGORY = 'Assemblies';
2920
+ const ASSEMBLY_BLOCK_NODE_TYPE = 'assemblyBlock';
2921
+ const EMPTY_CONTAINER_SIZE = '80px';
2922
+ const HYPERLINK_DEFAULT_PATTERN = `/{locale}/{entry.fields.slug}/`;
2923
+ var PostMessageMethods$2;
2924
+ (function (PostMessageMethods) {
2925
+ PostMessageMethods["REQUEST_ENTITIES"] = "REQUEST_ENTITIES";
2926
+ PostMessageMethods["REQUESTED_ENTITIES"] = "REQUESTED_ENTITIES";
2927
+ })(PostMessageMethods$2 || (PostMessageMethods$2 = {}));
2928
+
2929
+ const useTreeStore = create((set, get) => ({
2930
+ tree: {
2931
+ root: {
2932
+ children: [],
2933
+ type: 'root',
2934
+ data: {
2935
+ breakpoints: [],
2936
+ dataSource: {},
2937
+ id: ROOT_ID,
2938
+ props: {},
2939
+ unboundValues: {},
2940
+ },
2941
+ },
2942
+ },
2943
+ breakpoints: [],
2944
+ updateNodesByUpdatedEntity: (entityId) => {
2945
+ set(produce((draftState) => {
2946
+ treeVisit(draftState.tree.root, (node) => {
2947
+ if (isAssemblyNode(node) && node.data.blockId === entityId) {
2948
+ // Cannot use `structuredClone()` as node is probably a Proxy object with weird references
2949
+ updateNode(node.data.id, cloneDeepAsPOJO(node), draftState.tree.root);
2950
+ return;
2951
+ }
2952
+ const dataSourceIds = Object.values(node.data.dataSource).map((link) => link.sys.id);
2953
+ if (dataSourceIds.includes(entityId)) {
2954
+ // Cannot use `structuredClone()` as node is probably a Proxy object with weird references
2955
+ updateNode(node.data.id, cloneDeepAsPOJO(node), draftState.tree.root);
2956
+ }
2957
+ });
2958
+ }));
2959
+ },
2960
+ updateTree: (tree) => {
2961
+ const currentTree = get().tree;
2962
+ /**
3187
2963
  * If we simply update the tree as in:
3188
2964
  *
3189
2965
  * `state.tree = tree`
@@ -3226,21 +3002,6 @@ const useTreeStore = create((set, get) => ({
3226
3002
  state.breakpoints = tree?.root?.data?.breakpoints || [];
3227
3003
  }));
3228
3004
  },
3229
- addChild: (index, parentId, node) => {
3230
- set(produce((state) => {
3231
- addChildNode(index, parentId, node, state.tree.root);
3232
- }));
3233
- },
3234
- reorderChildren: (destinationIndex, destinationParentId, sourceIndex) => {
3235
- set(produce((state) => {
3236
- reorderChildNode(sourceIndex, destinationIndex, destinationParentId, state.tree.root);
3237
- }));
3238
- },
3239
- reparentChild: (destinationIndex, destinationParentId, sourceIndex, sourceParentId) => {
3240
- set(produce((state) => {
3241
- reparentChildNode(sourceIndex, destinationIndex, sourceParentId, destinationParentId, state.tree.root);
3242
- }));
3243
- },
3244
3005
  // breadth first search
3245
3006
  findNodeById(nodeId) {
3246
3007
  if (!nodeId) {
@@ -3278,8 +3039,8 @@ const cloneDeepAsPOJO = (obj) => {
3278
3039
  return JSON.parse(JSON.stringify(obj));
3279
3040
  };
3280
3041
 
3281
- var css_248z$a = ".render-module_hitbox__l4ysJ {\n position: absolute;\n top: 0;\n left: 0;\n width: 100%;\n height: 10px;\n z-index: 1000000;\n}\n\n.render-module_hitboxLower__tgsA1 {\n position: absolute;\n bottom: -20px;\n left: 0;\n width: 100%;\n height: 20px;\n z-index: 1000000;\n}\n\n.render-module_canvasBottomSpacer__JuxVh {\n position: absolute;\n width: 100%;\n height: 50px;\n}\n\n.render-module_container__-C3d7 {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\nbody {\n margin: 0;\n}\n\nhtml {\n -ms-overflow-style: none; /* Internet Explorer 10+ */\n scrollbar-width: none; /* Firefox */\n}\n\nhtml::-webkit-scrollbar {\n display: none;\n}\n";
3282
- var styles$3 = {"hitbox":"render-module_hitbox__l4ysJ","hitboxLower":"render-module_hitboxLower__tgsA1","canvasBottomSpacer":"render-module_canvasBottomSpacer__JuxVh","container":"render-module_container__-C3d7"};
3042
+ var css_248z$a = ".RootRenderer-module_rootContainer__9UawM {\n position: relative;\n display: flex;\n flex-direction: column;\n}\n\nbody {\n margin: 0;\n}\n\nhtml {\n -ms-overflow-style: none; /* Internet Explorer 10+ */\n scrollbar-width: none; /* Firefox */\n}\n\nhtml::-webkit-scrollbar {\n display: none;\n}\n";
3043
+ var styles$2 = {"rootContainer":"RootRenderer-module_rootContainer__9UawM"};
3283
3044
  styleInject(css_248z$a);
3284
3045
 
3285
3046
  // TODO: In order to support integrations without React, we should extract this heavy logic into simple
@@ -3318,66 +3079,6 @@ const useBreakpoints = (breakpoints) => {
3318
3079
  return { resolveDesignValue };
3319
3080
  };
3320
3081
 
3321
- /**
3322
- * This function gets the element co-ordinates of a specified component in the DOM and its parent
3323
- * and sends the DOM Rect to the client app
3324
- */
3325
- const sendSelectedComponentCoordinates = (instanceId) => {
3326
- const selection = getSelectionNodes(instanceId);
3327
- if (selection?.target) {
3328
- const sendUpdateSelectedComponentCoordinates = () => {
3329
- sendMessage(OUTGOING_EVENTS.UpdateSelectedComponentCoordinates, {
3330
- selectedNodeCoordinates: getElementCoordinates(selection.target),
3331
- selectedAssemblyChildCoordinates: selection.patternChild
3332
- ? getElementCoordinates(selection.patternChild)
3333
- : undefined,
3334
- parentCoordinates: selection.parent ? getElementCoordinates(selection.parent) : undefined,
3335
- });
3336
- };
3337
- // If the target contains an image, wait for this image to be loaded before sending the coordinates
3338
- const childImage = selection.target.querySelector('img');
3339
- if (childImage) {
3340
- const handleImageLoad = () => {
3341
- sendUpdateSelectedComponentCoordinates();
3342
- childImage.removeEventListener('load', handleImageLoad);
3343
- };
3344
- childImage.addEventListener('load', handleImageLoad);
3345
- }
3346
- sendUpdateSelectedComponentCoordinates();
3347
- }
3348
- };
3349
- const getSelectionNodes = (instanceId) => {
3350
- if (!instanceId)
3351
- return;
3352
- let selectedNode = document.querySelector(`[data-cf-node-id="${instanceId}"]`);
3353
- let selectedPatternChild = null;
3354
- let selectedParent = null;
3355
- // Use RegEx instead of split to match the last occurrence of '---' in the instanceId instead of the first one
3356
- const idMatch = instanceId.match(/(.*)---(.*)/);
3357
- const rootNodeId = idMatch?.[1] ?? instanceId;
3358
- const nodeLocation = idMatch?.[2];
3359
- const isNestedPattern = nodeLocation && selectedNode?.dataset?.cfNodeBlockType === ASSEMBLY_NODE_TYPE;
3360
- const isPatternChild = !isNestedPattern && nodeLocation;
3361
- if (isPatternChild) {
3362
- // For pattern child nodes, render the pattern itself as selected component
3363
- selectedPatternChild = selectedNode;
3364
- selectedNode = document.querySelector(`[data-cf-node-id="${rootNodeId}"]`);
3365
- }
3366
- else if (isNestedPattern) {
3367
- // For nested patterns, return the upper pattern as parent
3368
- selectedParent = document.querySelector(`[data-cf-node-id="${rootNodeId}"]`);
3369
- }
3370
- else {
3371
- // Find the next valid parent of the selected element
3372
- selectedParent = selectedNode?.parentElement ?? null;
3373
- // Ensure that the selection parent is a VisualEditorBlock
3374
- while (selectedParent && !selectedParent.dataset?.cfNodeId) {
3375
- selectedParent = selectedParent?.parentElement;
3376
- }
3377
- }
3378
- return { target: selectedNode, patternChild: selectedPatternChild, parent: selectedParent };
3379
- };
3380
-
3381
3082
  // Note: During development, the hot reloading might empty this and it
3382
3083
  // stays empty leading to not rendering assemblies. Ideally, this is
3383
3084
  // integrated into the state machine to keep track of its state.
@@ -3411,14 +3112,10 @@ const useEditorStore = create((set, get) => ({
3411
3112
  dataSource: {},
3412
3113
  hyperLinkPattern: undefined,
3413
3114
  unboundValues: {},
3414
- selectedNodeId: null,
3415
3115
  locale: null,
3416
3116
  setHyperLinkPattern: (pattern) => {
3417
3117
  set({ hyperLinkPattern: pattern });
3418
3118
  },
3419
- setSelectedNodeId: (id) => {
3420
- set({ selectedNodeId: id });
3421
- },
3422
3119
  setDataSource(data) {
3423
3120
  const dataSource = get().dataSource;
3424
3121
  const newDataSource = { ...dataSource, ...data };
@@ -3450,6 +3147,7 @@ const useEditorStore = create((set, get) => ({
3450
3147
  var css_248z$8 = "@import url(https://fonts.googleapis.com/css2?family=Archivo:ital,wght@0,400;0,500;0,600;1,400;1,600&display=swap);:root{--cf-color-white:#fff;--cf-color-black:#000;--cf-color-gray100:#f7f9fa;--cf-color-gray400:#aec1cc;--cf-color-gray400-rgb:174,193,204;--cf-spacing-0:0rem;--cf-spacing-1:0.125rem;--cf-spacing-2:0.25rem;--cf-spacing-3:0.375rem;--cf-spacing-4:0.5rem;--cf-spacing-5:0.625rem;--cf-spacing-6:0.75rem;--cf-spacing-7:0.875rem;--cf-spacing-8:1rem;--cf-spacing-9:1.25rem;--cf-spacing-10:1.5rem;--cf-spacing-11:1.75rem;--cf-spacing-12:2rem;--cf-spacing-13:2.25rem;--cf-text-xs:0.75rem;--cf-text-sm:0.875rem;--cf-text-base:1rem;--cf-text-lg:1.125rem;--cf-text-xl:1.25rem;--cf-text-2xl:1.5rem;--cf-text-3xl:2rem;--cf-text-4xl:2.75rem;--cf-font-light:300;--cf-font-normal:400;--cf-font-medium:500;--cf-font-semibold:600;--cf-font-bold:700;--cf-font-extra-bold:800;--cf-font-black:900;--cf-border-radius-none:0px;--cf-border-radius-sm:0.125rem;--cf-border-radius:0.25rem;--cf-border-radius-md:0.375rem;--cf-border-radius-lg:0.5rem;--cf-border-radius-xl:0.75rem;--cf-border-radius-2xl:1rem;--cf-border-radius-3xl:1.5rem;--cf-border-radius-full:9999px;--cf-font-family-sans:Archivo,Helvetica,Arial,sans-serif;--cf-font-family-serif:Georgia,Cambria,Times New Roman,Times,serif;--cf-max-width-full:100%;--cf-button-bg:var(--cf-color-black);--cf-button-color:var(--cf-color-white);--cf-text-color:var(--cf-color-black)}*{box-sizing:border-box}";
3451
3148
  styleInject(css_248z$8);
3452
3149
 
3150
+ /** @deprecated will be removed when dropping backward compatibility for old DND */
3453
3151
  /**
3454
3152
  * These modes are ONLY intended to be internally used within the context of
3455
3153
  * editing an experience inside of Contentful Studio. i.e. these modes
@@ -4454,8 +4152,8 @@ var VisualEditorMode;
4454
4152
  VisualEditorMode["InjectScript"] = "injectScript";
4455
4153
  })(VisualEditorMode || (VisualEditorMode = {}));
4456
4154
 
4457
- var css_248z$2$1 = ".contentful-container{display:flex;pointer-events:all;position:relative}.contentful-container::-webkit-scrollbar{display:none}.cf-single-column-wrapper{position:relative}.cf-container-wrapper{position:relative;width:100%}.contentful-container:after{align-items:center;bottom:0;color:var(--exp-builder-gray400);content:\"\";display:block;display:flex;font-family:var(--exp-builder-font-stack-primary);font-size:12px;justify-content:center;left:0;overflow-x:clip;pointer-events:none;position:absolute;right:0;top:0;z-index:1}.contentful-section-label:after{content:\"Section\"}.contentful-container-label:after{content:\"Container\"}.contentful-container-link,.contentful-container-link:active,.contentful-container-link:focus-visible,.contentful-container-link:hover,.contentful-container-link:read-write,.contentful-container-link:visited{color:inherit;outline:unset;text-decoration:unset}";
4458
- styleInject(css_248z$2$1);
4155
+ var css_248z$2 = ".contentful-container{display:flex;pointer-events:all;position:relative}.contentful-container::-webkit-scrollbar{display:none}.cf-container-wrapper{position:relative;width:100%}.contentful-container:after{align-items:center;bottom:0;color:var(--exp-builder-gray400);content:\"\";display:block;display:flex;font-family:var(--exp-builder-font-stack-primary);font-size:12px;justify-content:center;left:0;overflow-x:clip;pointer-events:none;position:absolute;right:0;top:0;z-index:1}.contentful-section-label:after{content:\"Section\"}.contentful-container-label:after{content:\"Container\"}.contentful-container-link,.contentful-container-link:active,.contentful-container-link:focus-visible,.contentful-container-link:hover,.contentful-container-link:read-write,.contentful-container-link:visited{color:inherit;outline:unset;text-decoration:unset}";
4156
+ styleInject(css_248z$2);
4459
4157
 
4460
4158
  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) => {
4461
4159
  return (React.createElement("div", { id: id, ref: ref, style: {
@@ -4479,113 +4177,24 @@ const Flex = forwardRef(({ id, children, onMouseEnter, onMouseUp, onMouseLeave,
4479
4177
  });
4480
4178
  Flex.displayName = 'Flex';
4481
4179
 
4482
- var css_248z$1$1 = ".cf-divider{display:contents;height:100%;position:relative;width:100%}.cf-divider hr{border:none}[data-ctfl-zone-id=root] .cf-divider:before{bottom:-5px;content:\"\";left:-5px;pointer-events:all;position:absolute;right:-5px;top:-5px}";
4180
+ var css_248z$1$1 = ".cf-divider{display:contents;height:100%;position:relative;width:100%}.cf-divider hr{border:none}";
4483
4181
  styleInject(css_248z$1$1);
4484
4182
 
4485
- var css_248z$9 = ".cf-columns{display:flex;flex-direction:column;gap:24px;grid-template-columns:repeat(12,1fr);min-height:0;min-width:0}@media (min-width:768px){.cf-columns{display:grid}}.cf-single-column-wrapper{display:flex;position:relative}.cf-single-column{pointer-events:all}.cf-single-column-wrapper:after{align-items:center;bottom:0;color:var(--exp-builder-gray400);content:\"\";display:block;display:flex;font-family:var(--exp-builder-font-stack-primary);font-size:12px;justify-content:center;left:0;overflow-x:clip;pointer-events:none;position:absolute;right:0;top:0;z-index:1}.cf-single-column-label:after{content:\"Column\"}";
4183
+ var css_248z$9 = ".cf-columns{display:flex;flex-direction:column;gap:24px;grid-template-columns:repeat(12,1fr);min-height:0;min-width:0}@media (min-width:768px){.cf-columns{display:grid}}.cf-single-column-wrapper{position:relative}.cf-single-column-wrapper:after{align-items:center;bottom:0;color:var(--exp-builder-gray400);content:\"\";display:block;display:flex;font-family:var(--exp-builder-font-stack-primary);font-size:12px;justify-content:center;left:0;overflow-x:clip;pointer-events:none;position:absolute;right:0;top:0;z-index:1}.cf-single-column-label:after{content:\"Column\"}";
4486
4184
  styleInject(css_248z$9);
4487
4185
 
4488
- const ColumnWrapper = forwardRef((props, ref) => {
4489
- return (React.createElement("div", { ref: ref, ...props, style: {
4490
- ...(props.style || {}),
4491
- display: 'grid',
4492
- gridTemplateColumns: 'repeat(12, [col-start] 1fr)',
4493
- } }, props.children));
4494
- });
4495
- ColumnWrapper.displayName = 'ColumnWrapper';
4496
-
4497
4186
  const assemblyStyle = { display: 'contents' };
4498
- // Feel free to do any magic as regards variable definitions for assemblies
4499
- // Or if this isn't necessary by the time we figure that part out, we can bid this part farewell
4500
4187
  const Assembly = (props) => {
4501
- if (props.editorMode) {
4502
- const { node, dragProps, ...editorModeProps } = props;
4503
- return props.renderDropzone(node, {
4504
- ...editorModeProps,
4505
- ['data-test-id']: 'contentful-assembly',
4506
- className: props.className,
4507
- dragProps,
4508
- });
4509
- }
4510
4188
  // Using a display contents so assembly content/children
4511
4189
  // can appear as if they are direct children of the div wrapper's parent
4512
4190
  return React.createElement("div", { "data-test-id": "assembly", ...props, style: assemblyStyle });
4513
4191
  };
4514
4192
 
4515
- class DragState {
4516
- constructor() {
4517
- this.isDragStartedOnParent = false;
4518
- this.isDraggingItem = false;
4519
- }
4520
- get isDragging() {
4521
- return this.isDraggingItem;
4522
- }
4523
- get isDraggingOnParent() {
4524
- return this.isDragStartedOnParent;
4525
- }
4526
- updateIsDragging(isDraggingItem) {
4527
- this.isDraggingItem = isDraggingItem;
4528
- }
4529
- updateIsDragStartedOnParent(isDragStartedOnParent) {
4530
- this.isDragStartedOnParent = isDragStartedOnParent;
4531
- }
4532
- resetState() {
4533
- this.isDraggingItem = false;
4534
- this.isDragStartedOnParent = false;
4535
- }
4536
- }
4537
-
4538
- class SimulateDnD extends DragState {
4539
- constructor() {
4540
- super();
4541
- this.draggingElement = null;
4542
- }
4543
- setupDrag() {
4544
- this.updateIsDragStartedOnParent(true);
4545
- }
4546
- startDrag(coordX, coordY) {
4547
- this.draggingElement = document.getElementById(NEW_COMPONENT_ID);
4548
- this.updateIsDragging(true);
4549
- this.simulateMouseEvent(coordX, coordY, 'mousedown');
4550
- }
4551
- updateDrag(coordX, coordY) {
4552
- if (!this.draggingElement) {
4553
- this.draggingElement = document.querySelector(`[${CTFL_DRAGGING_ELEMENT}]`);
4554
- }
4555
- this.simulateMouseEvent(coordX, coordY);
4556
- }
4557
- endDrag(coordX, coordY) {
4558
- this.simulateMouseEvent(coordX, coordY, 'mouseup');
4559
- this.reset();
4560
- }
4561
- reset() {
4562
- this.draggingElement = null;
4563
- this.resetState();
4564
- }
4565
- simulateMouseEvent(coordX, coordY, eventName = 'mousemove') {
4566
- if (!this.draggingElement) {
4567
- return;
4568
- }
4569
- const options = {
4570
- bubbles: true,
4571
- cancelable: true,
4572
- view: window,
4573
- pageX: 0,
4574
- pageY: 0,
4575
- clientX: coordX,
4576
- clientY: coordY,
4577
- };
4578
- const event = new MouseEvent(eventName, options);
4579
- this.draggingElement.dispatchEvent(event);
4580
- }
4581
- }
4582
- var SimulateDnD$1 = new SimulateDnD();
4583
-
4584
- function useEditorSubscriber(entityCache) {
4585
- const entityStore = entityCache((state) => state.entityStore);
4586
- const areEntitiesFetched = entityCache((state) => state.areEntitiesFetched);
4587
- const setEntitiesFetched = entityCache((state) => state.setEntitiesFetched);
4588
- const resetEntityStore = entityCache((state) => state.resetEntityStore);
4193
+ function useEditorSubscriber(inMemoryEntitiesStore) {
4194
+ const entityStore = inMemoryEntitiesStore((state) => state.entityStore);
4195
+ const areEntitiesFetched = inMemoryEntitiesStore((state) => state.areEntitiesFetched);
4196
+ const setEntitiesFetched = inMemoryEntitiesStore((state) => state.setEntitiesFetched);
4197
+ const resetEntityStore = inMemoryEntitiesStore((state) => state.resetEntityStore);
4589
4198
  const { updateTree, updateNodesByUpdatedEntity } = useTreeStore((state) => ({
4590
4199
  updateTree: state.updateTree,
4591
4200
  updateNodesByUpdatedEntity: state.updateNodesByUpdatedEntity,
@@ -4595,13 +4204,6 @@ function useEditorSubscriber(entityCache) {
4595
4204
  const setLocale = useEditorStore((state) => state.setLocale);
4596
4205
  const setUnboundValues = useEditorStore((state) => state.setUnboundValues);
4597
4206
  const setDataSource = useEditorStore((state) => state.setDataSource);
4598
- const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
4599
- const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
4600
- const setComponentId = useDraggedItemStore((state) => state.setComponentId);
4601
- const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
4602
- const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
4603
- const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
4604
- const setScrollY = useDraggedItemStore((state) => state.setScrollY);
4605
4207
  const reloadApp = () => {
4606
4208
  sendMessage(OUTGOING_EVENTS.CanvasReload, undefined);
4607
4209
  // Wait a moment to ensure that the message was sent
@@ -4779,27 +4381,6 @@ function useEditorSubscriber(entityCache) {
4779
4381
  }
4780
4382
  break;
4781
4383
  }
4782
- case INCOMING_EVENTS.CanvasResized: {
4783
- const { selectedNodeId } = eventData.payload;
4784
- if (selectedNodeId) {
4785
- sendSelectedComponentCoordinates(selectedNodeId);
4786
- }
4787
- break;
4788
- }
4789
- case INCOMING_EVENTS.HoverComponent: {
4790
- const { hoveredNodeId } = eventData.payload;
4791
- setHoveredComponentId(hoveredNodeId);
4792
- break;
4793
- }
4794
- case INCOMING_EVENTS.ComponentDraggingChanged: {
4795
- const { isDragging } = eventData.payload;
4796
- if (!isDragging) {
4797
- setComponentId('');
4798
- setDraggingOnCanvas(false);
4799
- SimulateDnD$1.reset();
4800
- }
4801
- break;
4802
- }
4803
4384
  case INCOMING_EVENTS.UpdatedEntity: {
4804
4385
  const { entity: updatedEntity, shouldRerender } = eventData.payload;
4805
4386
  if (updatedEntity) {
@@ -4816,51 +4397,6 @@ function useEditorSubscriber(entityCache) {
4816
4397
  case INCOMING_EVENTS.RequestEditorMode: {
4817
4398
  break;
4818
4399
  }
4819
- case INCOMING_EVENTS.ComponentDragCanceled: {
4820
- if (SimulateDnD$1.isDragging) {
4821
- //simulate a mouseup event to cancel the drag
4822
- SimulateDnD$1.endDrag(0, 0);
4823
- }
4824
- break;
4825
- }
4826
- case INCOMING_EVENTS.ComponentDragStarted: {
4827
- const { id, isAssembly } = eventData.payload;
4828
- SimulateDnD$1.setupDrag();
4829
- setComponentId(`${id}:${isAssembly}` || '');
4830
- setDraggingOnCanvas(true);
4831
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
4832
- nodeId: '',
4833
- });
4834
- break;
4835
- }
4836
- case INCOMING_EVENTS.ComponentDragEnded: {
4837
- SimulateDnD$1.reset();
4838
- setComponentId('');
4839
- setDraggingOnCanvas(false);
4840
- break;
4841
- }
4842
- case INCOMING_EVENTS.SelectComponent: {
4843
- const { selectedNodeId: nodeId } = eventData.payload;
4844
- setSelectedNodeId(nodeId);
4845
- sendSelectedComponentCoordinates(nodeId);
4846
- break;
4847
- }
4848
- case INCOMING_EVENTS.MouseMove: {
4849
- const { mouseX, mouseY } = eventData.payload;
4850
- setMousePosition(mouseX, mouseY);
4851
- if (SimulateDnD$1.isDraggingOnParent && !SimulateDnD$1.isDragging) {
4852
- SimulateDnD$1.startDrag(mouseX, mouseY);
4853
- }
4854
- else {
4855
- SimulateDnD$1.updateDrag(mouseX, mouseY);
4856
- }
4857
- break;
4858
- }
4859
- case INCOMING_EVENTS.ComponentMoveEnded: {
4860
- const { mouseX, mouseY } = eventData.payload;
4861
- SimulateDnD$1.endDrag(mouseX, mouseY);
4862
- break;
4863
- }
4864
4400
  default:
4865
4401
  console.error(`[experiences-sdk-react::onMessage] Logic error, unsupported eventType: [${eventData.eventType}]`);
4866
4402
  }
@@ -4871,11 +4407,8 @@ function useEditorSubscriber(entityCache) {
4871
4407
  };
4872
4408
  }, [
4873
4409
  entityStore,
4874
- setComponentId,
4875
- setDraggingOnCanvas,
4876
4410
  setDataSource,
4877
4411
  setLocale,
4878
- setSelectedNodeId,
4879
4412
  dataSource,
4880
4413
  areEntitiesFetched,
4881
4414
  fetchMissingEntities,
@@ -4883,328 +4416,91 @@ function useEditorSubscriber(entityCache) {
4883
4416
  unboundValues,
4884
4417
  updateTree,
4885
4418
  updateNodesByUpdatedEntity,
4886
- setMousePosition,
4887
4419
  resetEntityStore,
4888
- setHoveredComponentId,
4889
4420
  ]);
4890
- /*
4891
- * Handles on scroll business
4892
- */
4893
- useEffect(() => {
4894
- let timeoutId = 0;
4895
- let isScrolling = false;
4896
- const onScroll = () => {
4897
- setScrollY(window.scrollY);
4898
- if (isScrolling === false) {
4899
- sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.Start);
4900
- }
4901
- sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.IsScrolling);
4902
- isScrolling = true;
4903
- clearTimeout(timeoutId);
4904
- timeoutId = window.setTimeout(() => {
4905
- if (isScrolling === false) {
4906
- return;
4907
- }
4908
- isScrolling = false;
4909
- sendMessage(OUTGOING_EVENTS.CanvasScroll, SCROLL_STATES.End);
4910
- /**
4911
- * On scroll end, send new co-ordinates of selected node
4912
- */
4913
- if (selectedNodeId) {
4914
- sendSelectedComponentCoordinates(selectedNodeId);
4915
- }
4916
- }, 150);
4917
- };
4918
- window.addEventListener('scroll', onScroll, { capture: true, passive: true });
4919
- return () => {
4920
- window.removeEventListener('scroll', onScroll, { capture: true });
4921
- clearTimeout(timeoutId);
4922
- };
4923
- }, [selectedNodeId, setScrollY]);
4924
4421
  }
4925
4422
 
4926
- const onComponentMoved = (options) => {
4927
- sendMessage(OUTGOING_EVENTS.ComponentMoved, options);
4423
+ const CircularDependencyErrorPlaceholder = ({ wrappingPatternIds, ...props }) => {
4424
+ return (React.createElement("div", { ...props, "data-cf-node-error": "circular-pattern-dependency", style: {
4425
+ border: '1px solid red',
4426
+ background: 'rgba(255, 0, 0, 0.1)',
4427
+ padding: '1rem 1rem 0 1rem',
4428
+ width: '100%',
4429
+ height: '100%',
4430
+ } },
4431
+ "Circular usage of patterns detected:",
4432
+ React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
4433
+ const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
4434
+ const entry = inMemoryEntities.maybeResolveLink(entryLink);
4435
+ const entryTitle = entry?.fields?.title;
4436
+ const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
4437
+ return React.createElement("li", { key: patternId }, text);
4438
+ }))));
4928
4439
  };
4929
4440
 
4930
- const generateId = (type) => `${type}-${v4()}`;
4441
+ class ImportedComponentError extends Error {
4442
+ constructor(message) {
4443
+ super(message);
4444
+ this.name = 'ImportedComponentError';
4445
+ }
4446
+ }
4447
+ class ExperienceSDKError extends Error {
4448
+ constructor(message) {
4449
+ super(message);
4450
+ this.name = 'ExperienceSDKError';
4451
+ }
4452
+ }
4453
+ class ImportedComponentErrorBoundary extends React.Component {
4454
+ componentDidCatch(error, _errorInfo) {
4455
+ if (error.name === 'ImportedComponentError' || error.name === 'ExperienceSDKError') {
4456
+ // This error was already handled by a nested error boundary and should be passed upwards
4457
+ // We have to do this as we wrap every component on every layer with this error boundary and
4458
+ // thus an error deep in the tree bubbles through many layers of error boundaries.
4459
+ throw error;
4460
+ }
4461
+ // Differentiate between custom and SDK-provided components for error tracking
4462
+ const ErrorClass = isContentfulComponent(this.props.componentId)
4463
+ ? ExperienceSDKError
4464
+ : ImportedComponentError;
4465
+ const err = new ErrorClass(error.message);
4466
+ err.stack = error.stack;
4467
+ throw err;
4468
+ }
4469
+ render() {
4470
+ return this.props.children;
4471
+ }
4472
+ }
4931
4473
 
4932
- const createTreeNode = ({ blockId, parentId, slotId }) => {
4933
- const node = {
4934
- type: 'block',
4935
- data: {
4936
- id: generateId(blockId),
4937
- blockId,
4938
- slotId,
4939
- props: {},
4940
- dataSource: {},
4941
- breakpoints: [],
4942
- unboundValues: {},
4943
- },
4944
- parentId,
4945
- children: [],
4946
- };
4947
- return node;
4474
+ const MissingComponentPlaceholder = ({ blockId }) => {
4475
+ return (React.createElement("div", { style: {
4476
+ border: '1px solid red',
4477
+ width: '100%',
4478
+ height: '100%',
4479
+ } },
4480
+ "Missing component '",
4481
+ blockId,
4482
+ "'"));
4948
4483
  };
4949
4484
 
4950
- const onComponentDropped = ({ node, index, parentBlockId, parentType, parentId, }) => {
4951
- sendMessage(OUTGOING_EVENTS.ComponentDropped, {
4952
- node,
4953
- index: index ?? node.children.length,
4954
- parentNode: {
4955
- type: parentType,
4956
- data: {
4957
- blockId: parentBlockId,
4958
- id: parentId,
4959
- },
4960
- },
4961
- });
4962
- };
4485
+ var css_248z$1 = ".EditorBlock-module_emptySlot__za-Bi {\n min-height: 80px;\n min-width: 80px;\n}\n";
4486
+ var styles$1 = {"emptySlot":"EditorBlock-module_emptySlot__za-Bi"};
4487
+ styleInject(css_248z$1);
4963
4488
 
4964
- const onDrop = ({ destinationIndex, componentType, destinationZoneId, data, slotId, }) => {
4965
- const parentId = destinationZoneId;
4966
- const parentNode = getItem({ id: parentId }, data);
4967
- const parentIsRoot = parentId === ROOT_ID;
4968
- const emptyComponentData = {
4969
- type: 'block',
4970
- parentId,
4971
- children: [],
4972
- data: {
4973
- blockId: componentType,
4974
- id: generateId(componentType),
4975
- slotId,
4976
- breakpoints: [],
4977
- dataSource: {},
4978
- props: {},
4979
- unboundValues: {},
4980
- },
4981
- };
4982
- onComponentDropped({
4983
- node: emptyComponentData,
4984
- index: destinationIndex,
4985
- parentType: parentIsRoot ? 'root' : parentNode?.type,
4986
- parentBlockId: parentNode?.data.blockId,
4987
- parentId: parentIsRoot ? 'root' : parentId,
4988
- });
4989
- };
4990
-
4991
- /**
4992
- * Parses a droppable zone ID into a node ID and slot ID.
4993
- *
4994
- * The slot ID is optional and only present if the component implements multiple drop zones.
4995
- *
4996
- * @param zoneId - Expected formats are `nodeId` or `nodeId|slotId`.
4997
- */
4998
- const parseZoneId = (zoneId) => {
4999
- const [nodeId, slotId] = zoneId.includes('|') ? zoneId.split('|') : [zoneId, undefined];
5000
- return { nodeId, slotId };
5001
- };
5002
-
5003
- function useCanvasInteractions() {
5004
- const tree = useTreeStore((state) => state.tree);
5005
- const reorderChildren = useTreeStore((state) => state.reorderChildren);
5006
- const reparentChild = useTreeStore((state) => state.reparentChild);
5007
- const addChild = useTreeStore((state) => state.addChild);
5008
- const onAddComponent = (droppedItem) => {
5009
- const { destination, draggableId } = droppedItem;
5010
- if (!destination) {
5011
- return;
5012
- }
5013
- /**
5014
- * We only have the draggableId as information about the new component being dropped.
5015
- * So we need to split it to get the blockId and the isAssembly flag.
5016
- */
5017
- const [blockId, isAssembly] = draggableId.split(':');
5018
- const { nodeId: parentId, slotId } = parseZoneId(destination.droppableId);
5019
- const droppingOnRoot = parentId === ROOT_ID;
5020
- const isValidRootComponent = blockId === CONTENTFUL_COMPONENTS$1.container.id;
5021
- let node = createTreeNode({ blockId, parentId, slotId });
5022
- if (droppingOnRoot && !isValidRootComponent) {
5023
- const wrappingContainer = createTreeNode({
5024
- blockId: CONTENTFUL_COMPONENTS$1.container.id,
5025
- parentId,
5026
- });
5027
- const childNode = createTreeNode({
5028
- blockId,
5029
- parentId: wrappingContainer.data.id,
4489
+ const useComponentRegistration = (node) => {
4490
+ return useMemo(() => {
4491
+ let registration = componentRegistry.get(node.data.blockId);
4492
+ if (node.type === ASSEMBLY_NODE_TYPE && !registration) {
4493
+ registration = createAssemblyRegistration({
4494
+ definitionId: node.data.blockId,
4495
+ component: Assembly,
5030
4496
  });
5031
- node = wrappingContainer;
5032
- node.children = [childNode];
5033
- }
5034
- /**
5035
- * isAssembly comes from a string ID so we need to check if it's 'true' or 'false'
5036
- * in string format.
5037
- */
5038
- if (isAssembly === 'false') {
5039
- addChild(destination.index, parentId, node);
5040
- }
5041
- onDrop({
5042
- data: tree,
5043
- componentType: blockId,
5044
- destinationIndex: destination.index,
5045
- destinationZoneId: parentId,
5046
- slotId,
5047
- });
5048
- };
5049
- const onMoveComponent = (droppedItem) => {
5050
- const { destination, source, draggableId } = droppedItem;
5051
- if (!destination || !source) {
5052
- return;
5053
4497
  }
5054
- if (destination.droppableId === source.droppableId) {
5055
- reorderChildren(destination.index, destination.droppableId, source.index);
5056
- }
5057
- if (destination.droppableId !== source.droppableId) {
5058
- reparentChild(destination.index, destination.droppableId, source.index, source.droppableId);
5059
- }
5060
- onComponentMoved({
5061
- nodeId: draggableId,
5062
- destinationIndex: destination.index,
5063
- destinationParentId: destination.droppableId,
5064
- sourceIndex: source.index,
5065
- sourceParentId: source.droppableId,
5066
- });
5067
- };
5068
- return { onAddComponent, onMoveComponent };
5069
- }
5070
-
5071
- const TestDNDContainer = ({ onDragEnd, onBeforeDragStart, onDragStart, onDragUpdate, children, }) => {
5072
- const handleDragStart = (event) => {
5073
- const draggedItem = event.nativeEvent;
5074
- const start = {
5075
- mode: draggedItem.mode,
5076
- draggableId: draggedItem.draggableId,
5077
- type: draggedItem.type,
5078
- source: draggedItem.source,
5079
- };
5080
- onBeforeDragStart(start);
5081
- onDragStart(start, {});
5082
- };
5083
- const handleDrag = (event) => {
5084
- const draggedItem = event.nativeEvent;
5085
- const update = {
5086
- mode: draggedItem.mode,
5087
- draggableId: draggedItem.draggableId,
5088
- type: draggedItem.type,
5089
- source: draggedItem.source,
5090
- destination: draggedItem.destination,
5091
- combine: draggedItem.combine,
5092
- };
5093
- onDragUpdate(update, {});
5094
- };
5095
- const handleDragEnd = (event) => {
5096
- const draggedItem = event.nativeEvent;
5097
- const result = {
5098
- mode: draggedItem.mode,
5099
- draggableId: draggedItem.draggableId,
5100
- type: draggedItem.type,
5101
- source: draggedItem.source,
5102
- destination: draggedItem.destination,
5103
- combine: draggedItem.combine,
5104
- reason: draggedItem.reason,
5105
- };
5106
- onDragEnd(result, {});
5107
- };
5108
- return (React.createElement("div", { "data-test-id": "dnd-context-substitute", onDragStart: handleDragStart, onDrag: handleDrag, onDragEnd: handleDragEnd }, children));
5109
- };
5110
-
5111
- const DNDProvider = ({ children }) => {
5112
- const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
5113
- const draggedItem = useDraggedItemStore((state) => state.draggedItem);
5114
- const setOnBeforeCaptureId = useDraggedItemStore((state) => state.setOnBeforeCaptureId);
5115
- const setDraggingOnCanvas = useDraggedItemStore((state) => state.setDraggingOnCanvas);
5116
- const updateItem = useDraggedItemStore((state) => state.updateItem);
5117
- const { onAddComponent, onMoveComponent } = useCanvasInteractions();
5118
- const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
5119
- const prevSelectedNodeId = useRef(null);
5120
- const isTestRun = typeof window !== 'undefined' && Object.prototype.hasOwnProperty.call(window, 'Cypress');
5121
- const beforeDragStart = ({ source }) => {
5122
- prevSelectedNodeId.current = selectedNodeId;
5123
- // Unselect the current node when dragging and remove the outline
5124
- setSelectedNodeId('');
5125
- // Set dragging state here to make sure that DnD capture phase has completed
5126
- setDraggingOnCanvas(true);
5127
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
5128
- nodeId: '',
5129
- });
5130
- if (source.droppableId !== COMPONENT_LIST_ID) {
5131
- sendMessage(OUTGOING_EVENTS.ComponentMoveStarted, undefined);
5132
- }
5133
- };
5134
- const beforeCapture = ({ draggableId }) => {
5135
- setOnBeforeCaptureId(draggableId);
5136
- };
5137
- const dragStart = (start) => {
5138
- updateItem(start);
5139
- };
5140
- const dragUpdate = (update) => {
5141
- updateItem(update);
5142
- };
5143
- const dragEnd = (dropResult) => {
5144
- setDraggingOnCanvas(false);
5145
- setOnBeforeCaptureId('');
5146
- updateItem();
5147
- SimulateDnD$1.reset();
5148
- // If the component is being dropped onto itself, do nothing
5149
- // This can happen from an apparent race condition where the hovering zone gets set
5150
- // to the component after its dropped.
5151
- if (dropResult.destination?.droppableId === dropResult.draggableId) {
5152
- return;
5153
- }
5154
- if (!dropResult.destination) {
5155
- if (!draggedItem?.destination) {
5156
- // User cancel drag
5157
- sendMessage(OUTGOING_EVENTS.ComponentDragCanceled, undefined);
5158
- //select the previously selected node if drag was canceled
5159
- if (prevSelectedNodeId.current) {
5160
- setSelectedNodeId(prevSelectedNodeId.current);
5161
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
5162
- nodeId: prevSelectedNodeId.current,
5163
- });
5164
- prevSelectedNodeId.current = null;
5165
- }
5166
- return;
5167
- }
5168
- // Use the destination from the draggedItem (when clicking the canvas)
5169
- dropResult.destination = draggedItem.destination;
5170
- }
5171
- // New component added to canvas
5172
- if (dropResult.source.droppableId.startsWith('component-list')) {
5173
- onAddComponent(dropResult);
5174
- }
5175
- else {
5176
- onMoveComponent(dropResult);
5177
- }
5178
- // If a node was previously selected prior to dragging, re-select it
5179
- setSelectedNodeId(dropResult.draggableId);
5180
- sendMessage(OUTGOING_EVENTS.ComponentMoveEnded, undefined);
5181
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
5182
- nodeId: dropResult.draggableId,
5183
- });
5184
- };
5185
- return (React.createElement(DragDropContext, { onBeforeCapture: beforeCapture, onDragUpdate: dragUpdate, onBeforeDragStart: beforeDragStart, onDragStart: dragStart, onDragEnd: dragEnd }, isTestRun ? (React.createElement(TestDNDContainer, { onDragEnd: dragEnd, onBeforeDragStart: beforeDragStart, onDragStart: dragStart, onDragUpdate: dragUpdate }, children)) : (children)));
5186
- };
5187
-
5188
- /**
5189
- * This hook gets the element co-ordinates of a specified element in the DOM
5190
- * and sends the DOM Rect to the client app
5191
- */
5192
- const useSelectedInstanceCoordinates = ({ node }) => {
5193
- const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
5194
- useEffect(() => {
5195
- if (selectedNodeId !== node.data.id) {
5196
- return;
4498
+ if (!registration) {
4499
+ console.warn(`Component registration not found for component with id: "${node.data.blockId}". The registered component might have been removed from the code. To proceed, remove the component manually from the layers tab.`);
4500
+ return undefined;
5197
4501
  }
5198
- // Allows the drop animation to finish before
5199
- // calculating the components coordinates
5200
- setTimeout(() => {
5201
- sendSelectedComponentCoordinates(node.data.id);
5202
- }, 10);
5203
- }, [node, selectedNodeId]);
5204
- const selectedElement = node.data.id
5205
- ? document.querySelector(`[data-cf-node-id="${selectedNodeId}"]`)
5206
- : undefined;
5207
- return selectedElement ? getElementCoordinates(selectedElement) : null;
4502
+ return registration;
4503
+ }, [node]);
5208
4504
  };
5209
4505
 
5210
4506
  /**
@@ -5284,15 +4580,12 @@ const maybeMergePatternDefaultDesignValues = ({ variableName, variableMapping, n
5284
4580
  return variableMapping.valuesByBreakpoint;
5285
4581
  };
5286
4582
 
5287
- const useComponentProps = ({ node, entityStore, areEntitiesFetched, resolveDesignValue, renderDropzone, definition, options, userIsDragging, requiresDragWrapper, }) => {
4583
+ const useComponentProps = ({ node, entityStore, areEntitiesFetched, resolveDesignValue, definition, options, }) => {
5288
4584
  const unboundValues = useEditorStore((state) => state.unboundValues);
5289
4585
  const hyperlinkPattern = useEditorStore((state) => state.hyperLinkPattern);
5290
4586
  const locale = useEditorStore((state) => state.locale);
5291
4587
  const dataSource = useEditorStore((state) => state.dataSource);
5292
- const draggingId = useDraggedItemStore((state) => state.onBeforeCaptureId);
5293
- const nodeRect = useDraggedItemStore((state) => state.domRect);
5294
4588
  const findNodeById = useTreeStore((state) => state.findNodeById);
5295
- const isEmptyZone = !node.children.length;
5296
4589
  const props = useMemo(() => {
5297
4590
  const propsBase = {
5298
4591
  cfSsrClassName: node.data.props.cfSsrClassName
@@ -5388,18 +4681,9 @@ const useComponentProps = ({ node, entityStore, areEntitiesFetched, resolveDesig
5388
4681
  return { ...acc };
5389
4682
  }
5390
4683
  }, {});
5391
- const slotProps = {};
5392
- if (definition.slots) {
5393
- for (const slotId in definition.slots) {
5394
- slotProps[slotId] = renderDropzone(node, {
5395
- zoneId: [node.data.id, slotId].join('|'),
5396
- });
5397
- }
5398
- }
5399
4684
  return {
5400
4685
  ...propsBase,
5401
4686
  ...extractedProps,
5402
- ...slotProps,
5403
4687
  };
5404
4688
  }, [
5405
4689
  hyperlinkPattern,
@@ -5411,1120 +4695,231 @@ const useComponentProps = ({ node, entityStore, areEntitiesFetched, resolveDesig
5411
4695
  areEntitiesFetched,
5412
4696
  unboundValues,
5413
4697
  entityStore,
5414
- renderDropzone,
5415
4698
  findNodeById,
5416
4699
  ]);
5417
4700
  const cfStyles = useMemo(() => ({
5418
4701
  ...buildCfStyles(props),
5419
- // This is not handled by buildCfStyles as it requires separate disjunct media queries in preview mode
4702
+ // The visibility needs to be transformed separately as it requires
4703
+ // a special handling for preview & SSR rendering (not here though).
5420
4704
  ...transformVisibility(props.cfVisibility),
5421
4705
  }), [props]);
5422
- const cfVisibility = props['cfVisibility'];
5423
- const isAssemblyBlock = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
5424
- const isSingleColumn = node?.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id;
5425
- const isStructureComponent = isContentfulStructureComponent(node?.data.blockId);
5426
- const isPatternNode = node.type === ASSEMBLY_NODE_TYPE;
5427
- const { overrideStyles, wrapperStyles } = useMemo(() => {
5428
- // Move size styles to the wrapping div and override the component styles
5429
- const overrideStyles = {};
5430
- const wrapperStyles = { width: options?.wrapContainerWidth };
5431
- if (requiresDragWrapper) {
5432
- // when element is marked by user as not-visible, on that element the node `display: none !important`
5433
- // will be set and it will disappear. However, when such a node has a wrapper div, the wrapper
5434
- // should not have any css properties (at least not ones which force size), as such div should
5435
- // simply be a zero height wrapper around element with `display: none !important`.
5436
- // Hence we guard all wrapperStyles with `cfVisibility` check.
5437
- if (cfVisibility && cfStyles.width)
5438
- wrapperStyles.width = cfStyles.width;
5439
- if (cfVisibility && cfStyles.height)
5440
- wrapperStyles.height = cfStyles.height;
5441
- if (cfVisibility && cfStyles.maxWidth)
5442
- wrapperStyles.maxWidth = cfStyles.maxWidth;
5443
- if (cfVisibility && cfStyles.margin)
5444
- wrapperStyles.margin = cfStyles.margin;
5445
- }
5446
- // Override component styles to fill the wrapper
5447
- if (wrapperStyles.width)
5448
- overrideStyles.width = '100%';
5449
- if (wrapperStyles.height)
5450
- overrideStyles.height = '100%';
5451
- if (wrapperStyles.margin)
5452
- overrideStyles.margin = '0';
5453
- if (wrapperStyles.maxWidth)
5454
- overrideStyles.maxWidth = 'none';
5455
- // Prevent the dragging element from changing sizes when it has a percentage width or height
5456
- if (draggingId === node.data.id && nodeRect) {
5457
- if (requiresDragWrapper) {
5458
- if (isPercentValue(cfStyles.width))
5459
- wrapperStyles.maxWidth = nodeRect.width;
5460
- if (isPercentValue(cfStyles.height))
5461
- wrapperStyles.maxHeight = nodeRect.height;
5462
- }
5463
- else {
5464
- if (isPercentValue(cfStyles.width))
5465
- overrideStyles.maxWidth = nodeRect.width;
5466
- if (isPercentValue(cfStyles.height))
5467
- overrideStyles.maxHeight = nodeRect.height;
5468
- }
5469
- }
5470
- return { overrideStyles, wrapperStyles };
5471
- }, [
5472
- cfStyles,
5473
- options?.wrapContainerWidth,
5474
- requiresDragWrapper,
5475
- node.data.id,
5476
- draggingId,
5477
- nodeRect,
5478
- cfVisibility,
5479
- ]);
4706
+ const shouldRenderEmptySpaceWithMinSize = useMemo(() => {
4707
+ if (node.children.length)
4708
+ return false;
4709
+ // Render with minimum height and with in those two scenarios:
4710
+ if (isStructureWithRelativeHeight(node.data.blockId, cfStyles.height))
4711
+ return true;
4712
+ if (definition?.children)
4713
+ return true;
4714
+ return false;
4715
+ }, [cfStyles.height, definition?.children, node.children.length, node.data.blockId]);
5480
4716
  // Styles that will be applied to the component element
5481
- // This has to be memoized to avoid recreating the styles in useEditorModeClassName on every render
5482
4717
  const componentStyles = useMemo(() => ({
5483
4718
  ...cfStyles,
5484
- ...overrideStyles,
5485
- ...(isEmptyZone &&
5486
- isStructureWithRelativeHeight(node?.data.blockId, cfStyles.height) && {
5487
- minHeight: EMPTY_CONTAINER_HEIGHT,
5488
- }),
5489
- ...(userIsDragging &&
5490
- isStructureComponent &&
5491
- !isSingleColumn &&
5492
- !isAssemblyBlock && {
5493
- padding: addExtraDropzonePadding(cfStyles.padding?.toString() || '0 0 0 0'),
4719
+ ...(shouldRenderEmptySpaceWithMinSize && {
4720
+ minHeight: EMPTY_CONTAINER_SIZE,
4721
+ minWidth: EMPTY_CONTAINER_SIZE,
5494
4722
  }),
5495
- }), [
5496
- cfStyles,
5497
- isAssemblyBlock,
5498
- isEmptyZone,
5499
- isSingleColumn,
5500
- isStructureComponent,
5501
- node?.data.blockId,
5502
- overrideStyles,
5503
- userIsDragging,
5504
- ]);
5505
- const componentClass = useEditorModeClassName({
4723
+ }), [cfStyles, shouldRenderEmptySpaceWithMinSize]);
4724
+ const cfCsrClassName = useEditorModeClassName({
5506
4725
  styles: componentStyles,
5507
4726
  nodeId: node.data.id,
5508
4727
  });
5509
- const sharedProps = {
5510
- 'data-cf-node-id': node.data.id,
5511
- 'data-cf-node-block-id': node.data.blockId,
5512
- 'data-cf-node-block-type': node.type,
5513
- className: props.cfSsrClassName ?? componentClass,
5514
- ...(definition?.children ? { children: renderDropzone(node) } : {}),
5515
- };
5516
- const customComponentProps = {
5517
- ...sharedProps,
5518
- // Allows custom components to render differently in the editor. This needs to be activated
5519
- // through options as the component has to be aware of this prop to not cause any React warnings.
5520
- ...(options?.enableCustomEditorView ? { isInExpEditorMode: true } : {}),
5521
- ...sanitizeNodeProps(props),
5522
- };
5523
- const structuralOrPatternComponentProps = {
5524
- ...sharedProps,
5525
- editorMode: true,
5526
- node,
5527
- renderDropzone,
5528
- };
5529
- return {
5530
- componentProps: isStructureComponent || isPatternNode
5531
- ? structuralOrPatternComponentProps
5532
- : customComponentProps,
5533
- componentStyles,
5534
- wrapperStyles,
5535
- };
5536
- };
5537
- const addExtraDropzonePadding = (padding) => padding
5538
- .split(' ')
5539
- .map((value) => parseFloat(value) === 0 ? `${DRAG_PADDING}px` : `calc(${value} + ${DRAG_PADDING}px)`)
5540
- .join(' ');
5541
- const isPercentValue = (value) => typeof value === 'string' && value.endsWith('%');
5542
-
5543
- class ImportedComponentError extends Error {
5544
- constructor(message) {
5545
- super(message);
5546
- this.name = 'ImportedComponentError';
5547
- }
5548
- }
5549
- class ExperienceSDKError extends Error {
5550
- constructor(message) {
5551
- super(message);
5552
- this.name = 'ExperienceSDKError';
5553
- }
5554
- }
5555
- class ImportedComponentErrorBoundary extends React.Component {
5556
- componentDidCatch(error, _errorInfo) {
5557
- if (error.name === 'ImportedComponentError' || error.name === 'ExperienceSDKError') {
5558
- // This error was already handled by a nested error boundary and should be passed upwards
5559
- // We have to do this as we wrap every component on every layer with this error boundary and
5560
- // thus an error deep in the tree bubbles through many layers of error boundaries.
5561
- throw error;
5562
- }
5563
- // Differentiate between custom and SDK-provided components for error tracking
5564
- const ErrorClass = isContentfulComponent(this.props.componentId)
5565
- ? ExperienceSDKError
5566
- : ImportedComponentError;
5567
- const err = new ErrorClass(error.message);
5568
- err.stack = error.stack;
5569
- throw err;
5570
- }
5571
- render() {
5572
- return this.props.children;
5573
- }
5574
- }
5575
-
5576
- const MissingComponentPlaceholder = ({ blockId }) => {
5577
- return (React.createElement("div", { style: {
5578
- border: '1px solid red',
5579
- width: '100%',
5580
- height: '100%',
5581
- } },
5582
- "Missing component '",
5583
- blockId,
5584
- "'"));
5585
- };
5586
-
5587
- const CircularDependencyErrorPlaceholder = forwardRef(({ wrappingPatternIds, ...props }, ref) => {
5588
- return (React.createElement("div", { ...props,
5589
- // Pass through ref to avoid DND errors being logged
5590
- ref: ref, "data-cf-node-error": "circular-pattern-dependency", style: {
5591
- border: '1px solid red',
5592
- background: 'rgba(255, 0, 0, 0.1)',
5593
- padding: '1rem 1rem 0 1rem',
5594
- width: '100%',
5595
- height: '100%',
5596
- } },
5597
- "Circular usage of patterns detected:",
5598
- React.createElement("ul", null, Array.from(wrappingPatternIds).map((patternId) => {
5599
- const entryLink = { sys: { type: 'Link', linkType: 'Entry', id: patternId } };
5600
- const entry = inMemoryEntities.maybeResolveLink(entryLink);
5601
- const entryTitle = entry?.fields?.title;
5602
- const text = entryTitle ? `${entryTitle} (${patternId})` : patternId;
5603
- return React.createElement("li", { key: patternId }, text);
5604
- }))));
5605
- });
5606
- CircularDependencyErrorPlaceholder.displayName = 'CircularDependencyErrorPlaceholder';
5607
-
5608
- const useComponent = ({ node, entityStore, areEntitiesFetched, resolveDesignValue, renderDropzone, userIsDragging, wrappingPatternIds, }) => {
5609
- const tree = useTreeStore((state) => state.tree);
5610
- const componentRegistration = useMemo(() => {
5611
- let registration = componentRegistry.get(node.data.blockId);
5612
- if (node.type === ASSEMBLY_NODE_TYPE && !registration) {
5613
- registration = createAssemblyRegistration({
5614
- definitionId: node.data.blockId,
5615
- component: Assembly,
5616
- });
5617
- }
5618
- if (!registration) {
5619
- console.warn(`Component registration not found for component with id: "${node.data.blockId}". The registered component might have been removed from the code. To proceed, remove the component manually from the layers tab.`);
5620
- return undefined;
5621
- }
5622
- return registration;
5623
- }, [node]);
5624
- const componentId = node.data.id;
5625
- const isPatternNode = node.type === ASSEMBLY_NODE_TYPE;
5626
- const isPatternComponent = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
5627
- const parentComponentNode = getItem({ id: node.parentId }, tree);
5628
- const isNestedPattern = isPatternNode &&
5629
- [ASSEMBLY_BLOCK_NODE_TYPE, ASSEMBLY_NODE_TYPE].includes(parentComponentNode?.type ?? '');
5630
- const isStructureComponent = isContentfulStructureComponent(node.data.blockId);
5631
- const requiresDragWrapper = !isPatternNode && !isStructureComponent && !componentRegistration?.options?.wrapComponent;
5632
- const { componentProps, wrapperStyles } = useComponentProps({
5633
- node,
5634
- entityStore,
5635
- areEntitiesFetched,
5636
- resolveDesignValue,
5637
- renderDropzone,
5638
- definition: componentRegistration?.definition,
5639
- options: componentRegistration?.options,
5640
- userIsDragging,
5641
- requiresDragWrapper,
5642
- });
5643
- const elementToRender = (props) => {
5644
- const { dragProps = {} } = props || {};
5645
- const { children, innerRef, Tag = 'div', ToolTipAndPlaceholder, style, ...rest } = dragProps;
5646
- const { 'data-cf-node-block-id': dataCfNodeBlockId, 'data-cf-node-block-type': dataCfNodeBlockType, 'data-cf-node-id': dataCfNodeId, } = componentProps;
5647
- const refCallback = (refNode) => {
5648
- if (innerRef && refNode)
5649
- innerRef(refNode);
5650
- };
5651
- if (!componentRegistration) {
5652
- return React.createElement(MissingComponentPlaceholder, { blockId: node.data.blockId });
5653
- }
5654
- if (node.data.blockId && wrappingPatternIds.has(node.data.blockId)) {
5655
- return (React.createElement(CircularDependencyErrorPlaceholder, { ref: refCallback, "data-cf-node-id": dataCfNodeId, "data-cf-node-block-id": dataCfNodeBlockId, "data-cf-node-block-type": dataCfNodeBlockType, wrappingPatternIds: wrappingPatternIds }));
5656
- }
5657
- const element = React.createElement(ImportedComponentErrorBoundary, { componentId: node.data.blockId }, React.createElement(componentRegistration.component, {
5658
- ...componentProps,
5659
- dragProps,
5660
- }));
5661
- if (!requiresDragWrapper) {
5662
- return element;
5663
- }
5664
- return (React.createElement(Tag, { ...rest, style: { ...style, ...wrapperStyles }, ref: refCallback, "data-cf-node-id": dataCfNodeId, "data-cf-node-block-id": dataCfNodeBlockId, "data-cf-node-block-type": dataCfNodeBlockType },
5665
- ToolTipAndPlaceholder,
5666
- element));
5667
- };
5668
- return {
5669
- node,
5670
- parentComponentNode,
5671
- isAssembly: isPatternNode,
5672
- isPatternNode,
5673
- isPatternComponent,
5674
- isNestedPattern,
5675
- componentId,
5676
- elementToRender,
5677
- definition: componentRegistration?.definition,
5678
- };
5679
- };
5680
-
5681
- const calcOffsetLeft = (parentElement, placeholderWidth, nodeWidth) => {
5682
- if (!parentElement) {
5683
- return 0;
5684
- }
5685
- const alignItems = window.getComputedStyle(parentElement).alignItems;
5686
- if (alignItems === 'center') {
5687
- return -(placeholderWidth - nodeWidth) / 2;
5688
- }
5689
- if (alignItems === 'end') {
5690
- return -placeholderWidth + nodeWidth + 2;
5691
- }
5692
- return 0;
5693
- };
5694
- const calcOffsetTop = (parentElement, placeholderHeight, nodeHeight) => {
5695
- if (!parentElement) {
5696
- return 0;
5697
- }
5698
- const alignItems = window.getComputedStyle(parentElement).alignItems;
5699
- if (alignItems === 'center') {
5700
- return -(placeholderHeight - nodeHeight) / 2;
5701
- }
5702
- if (alignItems === 'end') {
5703
- return -placeholderHeight + nodeHeight + 2;
5704
- }
5705
- return 0;
5706
- };
5707
- const getPaddingOffset = (element) => {
5708
- const paddingLeft = parseFloat(window.getComputedStyle(element).paddingLeft);
5709
- const paddingRight = parseFloat(window.getComputedStyle(element).paddingRight);
5710
- const paddingTop = parseFloat(window.getComputedStyle(element).paddingTop);
5711
- const paddingBottom = parseFloat(window.getComputedStyle(element).paddingBottom);
5712
- const horizontalOffset = paddingLeft + paddingRight;
5713
- const verticalOffset = paddingTop + paddingBottom;
5714
- return [horizontalOffset, verticalOffset];
5715
- };
5716
- /**
5717
- * Calculate the size and position of the dropzone indicator
5718
- * when dragging a new component onto the canvas
5719
- */
5720
- const calcNewComponentStyles = (params) => {
5721
- const { destinationIndex, elementIndex, dropzoneElementId, id, direction, totalIndexes } = params;
5722
- const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
5723
- const isHorizontal = direction === 'horizontal';
5724
- const isRightAlign = isHorizontal && isEnd;
5725
- const isBottomAlign = !isHorizontal && isEnd;
5726
- const dropzone = document.querySelector(`[data-rfd-droppable-id="${dropzoneElementId}"]`);
5727
- const element = document.querySelector(`[data-ctfl-draggable-id="${id}"]`);
5728
- if (!dropzone || !element) {
5729
- return emptyStyles;
5730
- }
5731
- const elementSizes = element.getBoundingClientRect();
5732
- const dropzoneSizes = dropzone.getBoundingClientRect();
5733
- const [horizontalPadding, verticalPadding] = getPaddingOffset(dropzone);
5734
- const width = isHorizontal ? DRAGGABLE_WIDTH : dropzoneSizes.width - horizontalPadding;
5735
- const height = isHorizontal ? dropzoneSizes.height - verticalPadding : DRAGGABLE_HEIGHT;
5736
- const top = isHorizontal
5737
- ? calcOffsetTop(element.parentElement, height, elementSizes.height)
5738
- : -height;
5739
- const left = isHorizontal
5740
- ? -width
5741
- : calcOffsetLeft(element.parentElement, width, elementSizes.width);
5742
- return {
5743
- width,
5744
- height,
5745
- top: !isBottomAlign ? top : 'unset',
5746
- right: isRightAlign ? -width : 'unset',
5747
- bottom: isBottomAlign ? -height : 'unset',
5748
- left: !isRightAlign ? left : 'unset',
5749
- };
5750
- };
5751
- /**
5752
- * Calculate the size and position of the dropzone indicator
5753
- * when moving an existing component on the canvas
5754
- */
5755
- const calcMovementStyles = (params) => {
5756
- const { destinationIndex, sourceIndex, destinationId, sourceId, elementIndex, dropzoneElementId, id, direction, totalIndexes, draggableId, } = params;
5757
- const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
5758
- const isHorizontal = direction === 'horizontal';
5759
- const isSameZone = destinationId === sourceId;
5760
- const isBelowSourceIndex = destinationIndex > sourceIndex;
5761
- const isRightAlign = isHorizontal && (isEnd || (isSameZone && isBelowSourceIndex));
5762
- const isBottomAlign = !isHorizontal && (isEnd || (isSameZone && isBelowSourceIndex));
5763
- const dropzone = document.querySelector(`[data-rfd-droppable-id="${dropzoneElementId}"]`);
5764
- const draggable = document.querySelector(`[data-rfd-draggable-id="${draggableId}"]`);
5765
- const element = document.querySelector(`[data-ctfl-draggable-id="${id}"]`);
5766
- if (!dropzone || !element || !draggable) {
5767
- return emptyStyles;
5768
- }
5769
- const elementSizes = element.getBoundingClientRect();
5770
- const dropzoneSizes = dropzone.getBoundingClientRect();
5771
- const draggableSizes = draggable.getBoundingClientRect();
5772
- const [horizontalPadding, verticalPadding] = getPaddingOffset(dropzone);
5773
- const width = isHorizontal ? draggableSizes.width : dropzoneSizes.width - horizontalPadding;
5774
- const height = isHorizontal ? dropzoneSizes.height - verticalPadding : draggableSizes.height;
5775
- const top = isHorizontal
5776
- ? calcOffsetTop(element.parentElement, height, elementSizes.height)
5777
- : -height;
5778
- const left = isHorizontal
5779
- ? -width
5780
- : calcOffsetLeft(element.parentElement, width, elementSizes.width);
5781
- return {
5782
- width,
5783
- height,
5784
- top: !isBottomAlign ? top : 'unset',
5785
- right: isRightAlign ? -width : 'unset',
5786
- bottom: isBottomAlign ? -height : 'unset',
5787
- left: !isRightAlign ? left : 'unset',
5788
- };
5789
- };
5790
- const emptyStyles = { width: 0, height: 0 };
5791
- const calcPlaceholderStyles = (params) => {
5792
- const { isDraggingOver, sourceId } = params;
5793
- if (!isDraggingOver) {
5794
- return emptyStyles;
5795
- }
5796
- if (sourceId === COMPONENT_LIST_ID) {
5797
- return calcNewComponentStyles(params);
5798
- }
5799
- return calcMovementStyles(params);
5800
- };
5801
- const Placeholder = (props) => {
5802
- const sourceIndex = useDraggedItemStore((state) => state.draggedItem?.source.index) ?? -1;
5803
- const draggableId = useDraggedItemStore((state) => state.draggedItem?.draggableId) ?? '';
5804
- const sourceId = useDraggedItemStore((state) => state.draggedItem?.source.droppableId) ?? '';
5805
- const destinationIndex = useDraggedItemStore((state) => state.draggedItem?.destination?.index) ?? -1;
5806
- const destinationId = useDraggedItemStore((state) => state.draggedItem?.destination?.droppableId) ?? '';
5807
- const { elementIndex, totalIndexes, isDraggingOver } = props;
5808
- const isActive = destinationIndex === elementIndex;
5809
- const isEnd = destinationIndex === totalIndexes && elementIndex === totalIndexes - 1;
5810
- const isVisible = isEnd || isActive;
5811
- const isComponentList = destinationId === COMPONENT_LIST_ID;
5812
- return (!isComponentList &&
5813
- isDraggingOver &&
5814
- isVisible && (React.createElement("div", { style: {
5815
- ...calcPlaceholderStyles({
5816
- ...props,
5817
- sourceId,
5818
- sourceIndex,
5819
- destinationId,
5820
- destinationIndex,
5821
- draggableId,
5822
- }),
5823
- backgroundColor: 'rgba(var(--exp-builder-blue300-rgb), 0.5)',
5824
- position: 'absolute',
5825
- pointerEvents: 'none',
5826
- } })));
5827
- };
5828
-
5829
- var css_248z$2 = ".styles-module_hitbox__i3wKV {\n position: fixed;\n pointer-events: all;\n}\n";
5830
- var styles$2 = {"hitbox":"styles-module_hitbox__i3wKV"};
5831
- styleInject(css_248z$2);
5832
-
5833
- const useZoneStore = create()((set) => ({
5834
- zones: {},
5835
- hoveringZone: '',
5836
- setHoveringZone(zoneId) {
5837
- set({
5838
- hoveringZone: zoneId,
5839
- });
5840
- },
5841
- upsertZone(id, data) {
5842
- set(produce((state) => {
5843
- state.zones[id] = { ...(state.zones[id] || {}), ...data };
5844
- }));
5845
- },
5846
- }));
5847
-
5848
- const { WIDTH, HEIGHT, INITIAL_OFFSET, OFFSET_INCREMENT, MIN_HEIGHT, MIN_DEPTH_HEIGHT, DEEP_ZONE } = HITBOX;
5849
- const calcOffsetDepth = (depth) => {
5850
- return INITIAL_OFFSET - OFFSET_INCREMENT * depth;
5851
- };
5852
- const getHitboxStyles = ({ direction, zoneDepth, domRect, scrollY, offsetRect, }) => {
5853
- if (!domRect) {
5854
- return {
5855
- display: 'none',
4728
+ const componentProps = useMemo(() => {
4729
+ const sharedProps = {
4730
+ 'data-cf-node-id': node.data.id,
4731
+ 'data-cf-node-block-id': node.data.blockId,
4732
+ 'data-cf-node-block-type': node.type,
4733
+ className: props.cfSsrClassName ?? cfCsrClassName,
5856
4734
  };
5857
- }
5858
- const { width, height, top, left, bottom, right } = domRect;
5859
- const { height: offsetHeight, width: offsetWidth } = offsetRect || { height: 0, width: 0 };
5860
- const MAX_SELF_HEIGHT = DRAGGABLE_HEIGHT * 2;
5861
- const isDeepZone = zoneDepth > DEEP_ZONE;
5862
- const isAboveMaxHeight = height > MAX_SELF_HEIGHT;
5863
- switch (direction) {
5864
- case HitboxDirection.TOP:
5865
- return {
5866
- width,
5867
- height: HEIGHT,
5868
- top: top + offsetHeight - calcOffsetDepth(zoneDepth) - scrollY,
5869
- left,
5870
- zIndex: 100 + zoneDepth,
5871
- };
5872
- case HitboxDirection.BOTTOM:
5873
- return {
5874
- width,
5875
- height: HEIGHT,
5876
- top: bottom + offsetHeight + calcOffsetDepth(zoneDepth) - scrollY,
5877
- left,
5878
- zIndex: 100 + zoneDepth,
5879
- };
5880
- case HitboxDirection.LEFT:
5881
- return {
5882
- width: WIDTH,
5883
- height: height - HEIGHT,
5884
- left: left + offsetWidth - calcOffsetDepth(zoneDepth) - WIDTH / 2,
5885
- top: top + HEIGHT / 2 - scrollY,
5886
- zIndex: 100 + zoneDepth,
5887
- };
5888
- case HitboxDirection.RIGHT:
5889
- return {
5890
- width: WIDTH,
5891
- height: height - HEIGHT,
5892
- left: right + offsetWidth + calcOffsetDepth(zoneDepth) - WIDTH / 2,
5893
- top: top + HEIGHT / 2 - scrollY,
5894
- zIndex: 100 + zoneDepth,
5895
- };
5896
- case HitboxDirection.SELF_VERTICAL: {
5897
- if (isAboveMaxHeight && !isDeepZone) {
5898
- return { display: 'none' };
5899
- }
5900
- const selfHeight = isDeepZone ? MIN_DEPTH_HEIGHT : MIN_HEIGHT;
5901
- return {
5902
- width,
5903
- height: selfHeight,
5904
- left,
5905
- top: top + height / 2 - selfHeight / 2 - scrollY,
5906
- zIndex: 1000 + zoneDepth,
5907
- };
5908
- }
5909
- case HitboxDirection.SELF_HORIZONTAL: {
5910
- if (width > DRAGGABLE_WIDTH) {
5911
- return { display: 'none' };
5912
- }
4735
+ // Only pass `editorMode` and `node` to structure components and assembly root nodes.
4736
+ const isStructureComponent = isContentfulStructureComponent(node.data.blockId);
4737
+ if (isStructureComponent) {
5913
4738
  return {
5914
- width: width - DRAGGABLE_WIDTH * 2,
5915
- height,
5916
- left: left + DRAGGABLE_WIDTH,
5917
- top: top - scrollY,
5918
- zIndex: 1000 + zoneDepth,
4739
+ ...sharedProps,
4740
+ editorMode: true,
4741
+ node,
5919
4742
  };
5920
4743
  }
5921
- default:
5922
- return {};
5923
- }
5924
- };
5925
-
5926
- const Hitboxes = ({ zoneId, parentZoneId, isEmptyZone }) => {
5927
- const tree = useTreeStore((state) => state.tree);
5928
- const isDraggingOnCanvas = useDraggedItemStore((state) => state.isDraggingOnCanvas);
5929
- const scrollY = useDraggedItemStore((state) => state.scrollY);
5930
- const zoneDepth = useMemo(() => getItemDepthFromNode({ id: parentZoneId }, tree.root), [tree, parentZoneId]);
5931
- const zones = useZoneStore((state) => state.zones);
5932
- const hoveringZone = useZoneStore((state) => state.hoveringZone);
5933
- const isHoveringZone = hoveringZone === zoneId;
5934
- const hitboxContainer = useMemo(() => {
5935
- return document.querySelector('[data-ctfl-hitboxes]');
5936
- }, []);
5937
- const domRect = useMemo(() => {
5938
- if (!isDraggingOnCanvas)
5939
- return;
5940
- return document.querySelector(`[${CTFL_ZONE_ID}="${zoneId}"]`)?.getBoundingClientRect();
5941
- // eslint-disable-next-line react-hooks/exhaustive-deps
5942
- }, [zoneId, isDraggingOnCanvas]);
5943
- // Use the size of the cloned dragging element to offset the position of the hitboxes
5944
- // So that when dragging causes a dropzone to expand, the hitboxes will be in the correct position
5945
- const offsetRect = useMemo(() => {
5946
- if (!isDraggingOnCanvas || isEmptyZone || !isHoveringZone)
5947
- return;
5948
- return document.querySelector(`[${CTFL_DRAGGING_ELEMENT}]`)?.getBoundingClientRect();
5949
- // eslint-disable-next-line react-hooks/exhaustive-deps
5950
- }, [isEmptyZone, isHoveringZone, isDraggingOnCanvas]);
5951
- const zoneDirection = zones[parentZoneId]?.direction || 'vertical';
5952
- const isVertical = zoneDirection === 'vertical';
5953
- const isRoot = parentZoneId === ROOT_ID;
5954
- const { slotId: parentSlotId } = parseZoneId(parentZoneId);
5955
- const getStyles = useCallback((direction) => getHitboxStyles({
5956
- direction,
5957
- zoneDepth,
5958
- domRect,
5959
- scrollY,
5960
- offsetRect,
5961
- }), [zoneDepth, domRect, scrollY, offsetRect]);
5962
- const renderFinalRootHitbox = () => {
5963
- if (!isRoot)
5964
- return null;
5965
- return (React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(HitboxDirection.BOTTOM) }));
5966
- };
5967
- const renderSurroundingHitboxes = () => {
5968
- if (isRoot || parentSlotId)
5969
- return null;
5970
- return (React.createElement(React.Fragment, null,
5971
- React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.TOP : HitboxDirection.LEFT) }),
5972
- React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.BOTTOM : HitboxDirection.RIGHT) })));
5973
- };
5974
- const ActiveHitboxes = (React.createElement(React.Fragment, null,
5975
- React.createElement("div", { "data-ctfl-zone-id": zoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.SELF_VERTICAL : HitboxDirection.SELF_HORIZONTAL) }),
5976
- renderSurroundingHitboxes(),
5977
- renderFinalRootHitbox()));
5978
- if (!hitboxContainer) {
5979
- return null;
5980
- }
5981
- return createPortal(ActiveHitboxes, hitboxContainer);
5982
- };
5983
-
5984
- const isRelativePreviewSize = (width) => {
5985
- // For now, we solely allow 100% as relative value
5986
- return width === '100%';
5987
- };
5988
- const getTooltipPositions = ({ previewSize, tooltipRect, coordinates, }) => {
5989
- if (!coordinates || !tooltipRect) {
5990
- return { display: 'none' };
5991
- }
5992
- /**
5993
- * By default, the tooltip floats to the left of the element
5994
- */
5995
- const newTooltipStyles = { display: 'flex' };
5996
- // If the preview size is relative, we don't change the floating direction
5997
- if (!isRelativePreviewSize(previewSize)) {
5998
- const previewSizeMatch = previewSize.match(/(\d{1,})px/);
5999
- if (!previewSizeMatch) {
6000
- return { display: 'none' };
6001
- }
6002
- const previewSizePx = parseInt(previewSizeMatch[1]);
6003
- /**
6004
- * If the element is at the right edge of the canvas, and the element isn't wide enough to fit the tooltip width,
6005
- * we float the tooltip to the right of the element.
6006
- */
6007
- if (tooltipRect.width > previewSizePx - coordinates.right &&
6008
- tooltipRect.width > coordinates.width) {
6009
- newTooltipStyles['float'] = 'right';
6010
- }
6011
- }
6012
- const tooltipHeight = tooltipRect.height === 0 ? 32 : tooltipRect.height;
6013
- /**
6014
- * For elements with small heights, we don't want the tooltip covering the content in the element,
6015
- * so we show the tooltip at the top or bottom.
6016
- */
6017
- if (tooltipHeight * 2 > coordinates.height) {
6018
- /**
6019
- * If there's enough space for the tooltip at the top of the element, we show the tooltip at the top of the element,
6020
- * else we show the tooltip at the bottom.
6021
- */
6022
- if (tooltipHeight < coordinates.top) {
6023
- newTooltipStyles['bottom'] = coordinates.height;
6024
- }
6025
- else {
6026
- newTooltipStyles['top'] = coordinates.height;
6027
- }
6028
- }
6029
- /**
6030
- * If the component draws outside of the borders of the canvas to the left we move the tooltip to the right
6031
- * so that it is fully visible.
6032
- */
6033
- if (coordinates.left < 0) {
6034
- newTooltipStyles['left'] = -coordinates.left;
6035
- }
6036
- /**
6037
- * If for any reason, the element's top is negative, we show the tooltip at the bottom
6038
- */
6039
- if (coordinates.top < 0) {
6040
- newTooltipStyles['top'] = coordinates.height;
6041
- }
6042
- return newTooltipStyles;
6043
- };
6044
-
6045
- var css_248z$1 = ".styles-module_DraggableComponent__oyE7Q,\n.styles-module_Dropzone__3R-sm:not(.styles-module_isSlot__HI9yO) {\n position: relative;\n transition: background-color 0.2s;\n pointer-events: all;\n box-sizing: border-box;\n cursor: grab;\n}\n\n.styles-module_DraggableComponent__oyE7Q:before,\n.styles-module_Dropzone__3R-sm:not(.styles-module_isSlot__HI9yO):before {\n content: '';\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n outline-offset: -2px;\n outline: 2px solid transparent;\n z-index: 1;\n transition: outline 0.2s;\n pointer-events: none;\n}\n\n.styles-module_DraggableComponent__oyE7Q.styles-module_isDragging__hldL4.styles-module_Dropzone__3R-sm:before {\n outline-offset: -1px;\n}\n\n.styles-module_DraggableComponent__oyE7Q.styles-module_isDragging__hldL4.styles-module_Dropzone__3R-sm {\n pointer-events: all;\n}\n\n.styles-module_Dropzone__3R-sm.styles-module_fullHeight__afMfT {\n height: 100%;\n}\n\n.styles-module_Dropzone__3R-sm.styles-module_fullWidth__Od117 {\n width: 100%;\n}\n\n.styles-module_isRoot__c-c-x,\n.styles-module_isEmptyCanvas__Mm6Al {\n flex: 1;\n}\n\n.styles-module_isEmptyZone__XZ1Ej {\n min-height: 80px;\n min-width: 80px;\n}\n\n.styles-module_isDragging__hldL4:not(.styles-module_isRoot__c-c-x):not(.styles-module_DraggableClone__CdKIH):before {\n outline: 2px dashed var(--exp-builder-gray300);\n}\n\n.styles-module_Dropzone__3R-sm.styles-module_isDestination__sE70P:not(.styles-module_isRoot__c-c-x):before {\n transition:\n outline 0.2s,\n background-color 0.2s;\n outline: 2px dashed var(--exp-builder-blue400);\n background-color: rgba(var(--exp-builder-blue100-rgb), 0.5);\n z-index: 2;\n}\n\n.styles-module_DraggableClone__CdKIH:before {\n outline: 2px solid var(--exp-builder-blue500);\n}\n\n.styles-module_DropzoneClone__xiT8j,\n.styles-module_DraggableClone__CdKIH,\n.styles-module_DropzoneClone__xiT8j *,\n.styles-module_DraggableClone__CdKIH * {\n pointer-events: none !important;\n}\n\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isDragging__hldL4) :not(.styles-module_DraggableComponent__oyE7Q) {\n pointer-events: none;\n}\n\n.styles-module_isDraggingThisComponent__yCZTp {\n overflow: hidden;\n}\n\n.styles-module_isSelected__c2QEJ:before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_tooltipWrapper__kqvmR {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n display: flex;\n z-index: 10;\n pointer-events: none;\n}\n\n.styles-module_DraggableComponent__oyE7Q.styles-module_isDragging__hldL4 .styles-module_tooltipWrapper__kqvmR {\n display: none;\n}\n\n.styles-module_overlay__knwhE {\n position: absolute;\n display: flex;\n align-items: center;\n min-width: max-content;\n height: 24px;\n z-index: 2;\n font-family: var(--exp-builder-font-stack-primary);\n font-size: 14px;\n font-weight: 500;\n background-color: var(--exp-builder-gray500);\n color: var(--exp-builder-color-white);\n padding: 4px 12px 4px 12px;\n transition: opacity 0.1s;\n opacity: 0;\n text-wrap: nowrap;\n}\n\n.styles-module_overlayContainer__lUsiC {\n opacity: 0;\n}\n\n.styles-module_overlayAssembly__3BKl4 {\n background-color: var(--exp-builder-purple600);\n}\n\n.styles-module_isDragging__hldL4 > .styles-module_overlay__knwhE,\n.styles-module_isDragging__hldL4 > .styles-module_overlayContainer__lUsiC {\n opacity: 0 !important;\n}\n\n.styles-module_isDragging__hldL4:not(.styles-module_Dropzone__3R-sm):before {\n outline: 2px solid transparent !important;\n}\n\n.styles-module_isHoveringComponent__f7G5m > div > .styles-module_overlay__knwhE,\n.styles-module_DraggableComponent__oyE7Q:hover:not(:has(.styles-module_DraggableComponent__oyE7Q:hover)) > div > .styles-module_overlay__knwhE {\n opacity: 1;\n}\n\n/* hovering related component in layers tab */\n\n.styles-module_DraggableComponent__oyE7Q:has(.styles-module_isHoveringComponent__f7G5m):not(.styles-module_isAssemblyBlock__goT9z):before,\n.styles-module_DraggableComponent__oyE7Q:has(.styles-module_isHoveringComponent__f7G5m):not(.styles-module_isAssemblyBlock__goT9z) .styles-module_DraggableComponent__oyE7Q:not(.styles-module_isHoveringComponent__f7G5m):not(.styles-module_isAssemblyBlock__goT9z):before,\n.styles-module_isHoveringComponent__f7G5m:not(.styles-module_isAssemblyBlock__goT9z) .styles-module_DraggableComponent__oyE7Q:not(.styles-module_isAssemblyBlock__goT9z):before,\n\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isAssemblyBlock__goT9z):not(.styles-module_isDragging__hldL4):hover:before,\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isDragging__hldL4):hover .styles-module_DraggableComponent__oyE7Q:before {\n outline: 2px dashed var(--exp-builder-gray500);\n}\n\n/* hovering component in layers tab */\n\n.styles-module_isHoveringComponent__f7G5m:not(.styles-module_isAssemblyBlock__goT9z):before,\n\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isAssemblyBlock__goT9z):not(.styles-module_isDragging__hldL4):hover:not(:has(.styles-module_DraggableComponent__oyE7Q:hover)):before {\n outline: 2px solid var(--exp-builder-gray500);\n}\n\n/* hovering related pattern in layers tab */\n\n.styles-module_isAssemblyBlock__goT9z:has(.styles-module_isHoveringComponent__f7G5m):before,\n.styles-module_isAssemblyBlock__goT9z:has(.styles-module_isHoveringComponent__f7G5m) .styles-module_isAssemblyBlock__goT9z:not(.styles-module_isHoveringComponent__f7G5m):before,\n.styles-module_isHoveringComponent__f7G5m .styles-module_isAssemblyBlock__goT9z:before,\n\n.styles-module_isAssemblyBlock__goT9z:hover:before,\n.styles-module_isAssemblyBlock__goT9z:hover .styles-module_DraggableComponent__oyE7Q:before,\n.styles-module_DraggableComponent__oyE7Q:not(.styles-module_isDragging__hldL4):hover .styles-module_isAssemblyBlock__goT9z:before {\n outline: 2px dashed var(--exp-builder-purple600);\n}\n\n/* hovering pattern in layers tab */\n\n.styles-module_isAssemblyBlock__goT9z.styles-module_isHoveringComponent__f7G5m:before,\n\n.styles-module_isAssemblyBlock__goT9z:hover:not(:has(.styles-module_DraggableComponent__oyE7Q:hover)):before {\n outline: 2px solid var(--exp-builder-purple600);\n}\n";
6046
- var styles$1 = {"DraggableComponent":"styles-module_DraggableComponent__oyE7Q","Dropzone":"styles-module_Dropzone__3R-sm","isSlot":"styles-module_isSlot__HI9yO","isDragging":"styles-module_isDragging__hldL4","fullHeight":"styles-module_fullHeight__afMfT","fullWidth":"styles-module_fullWidth__Od117","isRoot":"styles-module_isRoot__c-c-x","isEmptyCanvas":"styles-module_isEmptyCanvas__Mm6Al","isEmptyZone":"styles-module_isEmptyZone__XZ1Ej","DraggableClone":"styles-module_DraggableClone__CdKIH","isDestination":"styles-module_isDestination__sE70P","DropzoneClone":"styles-module_DropzoneClone__xiT8j","isDraggingThisComponent":"styles-module_isDraggingThisComponent__yCZTp","isSelected":"styles-module_isSelected__c2QEJ","tooltipWrapper":"styles-module_tooltipWrapper__kqvmR","overlay":"styles-module_overlay__knwhE","overlayContainer":"styles-module_overlayContainer__lUsiC","overlayAssembly":"styles-module_overlayAssembly__3BKl4","isHoveringComponent":"styles-module_isHoveringComponent__f7G5m","isAssemblyBlock":"styles-module_isAssemblyBlock__goT9z"};
6047
- styleInject(css_248z$1);
6048
-
6049
- const Tooltip = ({ coordinates, id, label, isAssemblyBlock, isContainer, isSelected, }) => {
6050
- const tooltipRef = useRef(null);
6051
- const previewSize = '100%'; // This should be based on breakpoints and added to usememo dependency array
6052
- const tooltipStyles = useMemo(() => {
6053
- const tooltipRect = tooltipRef.current?.getBoundingClientRect();
6054
- const draggableRect = document
6055
- .querySelector(`[data-ctfl-draggable-id="${id}"]`)
6056
- ?.getBoundingClientRect();
6057
- const newTooltipStyles = getTooltipPositions({
6058
- previewSize,
6059
- tooltipRect,
6060
- coordinates: draggableRect,
6061
- });
6062
- return newTooltipStyles;
6063
- // 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
6064
- // eslint-disable-next-line react-hooks/exhaustive-deps
6065
- }, [coordinates, id, tooltipRef.current]);
6066
- if (isSelected) {
6067
- return null;
6068
- }
6069
- return (React.createElement("div", { "data-tooltip": true, className: styles$1.tooltipWrapper },
6070
- React.createElement("div", { "data-tooltip": true, ref: tooltipRef, style: tooltipStyles, className: classNames(styles$1.overlay, {
6071
- [styles$1.overlayContainer]: isContainer,
6072
- [styles$1.overlayAssembly]: isAssemblyBlock,
6073
- }) }, label)));
4744
+ return {
4745
+ ...sharedProps,
4746
+ // Allows custom components to render differently in the editor. This needs to be activated
4747
+ // through options as the component has to be aware of this prop to not cause any React warnings.
4748
+ ...(options?.enableCustomEditorView ? { isInExpEditorMode: true } : {}),
4749
+ ...sanitizeNodeProps(props),
4750
+ };
4751
+ }, [cfCsrClassName, node, options?.enableCustomEditorView, props]);
4752
+ return { componentProps };
6074
4753
  };
6075
4754
 
6076
- function useSingleColumn(node, resolveDesignValue) {
6077
- const tree = useTreeStore((store) => store.tree);
6078
- const isSingleColumn = node.data.blockId === CONTENTFUL_COMPONENTS$1.singleColumn.id;
6079
- const isWrapped = useMemo(() => {
6080
- if (!node.parentId || !isSingleColumn) {
6081
- return false;
6082
- }
6083
- const parentNode = getItem({ id: node.parentId }, tree);
6084
- if (!parentNode || parentNode.data.blockId !== CONTENTFUL_COMPONENTS$1.columns.id) {
6085
- return false;
6086
- }
6087
- const { cfWrapColumns } = parentNode.data.props;
6088
- if (cfWrapColumns?.type !== 'DesignValue') {
6089
- return false;
4755
+ function EditorBlock({ node, resolveDesignValue, wrappingPatternIds: parentWrappingPatternIds = new Set(), entityStore, areEntitiesFetched, }) {
4756
+ const isRootAssemblyNode = node.type === ASSEMBLY_NODE_TYPE;
4757
+ const wrappingPatternIds = useMemo(() => {
4758
+ if (isRootAssemblyNode && node.data.blockId) {
4759
+ return new Set([node.data.blockId, ...parentWrappingPatternIds]);
6090
4760
  }
6091
- return resolveDesignValue(cfWrapColumns.valuesByBreakpoint);
6092
- }, [tree, node, isSingleColumn, resolveDesignValue]);
6093
- return {
6094
- isSingleColumn,
6095
- isWrapped,
6096
- };
6097
- }
6098
-
6099
- function getStyle$1(style, snapshot) {
6100
- if (!snapshot.isDropAnimating) {
6101
- return style;
6102
- }
6103
- return {
6104
- ...style,
6105
- // cannot be 0, but make it super tiny
6106
- transitionDuration: `0.001s`,
6107
- };
4761
+ return parentWrappingPatternIds;
4762
+ }, [isRootAssemblyNode, node, parentWrappingPatternIds]);
4763
+ const componentRegistration = useComponentRegistration(node);
4764
+ if (!componentRegistration) {
4765
+ return React.createElement(MissingComponentPlaceholder, { blockId: node.data.blockId });
4766
+ }
4767
+ if (isRootAssemblyNode && node.data.blockId && parentWrappingPatternIds.has(node.data.blockId)) {
4768
+ return React.createElement(CircularDependencyErrorPlaceholder, { wrappingPatternIds: wrappingPatternIds });
4769
+ }
4770
+ const slotNodes = {};
4771
+ for (const slotId in componentRegistration.definition.slots) {
4772
+ const nodes = node.children.filter((child) => child.data.slotId === slotId);
4773
+ slotNodes[slotId] =
4774
+ nodes.length === 0 ? (React.createElement("div", { className: styles$1.emptySlot })) : (React.createElement(React.Fragment, null, nodes.map((slotChildNode) => (React.createElement(EditorBlock, { key: slotChildNode.data.id, node: slotChildNode, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds, entityStore: entityStore, areEntitiesFetched: areEntitiesFetched })))));
4775
+ }
4776
+ const children = componentRegistration.definition.children
4777
+ ? node.children
4778
+ .filter((node) => node.data.slotId === undefined)
4779
+ .map((childNode) => (React.createElement(EditorBlock, { key: childNode.data.id, node: childNode, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds, entityStore: entityStore, areEntitiesFetched: areEntitiesFetched })))
4780
+ : null;
4781
+ return (React.createElement(RegistrationComponent, { node: node, resolveDesignValue: resolveDesignValue, componentRegistration: componentRegistration, slotNodes: slotNodes, entityStore: entityStore, areEntitiesFetched: areEntitiesFetched }, children));
6108
4782
  }
6109
- const EditorBlock = ({ node: rawNode, entityStore, areEntitiesFetched, resolveDesignValue, renderDropzone, index, zoneId, userIsDragging, placeholder, wrappingPatternIds, }) => {
6110
- const { slotId } = parseZoneId(zoneId);
6111
- const ref = useRef(null);
6112
- const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
6113
- const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
6114
- const { node, componentId, elementToRender, definition, isPatternNode, isPatternComponent, isNestedPattern, } = useComponent({
6115
- node: rawNode,
4783
+ const RegistrationComponent = ({ node, resolveDesignValue, componentRegistration, slotNodes, children, entityStore, areEntitiesFetched, }) => {
4784
+ const { componentProps } = useComponentProps({
4785
+ node,
4786
+ resolveDesignValue,
4787
+ definition: componentRegistration.definition,
4788
+ options: componentRegistration.options,
6116
4789
  entityStore,
6117
4790
  areEntitiesFetched,
6118
- resolveDesignValue,
6119
- renderDropzone,
6120
- userIsDragging,
6121
- wrappingPatternIds,
6122
4791
  });
6123
- const { isSingleColumn, isWrapped } = useSingleColumn(node, resolveDesignValue);
6124
- const setDomRect = useDraggedItemStore((state) => state.setDomRect);
6125
- const isHoveredComponent = useDraggedItemStore((state) => state.hoveredComponentId === componentId);
6126
- const coordinates = useSelectedInstanceCoordinates({ node });
6127
- const displayName = node.data.displayName || rawNode.data.displayName || definition?.name;
6128
- const testId = `draggable-${node.data.blockId ?? 'node'}`;
6129
- const isSelected = node.data.id === selectedNodeId;
6130
- const isContainer = node.data.blockId === CONTENTFUL_COMPONENTS$1.container.id;
6131
- const isSlotComponent = Boolean(node.data.slotId);
6132
- const isDragDisabled = isNestedPattern || isPatternComponent || (isSingleColumn && isWrapped) || isSlotComponent;
6133
- const isEmptyZone = useMemo(() => {
6134
- return !node.children.filter((node) => node.data.slotId === slotId).length;
6135
- }, [node.children, slotId]);
6136
- useDraggablePosition({
6137
- draggableId: componentId,
6138
- draggableRef: ref,
6139
- position: DraggablePosition.MOUSE_POSITION,
6140
- });
6141
- const onClick = (e) => {
6142
- e.stopPropagation();
6143
- if (!userIsDragging) {
6144
- setSelectedNodeId(node.data.id);
6145
- // if it is the assembly directly we just want to select it as a normal component
6146
- if (isPatternNode) {
6147
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
6148
- nodeId: node.data.id,
6149
- });
6150
- return;
6151
- }
6152
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
6153
- assembly: node.data.assembly,
6154
- nodeId: node.data.id,
6155
- });
6156
- }
6157
- };
6158
- const onMouseOver = (e) => {
6159
- e.stopPropagation();
6160
- if (userIsDragging)
6161
- return;
6162
- sendMessage(OUTGOING_EVENTS.NewHoveredElement, {
6163
- nodeId: componentId,
6164
- });
6165
- };
6166
- const onMouseDown = (e) => {
6167
- if (isDragDisabled) {
6168
- return;
6169
- }
6170
- e.stopPropagation();
6171
- setDomRect(e.currentTarget.getBoundingClientRect());
6172
- };
6173
- const ToolTipAndPlaceholder = (React.createElement(React.Fragment, null,
6174
- React.createElement(Tooltip, { id: componentId, coordinates: coordinates, isAssemblyBlock: isPatternNode || isPatternComponent, isContainer: isContainer, isSelected: isSelected, label: displayName || 'No label specified' }),
6175
- React.createElement(Placeholder, { ...placeholder, id: componentId }),
6176
- userIsDragging && !isPatternComponent && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId, isEmptyZone: isEmptyZone }))));
6177
- return (React.createElement(Draggable, { key: componentId, draggableId: componentId, index: index, isDragDisabled: isDragDisabled, disableInteractiveElementBlocking: true }, (provided, snapshot) => elementToRender({
6178
- dragProps: {
6179
- ...provided.draggableProps,
6180
- ...provided.dragHandleProps,
6181
- 'data-ctfl-draggable-id': componentId,
6182
- 'data-test-id': testId,
6183
- innerRef: (refNode) => {
6184
- provided?.innerRef(refNode);
6185
- ref.current = refNode;
6186
- },
6187
- className: classNames(styles$1.DraggableComponent, {
6188
- [styles$1.isAssemblyBlock]: isPatternComponent || isPatternNode,
6189
- [styles$1.isDragging]: snapshot?.isDragging || userIsDragging,
6190
- [styles$1.isSelected]: isSelected,
6191
- [styles$1.isHoveringComponent]: isHoveredComponent,
6192
- }),
6193
- style: getStyle$1(provided.draggableProps.style, snapshot),
6194
- onMouseDown,
6195
- onMouseOver,
6196
- onClick,
6197
- ToolTipAndPlaceholder,
6198
- },
6199
- })));
4792
+ return React.createElement(ImportedComponentErrorBoundary, { componentId: node.data.blockId }, React.createElement(componentRegistration.component, { ...componentProps, ...slotNodes }, children));
6200
4793
  };
6201
4794
 
6202
- var css_248z = ".EmptyContainer-module_container__XPH5b {\n height: 200px;\n display: flex;\n width: 100%;\n position: absolute;\n align-items: center;\n justify-content: center;\n flex-direction: row;\n transition: all 0.2s;\n color: var(--exp-builder-gray400);\n font-size: var(--exp-builder-font-size-l);\n font-family: var(--exp-builder-font-stack-primary);\n outline: 2px dashed var(--exp-builder-gray400);\n outline-offset: -2px;\n}\n\n.EmptyContainer-module_highlight__lcICy:hover {\n outline: 2px dashed var(--exp-builder-blue500);\n background-color: rgba(var(--exp-builder-blue100-rgb), 0.5);\n cursor: grabbing;\n}\n\n.EmptyContainer-module_icon__82-2O rect {\n fill: var(--exp-builder-gray400);\n}\n\n.EmptyContainer-module_label__4TxRa {\n margin-left: var(--exp-builder-spacing-s);\n}\n";
6203
- var styles = {"container":"EmptyContainer-module_container__XPH5b","highlight":"EmptyContainer-module_highlight__lcICy","icon":"EmptyContainer-module_icon__82-2O","label":"EmptyContainer-module_label__4TxRa"};
4795
+ var css_248z = ".EmptyCanvasMessage-module_empty-canvas-container__7K-0l {\n height: 200px;\n display: flex;\n width: 100%;\n align-items: center;\n justify-content: center;\n flex-direction: row;\n transition: all 0.2s;\n color: var(--exp-builder-gray400);\n font-size: var(--exp-builder-font-size-l);\n font-family: var(--exp-builder-font-stack-primary);\n outline: 2px dashed var(--exp-builder-gray400);\n outline-offset: -2px;\n}\n\n.EmptyCanvasMessage-module_empty-canvas-icon__EztFr rect {\n fill: var(--exp-builder-gray400);\n}\n\n.EmptyCanvasMessage-module_empty-canvas-label__cbIrR {\n margin-left: var(--exp-builder-spacing-s);\n}\n";
4796
+ var styles = {"empty-canvas-container":"EmptyCanvasMessage-module_empty-canvas-container__7K-0l","empty-canvas-icon":"EmptyCanvasMessage-module_empty-canvas-icon__EztFr","empty-canvas-label":"EmptyCanvasMessage-module_empty-canvas-label__cbIrR"};
6204
4797
  styleInject(css_248z);
6205
4798
 
6206
- const EmptyContainer = ({ isDragging }) => {
6207
- return (React.createElement("div", { className: classNames(styles.container, {
6208
- [styles.highlight]: isDragging,
6209
- }), "data-type": "empty-container" },
6210
- React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "37", height: "36", fill: "none", className: styles.icon },
4799
+ const EmptyCanvasMessage = () => {
4800
+ return (React.createElement("div", { className: styles['empty-canvas-container'], "data-type": "empty-container" },
4801
+ React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "37", height: "36", fill: "none", className: styles['empty-canvas-icon'] },
6211
4802
  React.createElement("rect", { width: "11.676", height: "11.676", x: "18.512", y: ".153", rx: "1.621", transform: "rotate(45 18.512 .153)" }),
6212
4803
  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)" }),
6213
4804
  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)" }),
6214
4805
  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)" }),
6215
4806
  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" })),
6216
- React.createElement("span", { className: styles.label }, "Add components to begin")));
6217
- };
6218
-
6219
- const useDropzoneDirection = ({ resolveDesignValue, node, zoneId }) => {
6220
- const zone = useZoneStore((state) => state.zones);
6221
- const upsertZone = useZoneStore((state) => state.upsertZone);
6222
- useEffect(() => {
6223
- function getDirection() {
6224
- if (!node || !node.data.blockId) {
6225
- return 'vertical';
6226
- }
6227
- if (!isContentfulStructureComponent(node.data.blockId)) {
6228
- return 'vertical';
6229
- }
6230
- if (node.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id) {
6231
- return 'horizontal';
6232
- }
6233
- const designValues = node.data.props['cfFlexDirection'];
6234
- if (!designValues || !resolveDesignValue || designValues.type !== 'DesignValue') {
6235
- return 'vertical';
6236
- }
6237
- const direction = resolveDesignValue(designValues.valuesByBreakpoint);
6238
- if (direction === 'row') {
6239
- return 'horizontal';
6240
- }
6241
- return 'vertical';
6242
- }
6243
- upsertZone(zoneId, { direction: getDirection() });
6244
- }, [node, resolveDesignValue, zoneId, upsertZone]);
6245
- return zone[zoneId]?.direction || 'vertical';
4807
+ React.createElement("span", { className: styles['empty-canvas-label'] }, "Add components to begin")));
6246
4808
  };
6247
4809
 
6248
- function getStyle(style = {}, snapshot) {
6249
- if (!snapshot?.isDropAnimating) {
6250
- return style;
6251
- }
6252
- return {
6253
- ...style,
6254
- // cannot be 0, but make it super tiny
6255
- transitionDuration: `0.001s`,
6256
- };
6257
- }
6258
- const EditorBlockClone = ({ node: rawNode, entityStore, areEntitiesFetched, resolveDesignValue, snapshot, provided, renderDropzone, wrappingPatternIds, }) => {
6259
- const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
6260
- const { node, elementToRender } = useComponent({
6261
- node: rawNode,
6262
- entityStore,
6263
- areEntitiesFetched,
6264
- resolveDesignValue,
6265
- renderDropzone,
6266
- userIsDragging,
6267
- wrappingPatternIds,
6268
- });
6269
- const isAssemblyBlock = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
6270
- return elementToRender({
6271
- dragProps: {
6272
- ...provided?.draggableProps,
6273
- ...provided?.dragHandleProps,
6274
- 'data-ctfl-dragging-element': 'true',
6275
- innerRef: provided?.innerRef,
6276
- className: classNames(styles$1.DraggableComponent, styles$1.DraggableClone, {
6277
- [styles$1.isAssemblyBlock]: isAssemblyBlock,
6278
- [styles$1.isDragging]: snapshot?.isDragging,
6279
- }),
6280
- style: getStyle(provided?.draggableProps.style, snapshot),
4810
+ /**
4811
+ * This function gets the element co-ordinates of a specified component in the DOM and its parent
4812
+ * and sends the DOM Rect to the client app.
4813
+ */
4814
+ const sendCanvasGeometryUpdatedMessage = async (tree, sourceEvent) => {
4815
+ const nodeToCoordinatesMap = {};
4816
+ await waitForAllImagesToBeLoaded();
4817
+ collectNodeCoordinates(tree.root, nodeToCoordinatesMap);
4818
+ sendMessage(OUTGOING_EVENTS.CanvasGeometryUpdated, {
4819
+ size: {
4820
+ width: document.documentElement.offsetWidth,
4821
+ height: document.documentElement.offsetHeight,
6281
4822
  },
4823
+ nodes: nodeToCoordinatesMap,
4824
+ sourceEvent,
6282
4825
  });
6283
4826
  };
6284
-
6285
- const getHtmlDragProps = (dragProps) => {
6286
- if (dragProps) {
6287
- const { ToolTipAndPlaceholder, Tag, innerRef, wrapComponent, ...htmlDragProps } = dragProps;
6288
- return htmlDragProps;
6289
- }
6290
- return {};
6291
- };
6292
- const getHtmlComponentProps = (props) => {
6293
- if (props) {
6294
- const { editorMode, renderDropzone, node, ...htmlProps } = props;
6295
- return htmlProps;
6296
- }
6297
- return {};
6298
- };
6299
-
6300
- function DropzoneClone({ node, entityStore, zoneId, resolveDesignValue, WrapperComponent = 'div', renderDropzone, dragProps, wrappingPatternIds, areEntitiesFetched, ...rest }) {
6301
- const tree = useTreeStore((state) => state.tree);
6302
- const content = node?.children || tree.root?.children || [];
6303
- const { slotId } = parseZoneId(zoneId);
6304
- const htmlDraggableProps = getHtmlDragProps(dragProps);
6305
- const htmlProps = getHtmlComponentProps(rest);
6306
- const isRootZone = zoneId === ROOT_ID;
6307
- if (!resolveDesignValue) {
6308
- return null;
6309
- }
6310
- return (React.createElement(WrapperComponent, { ...htmlDraggableProps, ...htmlProps, className: classNames(dragProps?.className, styles$1.Dropzone, styles$1.DropzoneClone, rest.className, {
6311
- [styles$1.isRoot]: isRootZone,
6312
- [styles$1.isEmptyZone]: !content.length,
6313
- }), "data-ctfl-slot-id": slotId, ref: (refNode) => {
6314
- if (dragProps?.innerRef) {
6315
- dragProps.innerRef(refNode);
6316
- }
6317
- } }, content
6318
- .filter((node) => node.data.slotId === slotId)
6319
- .map((item) => {
6320
- const componentId = item.data.id;
6321
- return (React.createElement(EditorBlockClone, { entityStore: entityStore, areEntitiesFetched: areEntitiesFetched, key: componentId, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone, wrappingPatternIds: wrappingPatternIds }));
6322
- })));
6323
- }
6324
-
6325
- function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponent = 'div', dragProps, entityStore, areEntitiesFetched, wrappingPatternIds: parentWrappingPatternIds = new Set(), ...rest }) {
6326
- const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
6327
- const draggedItem = useDraggedItemStore((state) => state.draggedItem);
6328
- const isDraggingNewComponent = useDraggedItemStore((state) => Boolean(state.componentId));
6329
- const isHoveringZone = useZoneStore((state) => state.hoveringZone === zoneId);
6330
- const tree = useTreeStore((state) => state.tree);
6331
- const content = node?.children || tree.root?.children || [];
6332
- const { slotId } = parseZoneId(zoneId);
6333
- const direction = useDropzoneDirection({ resolveDesignValue, node, zoneId });
6334
- const draggedDestinationId = draggedItem && draggedItem.destination?.droppableId;
6335
- const draggedNode = useMemo(() => {
6336
- if (!draggedItem)
4827
+ const collectNodeCoordinates = (node, nodeToCoordinatesMap) => {
4828
+ const selectedElement = document.querySelector(`[data-cf-node-id="${node.data.id}"]`);
4829
+ if (selectedElement) {
4830
+ const rect = getElementCoordinates(selectedElement);
4831
+ if (isElementHidden(rect)) {
6337
4832
  return;
6338
- return getItem({ id: draggedItem.draggableId }, tree);
6339
- }, [draggedItem, tree]);
6340
- const isRootZone = zoneId === ROOT_ID;
6341
- const isDestination = draggedDestinationId === zoneId;
6342
- const isEmptyCanvas = isRootZone && !content.length;
6343
- const isAssembly = ASSEMBLY_NODE_TYPES.includes(node?.type || '');
6344
- const isRootAssembly = node?.type === ASSEMBLY_NODE_TYPE;
6345
- const htmlDraggableProps = getHtmlDragProps(dragProps);
6346
- const htmlProps = getHtmlComponentProps(rest);
6347
- const wrappingPatternIds = useMemo(() => {
6348
- // On the top level, the node is not defined. If the root blockId is not the default string,
6349
- // we assume that it is the entry ID of the experience/ pattern to properly detect circular dependencies
6350
- if (!node && tree.root.data.blockId && tree.root.data.blockId !== ROOT_ID) {
6351
- return new Set([tree.root.data.blockId, ...parentWrappingPatternIds]);
6352
- }
6353
- if (isRootAssembly && node?.data.blockId) {
6354
- return new Set([node.data.blockId, ...parentWrappingPatternIds]);
6355
- }
6356
- return parentWrappingPatternIds;
6357
- }, [isRootAssembly, node, parentWrappingPatternIds, tree.root.data.blockId]);
6358
- // To avoid a circular dependency, we create the recursive rendering function here and trickle it down
6359
- const renderDropzone = useCallback((node, props) => {
6360
- return (React.createElement(Dropzone, { zoneId: node.data.id, entityStore: entityStore, node: node, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds, areEntitiesFetched: areEntitiesFetched, ...props }));
6361
- }, [wrappingPatternIds, resolveDesignValue, entityStore, areEntitiesFetched]);
6362
- const renderClonedDropzone = useCallback((node, props) => {
6363
- return (React.createElement(DropzoneClone, { entityStore: entityStore, areEntitiesFetched: areEntitiesFetched, zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, renderDropzone: renderClonedDropzone, wrappingPatternIds: wrappingPatternIds, ...props }));
6364
- }, [resolveDesignValue, wrappingPatternIds, entityStore, areEntitiesFetched]);
6365
- const isDropzoneEnabled = useMemo(() => {
6366
- const isColumns = node?.data.blockId === CONTENTFUL_COMPONENTS$1.columns.id;
6367
- const isDraggingSingleColumn = draggedNode?.data.blockId === CONTENTFUL_COMPONENTS$1.singleColumn.id;
6368
- const isParentOfDraggedNode = node?.data.id === draggedNode?.parentId;
6369
- // If dragging a single column, only enable the dropzone of the parent
6370
- // columns component
6371
- if (isDraggingSingleColumn && isColumns && isParentOfDraggedNode) {
6372
- return true;
6373
- }
6374
- // If dragging a single column, disable dropzones for any component besides
6375
- // the parent of the dragged single column
6376
- if (isDraggingSingleColumn && !isParentOfDraggedNode) {
6377
- return false;
6378
4833
  }
6379
- // Disable dropzone for Columns component
6380
- if (isColumns) {
6381
- return false;
6382
- }
6383
- // Disable dropzone for Assembly
6384
- if (isAssembly) {
6385
- return false;
6386
- }
6387
- // Enable dropzone for the non-root hovered zones if component is not allowed on root
6388
- if (!isDraggingNewComponent &&
6389
- !isComponentAllowedOnRoot({ type: draggedNode?.type, componentId: draggedNode?.data.blockId })) {
6390
- return isHoveringZone && !isRootZone;
6391
- }
6392
- // Enable dropzone for the hovered zone only
6393
- return isHoveringZone;
6394
- }, [isAssembly, isHoveringZone, isRootZone, isDraggingNewComponent, draggedNode, node]);
6395
- if (!resolveDesignValue) {
6396
- return null;
4834
+ nodeToCoordinatesMap[node.data.id] = {
4835
+ coordinates: {
4836
+ x: rect.x + window.scrollX,
4837
+ y: rect.y + window.scrollY,
4838
+ width: rect.width,
4839
+ height: rect.height,
4840
+ },
4841
+ };
6397
4842
  }
6398
- const isPatternWrapperComponentFullHeight = isRootAssembly
6399
- ? node.children.length === 1 &&
6400
- resolveDesignValue(node?.children[0]?.data.props.cfHeight?.valuesByBreakpoint ?? {}, 'cfHeight') === '100%'
6401
- : false;
6402
- const isPatternWrapperComponentFullWidth = isRootAssembly
6403
- ? node.children.length === 1 &&
6404
- resolveDesignValue(node?.children[0]?.data.props.cfWidth?.valuesByBreakpoint ?? {}, 'cfWidth') === '100%'
6405
- : false;
6406
- return (React.createElement(Droppable, { droppableId: zoneId, direction: direction, isDropDisabled: !isDropzoneEnabled, renderClone: (provided, snapshot, rubic) => (React.createElement(EditorBlockClone, { entityStore: entityStore, areEntitiesFetched: areEntitiesFetched, node: content[rubic.source.index], resolveDesignValue: resolveDesignValue, provided: provided, snapshot: snapshot, renderDropzone: renderClonedDropzone, wrappingPatternIds: wrappingPatternIds })) }, (provided, snapshot) => {
6407
- return (React.createElement(WrapperComponent, { ...(provided || { droppableProps: {} }).droppableProps, ...htmlDraggableProps, ...htmlProps, ref: (refNode) => {
6408
- if (dragProps?.innerRef) {
6409
- dragProps.innerRef(refNode);
4843
+ node.children.forEach((child) => collectNodeCoordinates(child, nodeToCoordinatesMap));
4844
+ };
4845
+ const waitForAllImagesToBeLoaded = () => {
4846
+ // If the document contains an image, wait for this image to be loaded before collecting & sending all geometry data.
4847
+ const allImageNodes = document.querySelectorAll('img');
4848
+ return Promise.all(Array.from(allImageNodes).map((imageNode) => {
4849
+ if (imageNode.complete) {
4850
+ return Promise.resolve();
4851
+ }
4852
+ return new Promise((resolve, reject) => {
4853
+ const handleImageLoad = (event) => {
4854
+ imageNode.removeEventListener('load', handleImageLoad);
4855
+ imageNode.removeEventListener('error', handleImageLoad);
4856
+ if (event.type === 'error') {
4857
+ console.warn('Image failed to load:', imageNode);
4858
+ reject();
4859
+ }
4860
+ else {
4861
+ resolve();
6410
4862
  }
6411
- provided?.innerRef(refNode);
6412
- }, id: zoneId, "data-ctfl-zone-id": zoneId, "data-ctfl-slot-id": slotId, className: classNames(dragProps?.className, styles$1.Dropzone, className, {
6413
- [styles$1.isEmptyCanvas]: isEmptyCanvas,
6414
- [styles$1.isDragging]: userIsDragging,
6415
- [styles$1.isDestination]: isDestination && !isAssembly,
6416
- [styles$1.isRoot]: isRootZone,
6417
- [styles$1.isEmptyZone]: !content.length,
6418
- [styles$1.isSlot]: Boolean(slotId),
6419
- [styles$1.fullHeight]: isPatternWrapperComponentFullHeight,
6420
- [styles$1.fullWidth]: isPatternWrapperComponentFullWidth,
6421
- }) },
6422
- isEmptyCanvas ? (React.createElement(EmptyContainer, { isDragging: isRootZone && userIsDragging })) : (content
6423
- .filter((node) => node.data.slotId === slotId)
6424
- .map((item, i) => (React.createElement(EditorBlock, { entityStore: entityStore, areEntitiesFetched: areEntitiesFetched, placeholder: {
6425
- isDraggingOver: snapshot?.isDraggingOver,
6426
- totalIndexes: content.length,
6427
- elementIndex: i,
6428
- dropzoneElementId: zoneId,
6429
- direction,
6430
- }, index: i, zoneId: zoneId, key: item.data.id, userIsDragging: userIsDragging, draggingNewComponent: isDraggingNewComponent, node: item, resolveDesignValue: resolveDesignValue, renderDropzone: renderDropzone, wrappingPatternIds: wrappingPatternIds })))),
6431
- provided?.placeholder,
6432
- dragProps?.ToolTipAndPlaceholder));
4863
+ };
4864
+ imageNode.addEventListener('load', handleImageLoad);
4865
+ imageNode.addEventListener('error', handleImageLoad);
4866
+ });
6433
4867
  }));
6434
- }
4868
+ };
6435
4869
 
6436
- const RootRenderer = ({ onChange, inMemoryEntitiesStore, }) => {
6437
- useEditorSubscriber(inMemoryEntitiesStore);
6438
- const dragItem = useDraggedItemStore((state) => state.componentId);
6439
- const userIsDragging = useDraggedItemStore((state) => state.isDraggingOnCanvas);
6440
- const setHoveredComponentId = useDraggedItemStore((state) => state.setHoveredComponentId);
6441
- const breakpoints = useTreeStore((state) => state.breakpoints);
6442
- const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
6443
- const containerRef = useRef(null);
6444
- const { resolveDesignValue } = useBreakpoints(breakpoints);
6445
- const [containerStyles, setContainerStyles] = useState({});
6446
- const tree = useTreeStore((state) => state.tree);
6447
- const handleMouseOver = useCallback(() => {
6448
- // Remove hover state set by UI when mouse is over canvas
6449
- setHoveredComponentId();
6450
- // Remove hover styling from components in the layers tab
6451
- sendMessage(OUTGOING_EVENTS.NewHoveredElement, {});
6452
- }, [setHoveredComponentId]);
6453
- const handleClickOutside = useCallback((e) => {
6454
- const element = e.target;
6455
- const isRoot = element.getAttribute('data-ctfl-zone-id') === ROOT_ID;
6456
- const clickedOnCanvas = element.closest(`[data-ctfl-root]`);
6457
- if (clickedOnCanvas && !isRoot) {
6458
- return;
6459
- }
6460
- sendMessage(OUTGOING_EVENTS.OutsideCanvasClick, {
6461
- outsideCanvasClick: true,
4870
+ const useCanvasGeometryUpdates = ({ tree }) => {
4871
+ const debouncedUpdateGeometry = useMemo(() => debounce((tree, sourceEvent) => {
4872
+ // When the DOM changed, we still need to wait for the next frame to ensure that
4873
+ // rendering is complete (e.g. this is required when deleting a node).
4874
+ window.requestAnimationFrame(() => {
4875
+ sendCanvasGeometryUpdatedMessage(tree, sourceEvent);
6462
4876
  });
6463
- sendMessage(OUTGOING_EVENTS.ComponentSelected, {
6464
- nodeId: '',
6465
- });
6466
- setSelectedNodeId('');
6467
- }, [setSelectedNodeId]);
6468
- const handleResizeCanvas = useCallback(() => {
6469
- const parentElement = containerRef.current?.parentElement;
6470
- if (!parentElement) {
6471
- return;
6472
- }
6473
- let siblingHeight = 0;
6474
- for (const child of parentElement.children) {
6475
- if (!child.hasAttribute('data-ctfl-root')) {
6476
- siblingHeight += child.getBoundingClientRect().height;
6477
- }
6478
- }
6479
- if (!siblingHeight) {
6480
- /**
6481
- * DRAGGABLE_HEIGHT is subtracted here due to an uninteded scrolling effect
6482
- * when dragging a new component onto the canvas
6483
- *
6484
- * The DRAGGABLE_HEIGHT is then added as margin bottom to offset this value
6485
- * so that visually there is no difference to the user.
6486
- */
6487
- setContainerStyles({
6488
- minHeight: `${window.innerHeight - DRAGGABLE_HEIGHT}px`,
6489
- });
6490
- return;
6491
- }
6492
- setContainerStyles({
6493
- minHeight: `${window.innerHeight - siblingHeight}px`,
6494
- });
6495
- // eslint-disable-next-line react-hooks/exhaustive-deps
6496
- }, [containerRef.current]);
4877
+ }, 100, {
4878
+ leading: true,
4879
+ // To be sure, we recalculate it at the end of the frame again. Though, we couldn't
4880
+ // yet show the need for this. So we might be able to drop this later to boost performance.
4881
+ trailing: true,
4882
+ }), []);
4883
+ // Store tree in a ref to avoid the need to deactivate & reactivate the mutation observer
4884
+ // when the tree changes. This is important to avoid missing out on some mutation events.
4885
+ const treeRef = useRef(tree);
6497
4886
  useEffect(() => {
6498
- if (onChange)
6499
- onChange(tree);
6500
- }, [tree, onChange]);
4887
+ treeRef.current = tree;
4888
+ }, [tree]);
4889
+ // Handling window resize events
6501
4890
  useEffect(() => {
6502
- window.addEventListener('mouseover', handleMouseOver);
6503
- return () => {
6504
- window.removeEventListener('mouseover', handleMouseOver);
6505
- };
6506
- }, [handleMouseOver]);
4891
+ const resizeEventListener = () => debouncedUpdateGeometry(treeRef.current, 'resize');
4892
+ window.addEventListener('resize', resizeEventListener);
4893
+ return () => window.removeEventListener('resize', resizeEventListener);
4894
+ }, [debouncedUpdateGeometry]);
4895
+ // Handling DOM mutations
6507
4896
  useEffect(() => {
6508
- document.addEventListener('click', handleClickOutside);
6509
- return () => {
6510
- document.removeEventListener('click', handleClickOutside);
6511
- };
6512
- }, [handleClickOutside]);
6513
- useEffect(() => {
6514
- handleResizeCanvas();
6515
- // eslint-disable-next-line react-hooks/exhaustive-deps
6516
- }, [containerRef.current]);
4897
+ const observer = new MutationObserver(() => debouncedUpdateGeometry(treeRef.current, 'mutation'));
4898
+ // send initial geometry in case the tree is empty
4899
+ debouncedUpdateGeometry(treeRef.current, 'mutation');
4900
+ observer.observe(document.documentElement, {
4901
+ childList: true,
4902
+ subtree: true,
4903
+ attributes: true,
4904
+ });
4905
+ return () => observer.disconnect();
4906
+ }, [debouncedUpdateGeometry]);
4907
+ };
4908
+
4909
+ const RootRenderer = ({ inMemoryEntitiesStore }) => {
4910
+ useEditorSubscriber(inMemoryEntitiesStore);
4911
+ const tree = useTreeStore((state) => state.tree);
4912
+ useCanvasGeometryUpdates({ tree });
4913
+ const breakpoints = useTreeStore((state) => state.breakpoints);
4914
+ const { resolveDesignValue } = useBreakpoints(breakpoints);
4915
+ // If the root blockId is defined but not the default string, it is the entry ID
4916
+ // of the experience/ pattern to properly detect circular dependencies.
4917
+ const rootBlockId = tree.root.data.blockId ?? ROOT_ID;
4918
+ const wrappingPatternIds = rootBlockId !== ROOT_ID ? new Set([rootBlockId]) : new Set();
6517
4919
  const entityStore = inMemoryEntitiesStore((state) => state.entityStore);
6518
4920
  const areEntitiesFetched = inMemoryEntitiesStore((state) => state.areEntitiesFetched);
6519
- return (React.createElement(DNDProvider, null,
6520
- dragItem && React.createElement(DraggableContainer, { id: dragItem }),
6521
- React.createElement("div", { "data-ctfl-root": true, className: styles$3.container, ref: containerRef, style: containerStyles },
6522
- userIsDragging && React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitbox }),
6523
- React.createElement(Dropzone, { zoneId: ROOT_ID, resolveDesignValue: resolveDesignValue, entityStore: entityStore, areEntitiesFetched: areEntitiesFetched }),
6524
- userIsDragging && (React.createElement(React.Fragment, null,
6525
- React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.hitboxLower }),
6526
- React.createElement("div", { "data-ctfl-zone-id": ROOT_ID, className: styles$3.canvasBottomSpacer })))),
6527
- React.createElement("div", { "data-ctfl-hitboxes": true })));
4921
+ return (React.createElement(React.Fragment, null,
4922
+ React.createElement("div", { "data-ctfl-root": true, className: styles$2.rootContainer }, !tree.root.children.length ? (React.createElement(EmptyCanvasMessage, null)) : (tree.root.children.map((topLevelChildNode) => (React.createElement(EditorBlock, { key: topLevelChildNode.data.id, node: topLevelChildNode, resolveDesignValue: resolveDesignValue, wrappingPatternIds: wrappingPatternIds, entityStore: entityStore, areEntitiesFetched: areEntitiesFetched })))))));
6528
4923
  };
6529
4924
 
6530
4925
  const useInitializeEditor = (inMemoryEntitiesStore) => {
@@ -6566,38 +4961,11 @@ const useInitializeEditor = (inMemoryEntitiesStore) => {
6566
4961
  const VisualEditorRoot = ({ experience, inMemoryEntitiesStore: inMemoryEntitiesStore$1 = inMemoryEntitiesStore, }) => {
6567
4962
  const initialized = useInitializeEditor(inMemoryEntitiesStore$1);
6568
4963
  const setHyperLinkPattern = useEditorStore((state) => state.setHyperLinkPattern);
6569
- const setMousePosition = useDraggedItemStore((state) => state.setMousePosition);
6570
- const setHoveringZone = useZoneStore((state) => state.setHoveringZone);
6571
4964
  useEffect(() => {
6572
4965
  if (experience?.hyperlinkPattern) {
6573
4966
  setHyperLinkPattern(experience.hyperlinkPattern);
6574
4967
  }
6575
4968
  }, [experience?.hyperlinkPattern, setHyperLinkPattern]);
6576
- useEffect(() => {
6577
- const onMouseMove = (e) => {
6578
- setMousePosition(e.clientX, e.clientY);
6579
- const target = e.target;
6580
- const zoneId = target.closest(`[${CTFL_ZONE_ID}]`)?.getAttribute(CTFL_ZONE_ID);
6581
- if (zoneId) {
6582
- setHoveringZone(zoneId);
6583
- }
6584
- if (!SimulateDnD$1.isDragging) {
6585
- return;
6586
- }
6587
- if (target.id === NEW_COMPONENT_ID) {
6588
- return;
6589
- }
6590
- SimulateDnD$1.updateDrag(e.clientX, e.clientY);
6591
- sendMessage(OUTGOING_EVENTS.MouseMove, {
6592
- clientX: e.pageX - window.scrollX,
6593
- clientY: e.pageY - window.scrollY,
6594
- });
6595
- };
6596
- document.addEventListener('mousemove', onMouseMove);
6597
- return () => {
6598
- document.removeEventListener('mousemove', onMouseMove);
6599
- };
6600
- }, [setHoveringZone, setMousePosition]);
6601
4969
  if (!initialized)
6602
4970
  return null;
6603
4971
  return React.createElement(RootRenderer, { inMemoryEntitiesStore: inMemoryEntitiesStore$1 });