UNPKG

@atlaskit/editor-plugin-block-controls

Version:

Block controls plugin for @atlaskit/editor-core

282 lines (280 loc) 15.1 kB
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] })); };