UNPKG

@atlaskit/editor-plugin-block-controls

Version:

Block controls plugin for @atlaskit/editor-core

246 lines (244 loc) 10.8 kB
/** * @jsxRuntime classic * @jsx jsx */ import { useCallback, useEffect, useLayoutEffect, 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 { layoutBreakpointWidth } 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'; import { getAnchorAttrName } from './utils/dom-attr-name'; // 8px gap + 16px on left and right const DROP_TARGET_LAYOUT_DROP_ZONE_WIDTH = 40; const dropTargetLayoutStyle = css({ height: '100%', width: `${DROP_TARGET_LAYOUT_DROP_ZONE_WIDTH}px`, transform: 'translateX(-50%)', zIndex: 120, position: 'relative', display: 'flex', justifyContent: 'center' }); const dropTargetLayoutHintStyle = css({ height: '100%', position: 'relative', borderRight: `${"var(--ds-border-width, 1px)"} dashed ${"var(--ds-border-focused, #4688EC)"}`, width: 0 }); export const DropTargetLayout = props => { var _ref$current, _ref$current$parentEl, _ref$current$parentEl2, _api$blockControls; const { api, getPos, parent, anchorRectCache } = props; const ref = useRef(null); const [isDraggedOver, setIsDraggedOver] = useState(false); const anchorName = getNodeAnchor(parent); const nextNodeAnchorName = (_ref$current = ref.current) === null || _ref$current === void 0 ? void 0 : (_ref$current$parentEl = _ref$current.parentElement) === null || _ref$current$parentEl === void 0 ? void 0 : (_ref$current$parentEl2 = _ref$current$parentEl.nextElementSibling) === null || _ref$current$parentEl2 === void 0 ? void 0 : _ref$current$parentEl2.getAttribute(getAnchorAttrName()); let height = '100%'; if (nextNodeAnchorName) { if (isAnchorSupported()) { height = `anchor-size(${nextNodeAnchorName} height)`; } else if (anchorRectCache) { const layoutColumnRect = anchorRectCache.getRect(nextNodeAnchorName); height = `${(layoutColumnRect === null || layoutColumnRect === void 0 ? void 0 : layoutColumnRect.height) || 0}px`; } } const dropTargetStackLayoutHintStyle = css({ // jest warning: JSDOM version (22) doesn't support the new @container CSS rule // eslint-disable-next-line @atlaskit/ui-styling-standard/no-container-queries, @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/ui-styling-standard/no-imported-style-values [`@container layout-area (max-width:${layoutBreakpointWidth.MEDIUM - 1}px)`]: { // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values height, marginTop: `${"var(--ds-space-050, 4px)"}` } }); const [isActiveAnchor] = useActiveAnchorTracker(anchorName); const { activeNode } = (api === null || api === void 0 ? void 0 : (_api$blockControls = api.blockControls) === null || _api$blockControls === void 0 ? void 0 : _api$blockControls.sharedState.currentState()) || {}; const onDrop = useCallback(() => { if (!activeNode) { return; } const to = getPos(); let mappedTo; if (to !== undefined) { var _api$core, _api$core2; const { pos: from } = activeNode; api === null || api === void 0 ? void 0 : (_api$core = api.core) === null || _api$core === void 0 ? void 0 : _api$core.actions.execute(({ tr }) => { var _api$blockControls2, _api$blockControls2$c; api === null || api === void 0 ? void 0 : (_api$blockControls2 = api.blockControls) === null || _api$blockControls2 === void 0 ? void 0 : (_api$blockControls2$c = _api$blockControls2.commands) === null || _api$blockControls2$c === void 0 ? void 0 : _api$blockControls2$c.moveToLayout(from, to)({ tr }); const insertColumnStep = getInsertLayoutStep(tr); mappedTo = insertColumnStep === null || insertColumnStep === void 0 ? void 0 : insertColumnStep.from; return tr; }); api === null || api === void 0 ? void 0 : (_api$core2 = api.core) === null || _api$core2 === void 0 ? void 0 : _api$core2.actions.execute(({ tr }) => { if (mappedTo !== undefined) { updateSelection(tr, mappedTo); } return tr; }); } }, [api, getPos, activeNode]); useEffect(() => { if (ref.current) { return dropTargetForElements({ element: ref.current, onDragEnter: () => { setIsDraggedOver(true); }, onDragLeave: () => { setIsDraggedOver(false); }, onDrop }); } }, [onDrop]); if ((activeNode === null || activeNode === void 0 ? void 0 : activeNode.nodeType) === 'layoutSection') { return null; } return jsx("div", { ref: ref // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage , css: [dropTargetLayoutStyle, dropTargetStackLayoutHintStyle], "data-testid": "block-ctrl-drop-indicator" }, isDraggedOver ? jsx(DropIndicator, { edge: "right", gap: `-${DROP_TARGET_LAYOUT_DROP_ZONE_WIDTH}px` }) : (isActiveAnchor || expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true)) && jsx("div", { "data-testid": "block-ctrl-drop-hint", css: dropTargetLayoutHintStyle })); }; export const DropTargetLayoutNativeAnchorSupport = props => { var _api$blockControls3; const { api, getPos, parent, anchorRectCache } = props; const ref = useRef(null); const [isDraggedOver, setIsDraggedOver] = useState(false); const anchorName = getNodeAnchor(parent); const [nextNodeAnchorName, setNextNodeAnchorName] = useState(null); const readNextNodeAnchor = useCallback(() => { var _ref$current2, _ref$current2$parentE, _nextElementSibling$g; const nextElementSibling = (_ref$current2 = ref.current) === null || _ref$current2 === void 0 ? void 0 : (_ref$current2$parentE = _ref$current2.parentElement) === null || _ref$current2$parentE === void 0 ? void 0 : _ref$current2$parentE.nextElementSibling; const attrName = getAnchorAttrName(); const nextAnchorName = (_nextElementSibling$g = nextElementSibling === null || nextElementSibling === void 0 ? void 0 : nextElementSibling.getAttribute(attrName)) !== null && _nextElementSibling$g !== void 0 ? _nextElementSibling$g : null; setNextNodeAnchorName(prev => prev === nextAnchorName ? prev : nextAnchorName); }, []); const height = useMemo(() => { if (nextNodeAnchorName) { if (isAnchorSupported()) { return `anchor-size(${nextNodeAnchorName} height)`; } else if (anchorRectCache) { const layoutColumnRect = anchorRectCache.getRect(nextNodeAnchorName); return `${(layoutColumnRect === null || layoutColumnRect === void 0 ? void 0 : layoutColumnRect.height) || 0}px`; } } // Stacked mode fallback: minimal height to avoid oversized hint on first render return '0px'; }, [nextNodeAnchorName, anchorRectCache]); useLayoutEffect(() => { const raf = requestAnimationFrame(() => { readNextNodeAnchor(); }); return () => cancelAnimationFrame(raf); }, [readNextNodeAnchor]); const dropTargetStackLayoutHintStyle = css({ // jest warning: JSDOM version (22) doesn't support the new @container CSS rule // eslint-disable-next-line @atlaskit/ui-styling-standard/no-container-queries, @atlaskit/ui-styling-standard/no-unsafe-values, @atlaskit/ui-styling-standard/no-imported-style-values [`@container layout-area (max-width:${layoutBreakpointWidth.MEDIUM - 1}px)`]: { // eslint-disable-next-line @atlaskit/ui-styling-standard/no-unsafe-values height, marginTop: `${"var(--ds-space-050, 4px)"}` } }); const [isActiveAnchor] = useActiveAnchorTracker(anchorName); const { activeNode } = (api === null || api === void 0 ? void 0 : (_api$blockControls3 = api.blockControls) === null || _api$blockControls3 === void 0 ? void 0 : _api$blockControls3.sharedState.currentState()) || {}; const onDrop = useCallback(() => { if (!activeNode) { return; } const to = getPos(); let mappedTo; if (to !== undefined) { var _api$core3, _api$core4; const { pos: from } = activeNode; api === null || api === void 0 ? void 0 : (_api$core3 = api.core) === null || _api$core3 === void 0 ? void 0 : _api$core3.actions.execute(({ tr }) => { var _api$blockControls4, _api$blockControls4$c; api === null || api === void 0 ? void 0 : (_api$blockControls4 = api.blockControls) === null || _api$blockControls4 === void 0 ? void 0 : (_api$blockControls4$c = _api$blockControls4.commands) === null || _api$blockControls4$c === void 0 ? void 0 : _api$blockControls4$c.moveToLayout(from, to)({ tr }); const insertColumnStep = getInsertLayoutStep(tr); mappedTo = insertColumnStep === null || insertColumnStep === void 0 ? void 0 : insertColumnStep.from; return tr; }); api === null || api === void 0 ? void 0 : (_api$core4 = api.core) === null || _api$core4 === void 0 ? void 0 : _api$core4.actions.execute(({ tr }) => { if (mappedTo !== undefined) { updateSelection(tr, mappedTo); } return tr; }); } }, [api, getPos, activeNode]); useEffect(() => { if (ref.current) { return dropTargetForElements({ element: ref.current, onDragEnter: () => { setIsDraggedOver(true); readNextNodeAnchor(); }, onDragLeave: () => { setIsDraggedOver(false); }, onDrop }); } }, [onDrop, readNextNodeAnchor]); if ((activeNode === null || activeNode === void 0 ? void 0 : activeNode.nodeType) === 'layoutSection') { return null; } return jsx("div", { ref: ref // eslint-disable-next-line @atlaskit/design-system/consistent-css-prop-usage , css: [dropTargetLayoutStyle, dropTargetStackLayoutHintStyle], "data-testid": "block-ctrl-drop-indicator" }, isDraggedOver ? jsx(DropIndicator, { edge: "right", gap: `-${DROP_TARGET_LAYOUT_DROP_ZONE_WIDTH}px` }) : (isActiveAnchor || expValEquals('platform_editor_native_anchor_with_dnd', 'isEnabled', true)) && jsx("div", { "data-testid": "block-ctrl-drop-hint", css: dropTargetLayoutHintStyle })); };