@contentful/experiences-visual-editor-react 1.0.2 → 1.0.3-beta.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/README.md +7 -1
- package/dist/index.js +56 -69
- package/dist/index.js.map +1 -1
- package/dist/renderApp.js +56 -69
- package/dist/renderApp.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
-
# visual-editor
|
|
1
|
+
# @contentful/experiences-visual-editor-react
|
|
2
|
+
|
|
3
|
+
This package provides a visual editor for the [Experiences SDK](https://www.contentful.com/developers/docs/experiences/set-up-experiences-sdk/). It implements drag-and-drop functionality into the canvas, making the creation and editing of experiences more intuitive and user-friendly.
|
|
4
|
+
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
Please refer to our [Documentation](https://www.contentful.com/developers/docs/experiences/) to learn more about it.
|
package/dist/index.js
CHANGED
|
@@ -89,6 +89,7 @@ const structureComponents = new Set([
|
|
|
89
89
|
CONTENTFUL_COMPONENTS$1.singleColumn.id,
|
|
90
90
|
]);
|
|
91
91
|
const isContentfulStructureComponent = (componentId) => structureComponents.has(componentId ?? '');
|
|
92
|
+
const isComponentAllowedOnRoot = (componentId) => isContentfulStructureComponent(componentId) || componentId === CONTENTFUL_COMPONENTS$1.divider.id;
|
|
92
93
|
const isEmptyStructureWithRelativeHeight = (children, componentId, height) => {
|
|
93
94
|
return (children === 0 &&
|
|
94
95
|
isContentfulStructureComponent(componentId) &&
|
|
@@ -2763,7 +2764,7 @@ const DraggableChildComponent = (props) => {
|
|
|
2763
2764
|
})));
|
|
2764
2765
|
};
|
|
2765
2766
|
|
|
2766
|
-
var css_248z$2 = ".styles-module_container__te-1H {\n margin-left: auto;\n margin-right: auto;\n position: relative;\n height: 100%;\n width: 100%;\n background-color: transparent;\n transition: background-color 0.2s;\n pointer-events: all
|
|
2767
|
+
var css_248z$2 = ".styles-module_container__te-1H {\n margin-left: auto;\n margin-right: auto;\n position: relative;\n height: 100%;\n width: 100%;\n background-color: transparent;\n transition: background-color 0.2s;\n pointer-events: all;\n}\n\n.styles-module_container__te-1H:not(.styles-module_isRoot__5cn-i):before {\n content: '';\n position: absolute;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n outline-offset: -1px;\n outline: 2px solid transparent;\n z-index: 1;\n transition: outline 0.2s;\n pointer-events: none;\n}\n\n.styles-module_isRoot__5cn-i,\n.styles-module_isEmptyCanvas__0XHZR {\n flex: 1;\n}\n\n.styles-module_isEmptyZone__zVpnZ {\n min-height: 80px;\n}\n\n.styles-module_isDragging__Gm8v5:not(.styles-module_isRoot__5cn-i):before {\n outline: 2px dashed var(--exp-builder-gray300);\n}\n\n.styles-module_isDestination__5sCQx:not(.styles-module_isRoot__5cn-i):before {\n transition:\n outline 0.2s,\n background-color 0.2s;\n outline: 2px dashed var(--exp-builder-blue400);\n background-color: rgba(var(--exp-builder-blue100-rgb), 0.5);\n z-index: 2;\n}\n\n.styles-module_hitbox__YQ-1Z {\n position: fixed;\n pointer-events: all;\n}\n";
|
|
2767
2768
|
var styles$2 = {"container":"styles-module_container__te-1H","isRoot":"styles-module_isRoot__5cn-i","isEmptyCanvas":"styles-module_isEmptyCanvas__0XHZR","isEmptyZone":"styles-module_isEmptyZone__zVpnZ","isDragging":"styles-module_isDragging__Gm8v5","isDestination":"styles-module_isDestination__5sCQx","hitbox":"styles-module_hitbox__YQ-1Z"};
|
|
2768
2769
|
styleInject(css_248z$2);
|
|
2769
2770
|
|
|
@@ -3155,13 +3156,14 @@ const { WIDTH, HEIGHT, INITIAL_OFFSET, OFFSET_INCREMENT, MIN_HEIGHT, MIN_DEPTH_H
|
|
|
3155
3156
|
const calcOffsetDepth = (depth) => {
|
|
3156
3157
|
return INITIAL_OFFSET - OFFSET_INCREMENT * depth;
|
|
3157
3158
|
};
|
|
3158
|
-
const getHitboxStyles = ({ direction, zoneDepth, domRect }) => {
|
|
3159
|
+
const getHitboxStyles = ({ direction, zoneDepth, domRect, scrollY, offsetRect, }) => {
|
|
3159
3160
|
if (!domRect) {
|
|
3160
3161
|
return {
|
|
3161
3162
|
display: 'none',
|
|
3162
3163
|
};
|
|
3163
3164
|
}
|
|
3164
3165
|
const { width, height, top, left, bottom, right } = domRect;
|
|
3166
|
+
const { height: offsetHeight, width: offsetWidth } = offsetRect || { height: 0, width: 0 };
|
|
3165
3167
|
const MAX_SELF_HEIGHT = DRAGGABLE_HEIGHT * 2;
|
|
3166
3168
|
const isDeepZone = zoneDepth > DEEP_ZONE;
|
|
3167
3169
|
const isAboveMaxHeight = height > MAX_SELF_HEIGHT;
|
|
@@ -3170,7 +3172,7 @@ const getHitboxStyles = ({ direction, zoneDepth, domRect }) => {
|
|
|
3170
3172
|
return {
|
|
3171
3173
|
width,
|
|
3172
3174
|
height: HEIGHT,
|
|
3173
|
-
top: top - calcOffsetDepth(zoneDepth) - scrollY,
|
|
3175
|
+
top: top + offsetHeight - calcOffsetDepth(zoneDepth) - scrollY,
|
|
3174
3176
|
left,
|
|
3175
3177
|
zIndex: 100 + zoneDepth,
|
|
3176
3178
|
};
|
|
@@ -3178,7 +3180,7 @@ const getHitboxStyles = ({ direction, zoneDepth, domRect }) => {
|
|
|
3178
3180
|
return {
|
|
3179
3181
|
width,
|
|
3180
3182
|
height: HEIGHT,
|
|
3181
|
-
top: bottom + calcOffsetDepth(zoneDepth) - scrollY,
|
|
3183
|
+
top: bottom + offsetHeight + calcOffsetDepth(zoneDepth) - scrollY,
|
|
3182
3184
|
left,
|
|
3183
3185
|
zIndex: 100 + zoneDepth,
|
|
3184
3186
|
};
|
|
@@ -3186,7 +3188,7 @@ const getHitboxStyles = ({ direction, zoneDepth, domRect }) => {
|
|
|
3186
3188
|
return {
|
|
3187
3189
|
width: WIDTH,
|
|
3188
3190
|
height: height - HEIGHT,
|
|
3189
|
-
left: left - calcOffsetDepth(zoneDepth) - WIDTH / 2,
|
|
3191
|
+
left: left + offsetWidth - calcOffsetDepth(zoneDepth) - WIDTH / 2,
|
|
3190
3192
|
top: top + HEIGHT / 2 - scrollY,
|
|
3191
3193
|
zIndex: 100 + zoneDepth,
|
|
3192
3194
|
};
|
|
@@ -3194,7 +3196,7 @@ const getHitboxStyles = ({ direction, zoneDepth, domRect }) => {
|
|
|
3194
3196
|
return {
|
|
3195
3197
|
width: WIDTH,
|
|
3196
3198
|
height: height - HEIGHT,
|
|
3197
|
-
left: right - calcOffsetDepth(zoneDepth) - WIDTH / 2,
|
|
3199
|
+
left: right + offsetWidth - calcOffsetDepth(zoneDepth) - WIDTH / 2,
|
|
3198
3200
|
top: top + HEIGHT / 2 - scrollY,
|
|
3199
3201
|
zIndex: 100 + zoneDepth,
|
|
3200
3202
|
};
|
|
@@ -3228,12 +3230,14 @@ const getHitboxStyles = ({ direction, zoneDepth, domRect }) => {
|
|
|
3228
3230
|
}
|
|
3229
3231
|
};
|
|
3230
3232
|
|
|
3231
|
-
const Hitboxes = ({ zoneId, parentZoneId,
|
|
3233
|
+
const Hitboxes = ({ zoneId, parentZoneId, isEmptyZone }) => {
|
|
3232
3234
|
const tree = useTreeStore((state) => state.tree);
|
|
3233
3235
|
const isDraggingOnCanvas = useDraggedItemStore((state) => state.isDraggingOnCanvas);
|
|
3234
3236
|
const scrollY = useDraggedItemStore((state) => state.scrollY);
|
|
3235
3237
|
const zoneDepth = useMemo(() => getItemDepthFromNode({ id: parentZoneId }, tree.root), [tree, parentZoneId]);
|
|
3236
3238
|
const [fetchDomRect, setFetchDomRect] = useState(Date.now());
|
|
3239
|
+
const { zones, hoveringZone } = useZoneStore();
|
|
3240
|
+
const isHoveringZone = hoveringZone === zoneId;
|
|
3237
3241
|
useEffect(() => {
|
|
3238
3242
|
/**
|
|
3239
3243
|
* A bit hacky but we need to wait a very small amount
|
|
@@ -3252,16 +3256,27 @@ const Hitboxes = ({ zoneId, parentZoneId, enableRootHitboxes }) => {
|
|
|
3252
3256
|
return document.querySelector(`[${CTFL_ZONE_ID}="${zoneId}"]`)?.getBoundingClientRect();
|
|
3253
3257
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3254
3258
|
}, [zoneId, fetchDomRect]);
|
|
3255
|
-
|
|
3259
|
+
// Use the size of the cloned dragging element to offset the position of the hitboxes
|
|
3260
|
+
// So that when dragging causes a dropzone to expand, the hitboxes will be in the correct position
|
|
3261
|
+
const offsetRect = useMemo(() => {
|
|
3262
|
+
if (isEmptyZone || !isHoveringZone)
|
|
3263
|
+
return;
|
|
3264
|
+
return document.querySelector(`[${CTFL_DRAGGING_ELEMENT}]`)?.getBoundingClientRect();
|
|
3265
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
3266
|
+
}, [isEmptyZone, isHoveringZone, fetchDomRect]);
|
|
3256
3267
|
const zoneDirection = zones[parentZoneId]?.direction || 'vertical';
|
|
3257
3268
|
const isVertical = zoneDirection === 'vertical';
|
|
3258
3269
|
const isRoot = parentZoneId === ROOT_ID;
|
|
3259
|
-
const
|
|
3260
|
-
|
|
3270
|
+
const getStyles = useCallback((direction) => getHitboxStyles({
|
|
3271
|
+
direction,
|
|
3272
|
+
zoneDepth,
|
|
3273
|
+
domRect,
|
|
3274
|
+
scrollY,
|
|
3275
|
+
offsetRect,
|
|
3276
|
+
}), [zoneDepth, domRect, scrollY, offsetRect]);
|
|
3261
3277
|
const ActiveHitboxes = (React.createElement(React.Fragment, null,
|
|
3262
3278
|
React.createElement("div", { "data-ctfl-zone-id": zoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.SELF_VERTICAL : HitboxDirection.SELF_HORIZONTAL) }),
|
|
3263
|
-
|
|
3264
|
-
!isRoot && (React.createElement(React.Fragment, null,
|
|
3279
|
+
isRoot ? (React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(HitboxDirection.BOTTOM) })) : (React.createElement(React.Fragment, null,
|
|
3265
3280
|
React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.TOP : HitboxDirection.LEFT) }),
|
|
3266
3281
|
React.createElement("div", { "data-ctfl-zone-id": parentZoneId, className: styles$2.hitbox, style: getStyles(isVertical ? HitboxDirection.BOTTOM : HitboxDirection.RIGHT) })))));
|
|
3267
3282
|
if (!hitboxContainer) {
|
|
@@ -3270,7 +3285,7 @@ const Hitboxes = ({ zoneId, parentZoneId, enableRootHitboxes }) => {
|
|
|
3270
3285
|
return createPortal(ActiveHitboxes, hitboxContainer);
|
|
3271
3286
|
};
|
|
3272
3287
|
|
|
3273
|
-
const EditorBlock = ({ node: rawNode, resolveDesignValue, renderDropzone,
|
|
3288
|
+
const EditorBlock = ({ node: rawNode, resolveDesignValue, renderDropzone, index, zoneId, userIsDragging, placeholder, }) => {
|
|
3274
3289
|
const setSelectedNodeId = useEditorStore((state) => state.setSelectedNodeId);
|
|
3275
3290
|
const selectedNodeId = useEditorStore((state) => state.selectedNodeId);
|
|
3276
3291
|
const { node, componentId, wrapperProps, definition, elementToRender } = useComponent({
|
|
@@ -3285,8 +3300,7 @@ const EditorBlock = ({ node: rawNode, resolveDesignValue, renderDropzone, draggi
|
|
|
3285
3300
|
const isAssemblyBlock = node.type === ASSEMBLY_BLOCK_NODE_TYPE;
|
|
3286
3301
|
const isAssembly = node.type === ASSEMBLY_NODE_TYPE;
|
|
3287
3302
|
const isStructureComponent = isContentfulStructureComponent(node.data.blockId);
|
|
3288
|
-
const
|
|
3289
|
-
const enableRootHitboxes = isRootComponent && !!draggingNewComponent;
|
|
3303
|
+
const isEmptyZone = !node.children.length;
|
|
3290
3304
|
const onClick = (e) => {
|
|
3291
3305
|
e.stopPropagation();
|
|
3292
3306
|
if (!userIsDragging) {
|
|
@@ -3307,11 +3321,11 @@ const EditorBlock = ({ node: rawNode, resolveDesignValue, renderDropzone, draggi
|
|
|
3307
3321
|
if (node.data.blockId === CONTENTFUL_COMPONENTS.singleColumn.id) {
|
|
3308
3322
|
return (React.createElement(React.Fragment, null,
|
|
3309
3323
|
React.createElement(DraggableChildComponent, { elementToRender: elementToRender, id: componentId, index: index, isAssemblyBlock: isAssemblyBlock, isDragDisabled: isSingleColumn, isSelected: selectedNodeId === componentId, userIsDragging: userIsDragging, isContainer: isContainer, blockId: node.data.blockId, coordinates: coordinates, wrapperProps: wrapperProps, onClick: onClick, definition: definition }),
|
|
3310
|
-
isStructureComponent && !isSingleColumn && userIsDragging && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId,
|
|
3324
|
+
isStructureComponent && !isSingleColumn && userIsDragging && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId, isEmptyZone: isEmptyZone }))));
|
|
3311
3325
|
}
|
|
3312
3326
|
return (React.createElement(DraggableComponent, { placeholder: placeholder, definition: definition, id: componentId, index: index, isAssemblyBlock: isAssemblyBlock, isDragDisabled: isAssemblyBlock, isSelected: selectedNodeId === componentId, userIsDragging: userIsDragging, isContainer: isContainer, blockId: node.data.blockId, coordinates: coordinates, wrapperProps: wrapperProps, onClick: onClick },
|
|
3313
3327
|
elementToRender(),
|
|
3314
|
-
isStructureComponent && !isSingleColumn && userIsDragging && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId,
|
|
3328
|
+
isStructureComponent && !isSingleColumn && userIsDragging && (React.createElement(Hitboxes, { parentZoneId: zoneId, zoneId: componentId, isEmptyZone: isEmptyZone }))));
|
|
3315
3329
|
};
|
|
3316
3330
|
|
|
3317
3331
|
var css_248z$1 = ".EmptyContainer-module_container__XPH5b {\n height: 200px;\n display: flex;\n width: 100%;\n position: absolute;\n align-items: center;\n justify-content: center;\n flex-direction: row;\n transition: all 0.2s;\n color: var(--exp-builder-gray400);\n font-size: var(--exp-builder-font-size-l);\n font-family: var(--exp-builder-font-stack-primary);\n outline: 2px dashed var(--exp-builder-gray400);\n outline-offset: -2px;\n}\n\n.EmptyContainer-module_highlight__lcICy:hover {\n outline: 2px dashed var(--exp-builder-blue500);\n background-color: rgba(var(--exp-builder-blue100-rgb), 0.5);\n cursor: grabbing;\n}\n\n.EmptyContainer-module_icon__82-2O rect {\n fill: var(--exp-builder-gray400);\n}\n\n.EmptyContainer-module_label__4TxRa {\n margin-left: var(--exp-builder-spacing-s);\n}\n";
|
|
@@ -3331,27 +3345,6 @@ const EmptyContainer = ({ isDragging }) => {
|
|
|
3331
3345
|
React.createElement("span", { className: styles$1.label }, "Add components to begin")));
|
|
3332
3346
|
};
|
|
3333
3347
|
|
|
3334
|
-
const getZoneParents = (zoneId) => {
|
|
3335
|
-
const element = document.querySelector(`[data-rfd-droppable-id='${zoneId}']`);
|
|
3336
|
-
if (!element) {
|
|
3337
|
-
return [];
|
|
3338
|
-
}
|
|
3339
|
-
function getZonesToRoot(element, parentIds = []) {
|
|
3340
|
-
if (!element) {
|
|
3341
|
-
return parentIds;
|
|
3342
|
-
}
|
|
3343
|
-
const attribute = element.getAttribute('data-rfd-droppable-id');
|
|
3344
|
-
if (attribute === ROOT_ID) {
|
|
3345
|
-
return parentIds;
|
|
3346
|
-
}
|
|
3347
|
-
if (attribute) {
|
|
3348
|
-
parentIds.push(attribute);
|
|
3349
|
-
}
|
|
3350
|
-
return getZonesToRoot(element.parentElement, parentIds);
|
|
3351
|
-
}
|
|
3352
|
-
return getZonesToRoot(element);
|
|
3353
|
-
};
|
|
3354
|
-
|
|
3355
3348
|
const useDropzoneDirection = ({ resolveDesignValue, node, zoneId }) => {
|
|
3356
3349
|
const zone = useZoneStore((state) => state.zones);
|
|
3357
3350
|
const upsertZone = useZoneStore((state) => state.upsertZone);
|
|
@@ -3434,8 +3427,12 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
|
|
|
3434
3427
|
const tree = useTreeStore((state) => state.tree);
|
|
3435
3428
|
const content = node?.children || tree.root?.children || [];
|
|
3436
3429
|
const direction = useDropzoneDirection({ resolveDesignValue, node, zoneId });
|
|
3437
|
-
const draggedSourceId = draggedItem && draggedItem.source.droppableId;
|
|
3438
3430
|
const draggedDestinationId = draggedItem && draggedItem.destination?.droppableId;
|
|
3431
|
+
const draggedBlockId = useMemo(() => {
|
|
3432
|
+
if (!draggedItem)
|
|
3433
|
+
return;
|
|
3434
|
+
return getItem({ id: draggedItem.draggableId }, tree)?.data.blockId;
|
|
3435
|
+
}, [draggedItem, tree]);
|
|
3439
3436
|
const isDraggingNewComponent = !!newComponentId;
|
|
3440
3437
|
const isHoveringZone = hoveringZone === zoneId;
|
|
3441
3438
|
const isRootZone = zoneId === ROOT_ID;
|
|
@@ -3449,43 +3446,33 @@ function Dropzone({ node, zoneId, resolveDesignValue, className, WrapperComponen
|
|
|
3449
3446
|
const renderClonedDropzone = useCallback((node, props) => {
|
|
3450
3447
|
return (React.createElement(DropzoneClone, { zoneId: node.data.id, node: node, resolveDesignValue: resolveDesignValue, renderDropzone: renderClonedDropzone, ...props }));
|
|
3451
3448
|
}, [resolveDesignValue]);
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
}
|
|
3455
|
-
/**
|
|
3456
|
-
* The Rules of Dropzones
|
|
3457
|
-
*
|
|
3458
|
-
* 1. A dropzone is disabled unless the mouse is hovering over it
|
|
3459
|
-
*
|
|
3460
|
-
* 2. Dragging a new component onto the canvas has no addtional rules
|
|
3461
|
-
* besides rule #1
|
|
3462
|
-
*
|
|
3463
|
-
* 3. Dragging a component that is a direct descendant of the root
|
|
3464
|
-
* (parentId === ROOT_ID) then only the Root Dropzone is enabled
|
|
3465
|
-
*
|
|
3466
|
-
* 4. Dragging a nested component (parentId !== ROOT_ID) then the Root
|
|
3467
|
-
* Dropzone is disabled, all other Dropzones follow rule #1
|
|
3468
|
-
*
|
|
3469
|
-
* 5. Assemblies and the SingleColumn component are always disabled
|
|
3470
|
-
*
|
|
3471
|
-
*/
|
|
3472
|
-
const isDropzoneEnabled = () => {
|
|
3449
|
+
const isDropzoneEnabled = useMemo(() => {
|
|
3450
|
+
// Disable dropzone for Columns component
|
|
3473
3451
|
if (node?.data.blockId === CONTENTFUL_COMPONENTS.columns.id) {
|
|
3474
3452
|
return false;
|
|
3475
3453
|
}
|
|
3454
|
+
// Disable dropzone for Assembly
|
|
3476
3455
|
if (isAssembly) {
|
|
3477
3456
|
return false;
|
|
3478
3457
|
}
|
|
3479
|
-
if
|
|
3480
|
-
|
|
3458
|
+
// Enable dropzone for the non-root hovered zones if component is not allowed on root
|
|
3459
|
+
if (!isDraggingNewComponent && !isComponentAllowedOnRoot(draggedBlockId)) {
|
|
3460
|
+
return isHoveringZone && !isRootZone;
|
|
3481
3461
|
}
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3462
|
+
// Enable dropzone for the hovered zone only
|
|
3463
|
+
return isHoveringZone;
|
|
3464
|
+
}, [
|
|
3465
|
+
node?.data.blockId,
|
|
3466
|
+
isAssembly,
|
|
3467
|
+
isHoveringZone,
|
|
3468
|
+
isRootZone,
|
|
3469
|
+
isDraggingNewComponent,
|
|
3470
|
+
draggedBlockId,
|
|
3471
|
+
]);
|
|
3472
|
+
if (!resolveDesignValue) {
|
|
3473
|
+
return null;
|
|
3474
|
+
}
|
|
3475
|
+
return (React.createElement(Droppable, { droppableId: zoneId, direction: direction, isDropDisabled: !isDropzoneEnabled, renderClone: (provided, snapshot, rubic) => (React.createElement(EditorBlockClone, { node: content[rubic.source.index], resolveDesignValue: resolveDesignValue, provided: provided, snapshot: snapshot, renderDropzone: renderClonedDropzone })) }, (provided, snapshot) => {
|
|
3489
3476
|
return (React.createElement(WrapperComponent, { ...(provided || { droppableProps: {} }).droppableProps, ref: provided?.innerRef, id: zoneId, "data-ctfl-zone-id": zoneId, className: classNames(styles$2.container, {
|
|
3490
3477
|
[styles$2.isEmptyCanvas]: isEmptyCanvas,
|
|
3491
3478
|
[styles$2.isDragging]: userIsDragging && !isAssembly,
|