@atlaskit/editor-plugin-block-controls
Version:
Block controls plugin for @atlaskit/editor-core
282 lines (280 loc) • 15.1 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
/* eslint-disable @atlaskit/design-system/consistent-css-prop-usage */
/* eslint-disable @atlaskit/ui-styling-standard/no-unsafe-values */
/**
* @jsxRuntime classic
* @jsx jsx
*/
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
// eslint-disable-next-line @atlaskit/ui-styling-standard/use-compiled, @typescript-eslint/consistent-type-imports
import { css, jsx } from '@emotion/react';
import { akEditorBreakoutPadding } from '@atlaskit/editor-shared-styles';
import { DropIndicator } from '@atlaskit/pragmatic-drag-and-drop-react-drop-indicator/box';
import { dropTargetForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { getNodeAnchor } from '../pm-plugins/decorations-common';
import { useActiveAnchorTracker } from '../pm-plugins/utils/active-anchor-tracker';
import { isAnchorSupported } from '../pm-plugins/utils/anchor-utils';
import { getInsertLayoutStep, updateSelection } from '../pm-plugins/utils/update-selection';
var HOVER_ZONE_WIDTH = '--editor-blocks-inline-hover-zone-width';
var HOVER_ZONE_HEIGHT = '--editor-blocks-inline-hover-zone-height';
var HOVER_ZONE_TOP = '--editor-blocks-inline-hover-zone-top';
var HOVER_ZONE_BOTTOM = '--editor-blocks-inline-hover-zone-bottom';
var HOVER_ZONE_ANCHOR_NAME = '--editor-blocks-inline-hover-zone-anchor-name';
var hoverZoneCommonStyle = css({
position: 'absolute',
// above the top and bottom drop zone as block hover zone
zIndex: 120,
positionAnchor: "var(".concat(HOVER_ZONE_ANCHOR_NAME, ")"),
minWidth: "var(--ds-space-100, 8px)",
left: 0,
right: 0,
width: "var(".concat(HOVER_ZONE_WIDTH, ")"),
height: "var(".concat(HOVER_ZONE_HEIGHT, ")")
});
var leftHoverZoneStyle = css({
right: "unset",
top: "var(".concat(HOVER_ZONE_TOP, ")"),
bottom: 'unset'
});
var rightHoverZoneStyle = css({
left: "unset",
top: 'unset',
bottom: "var(".concat(HOVER_ZONE_BOTTOM, ")")
});
// gap between node boundary and drop indicator/drop zone
var GAP = 4;
var dropTargetLayoutHintStyle = css({
height: '100%',
position: 'absolute',
borderRight: "var(--ds-border-width, 1px)".concat(" dashed ", "var(--ds-border-focused, #4688EC)"),
width: 0,
left: 0
});
var dropTargetLayoutHintLeftStyle = css({
left: 'unset',
right: 0
});
var defaultNodeDimension = {
width: '0',
height: '0',
top: 'unset',
bottom: 'unset'
};
var getWidthOffset = function getWidthOffset(node, width, position) {
if (['mediaSingle', 'table', 'embedCard'].includes(node.type.name) ||
// block card (without datasource) is positioned left-aligned, hence share the same logic as align-start
node.type.name === 'blockCard' && !node.attrs.datasource) {
var isLeftPosition = position === 'left';
if (node.attrs.layout === 'align-start' || node.type.name === 'blockCard') {
return isLeftPosition ? "-0.5*(var(--ak-editor--line-length) - ".concat(width, ")") : "0.5*(var(--ak-editor--line-length) - ".concat(width, ")");
} else if ((node === null || node === void 0 ? void 0 : node.attrs.layout) === 'align-end') {
return isLeftPosition ? "0.5*(var(--ak-editor--line-length) - ".concat(width, ")") : "-0.5*(var(--ak-editor--line-length) - ".concat(width, ")");
}
}
if (node.type.name === 'bodiedExtension' || node.type.name === 'extension') {
return '-12px';
}
};
var TABLE_NUMBERED_COLUMN_WIDTH = 42;
export var InlineDropTarget = function InlineDropTarget(_ref) {
var api = _ref.api,
nextNode = _ref.nextNode,
position = _ref.position,
anchorRectCache = _ref.anchorRectCache,
getPos = _ref.getPos;
var ref = useRef(null);
var _useState = useState(false),
_useState2 = _slicedToArray(_useState, 2),
isDraggedOver = _useState2[0],
setIsDraggedOver = _useState2[1];
var anchorName = useMemo(function () {
if (expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true)) {
var _getPos;
return nextNode ? (api === null || api === void 0 ? void 0 : api.core.actions.getAnchorIdForNode(nextNode, (_getPos = getPos()) !== null && _getPos !== void 0 ? _getPos : -1)) || '' : '';
}
return nextNode ? getNodeAnchor(nextNode) : '';
}, [api, getPos, nextNode]);
var _useActiveAnchorTrack = useActiveAnchorTracker(anchorName),
_useActiveAnchorTrack2 = _slicedToArray(_useActiveAnchorTrack, 1),
isActiveAnchor = _useActiveAnchorTrack2[0];
var isLeftPosition = position === 'left';
var nodeDimension = useMemo(function () {
if (!nextNode) {
return defaultNodeDimension;
}
var nextNodePos = getPos();
var innerContainerWidth = null;
var targetAnchorName = anchorName;
if (['blockCard', 'embedCard', 'extension'].includes(nextNode.type.name)) {
if (nextNode.attrs.layout === 'wide') {
innerContainerWidth = "max(var(--ak-editor--legacy-breakout-wide-layout-width), var(--ak-editor--line-length))";
} else if (nextNode.attrs.layout === 'full-width') {
innerContainerWidth = "min(calc(100cqw - ".concat(akEditorBreakoutPadding, "px), 1800px)");
}
if (nextNode.type.name === 'blockCard' && !nextNode.attrs.layout && nextNode.attrs.datasource) {
// block card with sourceNode and without layout has different width in full-width vs fixed-width editor
// Hence we need to set it based on editor mode
innerContainerWidth = 'var(--ak-editor-block-card-width)';
}
if (nextNode.type.name === 'embedCard' && ['center', 'align-start', 'align-end'].includes(nextNode.attrs.layout)) {
var percentageWidth = ((parseFloat(nextNode.attrs.width) || 100) / 100).toFixed(2);
innerContainerWidth = "calc(var(--ak-editor--line-length) * ".concat(percentageWidth, ")");
}
} else if (nextNode.type.name === 'table' && nextNode.firstChild) {
var tableWidthAnchor = expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true) ? typeof nextNodePos === 'number' ? (api === null || api === void 0 ? void 0 : api.core.actions.getAnchorIdForNode(nextNode.firstChild, nextNodePos + 1)) || '' : '' : getNodeAnchor(nextNode.firstChild);
var isNumberColumnEnabled = Boolean(nextNode.attrs.isNumberColumnEnabled);
if (isAnchorSupported()) {
innerContainerWidth = isNumberColumnEnabled ? "calc(anchor-size(".concat(tableWidthAnchor, " width) + ").concat(TABLE_NUMBERED_COLUMN_WIDTH, "px)") : "anchor-size(".concat(tableWidthAnchor, " width)");
} else {
var _anchorRectCache$getR;
innerContainerWidth = "".concat(((anchorRectCache === null || anchorRectCache === void 0 || (_anchorRectCache$getR = anchorRectCache.getRect(tableWidthAnchor)) === null || _anchorRectCache$getR === void 0 ? void 0 : _anchorRectCache$getR.width) || 0) + TABLE_NUMBERED_COLUMN_WIDTH, "px");
}
if (nextNode.attrs.width) {
// when the table has horizontal scroll
innerContainerWidth = "min(".concat(nextNode.attrs.width, "px, ").concat(innerContainerWidth, ")");
}
} else if (nextNode.type.name === 'mediaSingle' && nextNode.firstChild) {
if (expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true)) {
var _nextNode$firstChild;
// check pos is a number
if (typeof nextNodePos === 'number' && ((_nextNode$firstChild = nextNode.firstChild) === null || _nextNode$firstChild === void 0 ? void 0 : _nextNode$firstChild.type.name) === 'media') {
targetAnchorName = (api === null || api === void 0 ? void 0 : api.core.actions.getAnchorIdForNode(nextNode.firstChild, nextNodePos + 1)) || '';
}
} else {
targetAnchorName = getNodeAnchor(nextNode.firstChild);
}
}
// Set the height target anchor name to the first or last column of the layout section so that it also works for stacked layout
var heightTargetAnchorName = targetAnchorName;
if (nextNode.type.name === 'layoutSection' && nextNode.firstChild && nextNode.lastChild) {
if (isLeftPosition) {
if (expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true)) {
if (typeof nextNodePos === 'number') {
heightTargetAnchorName = (api === null || api === void 0 ? void 0 : api.core.actions.getAnchorIdForNode(nextNode.firstChild, nextNodePos + 1)) || '';
} else {
heightTargetAnchorName = '';
}
} else {
heightTargetAnchorName = getNodeAnchor(nextNode.firstChild);
}
} else {
if (expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true)) {
if (typeof nextNodePos === 'number') {
var lastNodeStartPos = nextNode.content.size - nextNode.lastChild.nodeSize;
heightTargetAnchorName = (api === null || api === void 0 ? void 0 : api.core.actions.getAnchorIdForNode(nextNode.lastChild, lastNodeStartPos + 1)) || '';
} else {
heightTargetAnchorName = '';
}
} else {
heightTargetAnchorName = getNodeAnchor(nextNode.lastChild);
}
}
}
if (isAnchorSupported()) {
var width = innerContainerWidth || "anchor-size(".concat(targetAnchorName, " width)");
var height = "anchor-size(".concat(heightTargetAnchorName, " height)");
return {
width: width,
height: height,
top: 'anchor(top)',
bottom: 'anchor(bottom)',
widthOffset: getWidthOffset(nextNode, width, position)
};
}
if (anchorRectCache) {
var nodeRect = anchorRectCache.getRect(targetAnchorName);
var _width = innerContainerWidth || "".concat((nodeRect === null || nodeRect === void 0 ? void 0 : nodeRect.width) || 0, "px");
var top = nodeRect !== null && nodeRect !== void 0 && nodeRect.top ? "".concat(nodeRect === null || nodeRect === void 0 ? void 0 : nodeRect.top, "px") : 'unset';
var bottom = "100% - ".concat((nodeRect === null || nodeRect === void 0 ? void 0 : nodeRect.bottom) || 0, "px + ").concat(GAP, "px");
var _height = "".concat((nodeRect === null || nodeRect === void 0 ? void 0 : nodeRect.height) || 0, "px");
if (heightTargetAnchorName !== targetAnchorName) {
var nodeHeightRect = anchorRectCache.getRect(heightTargetAnchorName);
_height = "".concat((nodeHeightRect === null || nodeHeightRect === void 0 ? void 0 : nodeHeightRect.height) || 0, "px + ").concat(GAP, "px");
}
return {
width: _width,
height: _height,
top: top,
bottom: bottom,
widthOffset: getWidthOffset(nextNode, _width, position)
};
}
return defaultNodeDimension;
}, [nextNode, anchorName, anchorRectCache, getPos, api, isLeftPosition, position]);
var onDrop = useCallback(function () {
var _api$blockControls;
var _ref2 = (api === null || api === void 0 || (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.currentState()) || {},
activeNode = _ref2.activeNode;
if (!activeNode) {
return;
}
var toPos = getPos();
var mappedTo;
if (activeNode && toPos !== undefined) {
var _api$core, _api$core2;
var start = activeNode.pos;
var moveToEnd = position === 'right';
api === null || api === void 0 || (_api$core = api.core) === null || _api$core === void 0 || _api$core.actions.execute(function (_ref3) {
var _api$blockControls2;
var tr = _ref3.tr;
api === null || api === void 0 || (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 || (_api$blockControls2 = _api$blockControls2.commands) === null || _api$blockControls2 === void 0 || _api$blockControls2.moveToLayout(start, toPos, {
moveToEnd: moveToEnd
})({
tr: tr
});
var insertLayoutStep = getInsertLayoutStep(tr);
mappedTo = insertLayoutStep === null || insertLayoutStep === void 0 ? void 0 : insertLayoutStep.from;
return tr;
});
api === null || api === void 0 || (_api$core2 = api.core) === null || _api$core2 === void 0 || _api$core2.actions.execute(function (_ref4) {
var tr = _ref4.tr;
if (mappedTo !== undefined) {
updateSelection(tr, mappedTo, moveToEnd);
}
return tr;
});
}
}, [api, getPos, position]);
var hoverZoneRectStyle = useMemo(function () {
var isLayoutNode = (nextNode === null || nextNode === void 0 ? void 0 : nextNode.type.name) === 'layoutSection';
var layoutAdjustment = isLayoutNode ? {
width: 11,
height: 4,
top: 6,
bottom: 2
} : undefined;
return _defineProperty(_defineProperty(_defineProperty(_defineProperty(_defineProperty({}, HOVER_ZONE_WIDTH, nodeDimension.widthOffset ? "calc((100% - ".concat(nodeDimension.width, ")/2 - ").concat(GAP, "px + ").concat(nodeDimension.widthOffset, " - ").concat((layoutAdjustment === null || layoutAdjustment === void 0 ? void 0 : layoutAdjustment.width) || 0, "px)") : "calc((100% - ".concat(nodeDimension.width, ")/2 - ").concat(GAP, "px - ").concat((layoutAdjustment === null || layoutAdjustment === void 0 ? void 0 : layoutAdjustment.width) || 0, "px)")), HOVER_ZONE_HEIGHT, "calc(".concat(nodeDimension.height, " + ").concat((layoutAdjustment === null || layoutAdjustment === void 0 ? void 0 : layoutAdjustment.height) || 0, "px)")), HOVER_ZONE_TOP, "calc(".concat(nodeDimension.top, " + ").concat((layoutAdjustment === null || layoutAdjustment === void 0 ? void 0 : layoutAdjustment.top) || 0, "px)")), HOVER_ZONE_BOTTOM, "calc(".concat(nodeDimension.bottom, " - ").concat((layoutAdjustment === null || layoutAdjustment === void 0 ? void 0 : layoutAdjustment.bottom) || 0, "px)")), HOVER_ZONE_ANCHOR_NAME, anchorName);
}, [nextNode === null || nextNode === void 0 ? void 0 : nextNode.type.name, nodeDimension, anchorName]);
var dropIndicatorPos = useMemo(function () {
return isLeftPosition ? 'right' : 'left';
}, [isLeftPosition]);
useEffect(function () {
if (ref.current) {
return dropTargetForElements({
element: ref.current,
onDragEnter: function onDragEnter() {
setIsDraggedOver(true);
},
onDragLeave: function onDragLeave() {
setIsDraggedOver(false);
},
onDrop: onDrop
});
}
}, [onDrop, setIsDraggedOver]);
return jsx("div", {
ref: ref,
"data-testid": "drop-target-hover-zone-".concat(position),
css: [hoverZoneCommonStyle, isLeftPosition ? leftHoverZoneStyle : rightHoverZoneStyle]
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
,
style: hoverZoneRectStyle
}, isDraggedOver ? jsx(DropIndicator, {
edge: dropIndicatorPos
}) : isActiveAnchor && jsx("div", {
"data-testid": "block-ctrl-drop-hint",
css: [dropTargetLayoutHintStyle, isLeftPosition && dropTargetLayoutHintLeftStyle]
}));
};