UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

298 lines (295 loc) • 10.8 kB
import _slicedToArray from "@babel/runtime/helpers/slicedToArray"; import React, { useCallback, useMemo, useRef, useState, useLayoutEffect } from 'react'; import { bind, bindAll } from 'bind-event-listener'; import { akEditorDefaultLayoutWidth, akEditorFullWidthLayoutWidth, akEditorGutterPadding, akEditorGutterPaddingDynamic } from '@atlaskit/editor-shared-styles'; import { fg } from '@atlaskit/platform-feature-flags'; import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments'; import { ACTION, ACTION_SUBJECT, EVENT_TYPE } from '../analytics'; import { LAYOUT_COLUMN_PADDING, LAYOUT_SECTION_MARGIN } from '../styles'; import { browser } from '../utils/browser'; import Resizer from './Resizer'; var breakoutSupportedNodes = ['layoutSection', 'expand', 'codeBlock']; var getHandleStyle = function getHandleStyle(node) { var layoutMarginOffset = fg('platform_editor_advanced_layouts_post_fix_patch_2') ? 12 : 8; switch (node) { case 'codeBlock': return { left: { left: '-12px' }, right: { right: '-12px' } }; // expand and layout section elements have a negative margin applied default: var handleOffset = editorExperiment('nested-dnd', true) ? LAYOUT_SECTION_MARGIN * 2 + layoutMarginOffset : LAYOUT_COLUMN_PADDING * 2; return { left: { left: "-".concat(handleOffset, "px"), height: 'calc(100% - 8px)', bottom: '0px', top: 'unset' }, right: { right: "-".concat(handleOffset, "px"), height: 'calc(100% - 8px)', bottom: '0px', top: 'unset' } }; } }; export var ignoreResizerMutations = function ignoreResizerMutations(mutation) { if (fg('platform_editor_breakoutresizer_remove_assertion')) { if (mutation.target instanceof Element) { return mutation.target.classList.contains('resizer-item') || mutation.type === 'attributes' && mutation.attributeName === 'style'; } return mutation.type === 'attributes' && mutation.attributeName === 'style'; } else { return ( // eslint-disable-next-line @atlaskit/editor/no-as-casting mutation.target.classList.contains('resizer-item') || mutation.type === 'attributes' && mutation.attributeName === 'style' ); } }; var resizingStyles = { left: '50%', transform: 'translateX(-50%)', display: 'grid' }; // Apply grid to stop drag handles rendering inside .resizer-item affecting its height var defaultStyles = { display: 'grid' }; var RESIZE_STEP_VALUE = 10; /** * BreakoutResizer is a common component used to resize nodes that support the 'Breakout' mark, so it requires * correct ADF support. * * use experiment platform_editor_advanced_layouts */ var BreakoutResizer = function BreakoutResizer(_ref) { var editorView = _ref.editorView, nodeType = _ref.nodeType, getPos = _ref.getPos, getRef = _ref.getRef, disabled = _ref.disabled, getEditorWidth = _ref.getEditorWidth, parentRef = _ref.parentRef, editorAnalyticsApi = _ref.editorAnalyticsApi, displayGapCursor = _ref.displayGapCursor; var _useState = useState({ minWidth: undefined, maxWidth: undefined, isResizing: false }), _useState2 = _slicedToArray(_useState, 2), _useState2$ = _useState2[0], minWidth = _useState2$.minWidth, maxWidth = _useState2$.maxWidth, isResizing = _useState2$.isResizing, setResizingState = _useState2[1]; var areResizeMetaKeysPressed = useRef(false); var resizerRef = useRef(null); // Relying on re-renders caused by selection changes inside/around node var isSelectionInNode = useMemo(function () { var pos = getPos(); if (pos === undefined) { return false; } var node = editorView.state.doc.nodeAt(pos); if (node === null) { return false; } var endPos = pos + node.nodeSize; var startPos = pos; var _editorView$state$sel = editorView.state.selection, $from = _editorView$state$sel.$from, $to = _editorView$state$sel.$to; return $from.pos >= startPos && endPos >= $to.pos; }, [editorView.state.doc, editorView.state.selection, getPos]); var handleResizeStart = useCallback(function () { var newMinWidth; var newMaxWidth; var widthState = getEditorWidth(); var dispatch = editorView.dispatch, state = editorView.state; displayGapCursor(false); if (widthState !== undefined && widthState.lineLength !== undefined && widthState.width !== undefined) { newMaxWidth = Math.min(widthState.width - akEditorGutterPaddingDynamic() * 2 - akEditorGutterPadding, akEditorFullWidthLayoutWidth); if (fg('platform_editor_advanced_layouts_post_fix_patch_2')) { newMinWidth = Math.min(widthState.lineLength, akEditorDefaultLayoutWidth, newMaxWidth); } else { newMinWidth = Math.min(widthState.lineLength, akEditorDefaultLayoutWidth); } } setResizingState({ isResizing: true, minWidth: newMinWidth, maxWidth: newMaxWidth }); dispatch(state.tr.setMeta('is-resizer-resizing', true)); }, [getEditorWidth, editorView, displayGapCursor]); var handleResizeStop = useCallback(function (originalState, delta) { var newWidth = originalState.width + delta.width; var pos = getPos(); if (pos === undefined) { return; } var state = editorView.state, dispatch = editorView.dispatch; var breakout = state.schema.marks.breakout; var node = state.doc.nodeAt(pos); var newTr = state.tr; if (node && breakoutSupportedNodes.includes(node.type.name)) { var newBreakoutWidth = Math.max(newWidth, akEditorDefaultLayoutWidth); newTr.setNodeMarkup(pos, node.type, node.attrs, [breakout.create({ width: newBreakoutWidth })]); var breakoutResizePayload = { action: ACTION.RESIZED, actionSubject: ACTION_SUBJECT.ELEMENT, eventType: EVENT_TYPE.TRACK, attributes: { nodeType: node.type.name, prevWidth: originalState.width, newWidth: newBreakoutWidth } }; editorAnalyticsApi === null || editorAnalyticsApi === void 0 || editorAnalyticsApi.attachAnalyticsEvent(breakoutResizePayload)(newTr); } newTr.setMeta('is-resizer-resizing', false).setMeta('scrollIntoView', false); displayGapCursor(true); dispatch(newTr); setResizingState({ isResizing: false, minWidth: undefined, maxWidth: undefined }); }, [getPos, editorView, displayGapCursor, editorAnalyticsApi]); var handleEscape = useCallback(function () { editorView === null || editorView === void 0 || editorView.focus(); }, [editorView]); var handleLayoutSizeChangeOnKeypress = useCallback(function (step) { if (!parentRef) { return; } var resizerItem = parentRef.closest('.resizer-item'); if (!(resizerItem instanceof HTMLElement)) { return; } var newWidth = resizerItem.offsetWidth + step; if (maxWidth && newWidth > maxWidth || minWidth && newWidth < minWidth) { return; } handleResizeStop({ width: resizerItem.offsetWidth, x: 0, y: 0, height: 0 }, { width: step, height: 0 }); }, [handleResizeStop, maxWidth, minWidth, parentRef]); var resizeHandleKeyDownHandler = useCallback(function (event) { var isBracketKey = event.code === 'BracketRight' || event.code === 'BracketLeft'; var metaKey = browser.mac ? event.metaKey : event.ctrlKey; if (event.altKey || metaKey || event.shiftKey) { areResizeMetaKeysPressed.current = true; } if (event.altKey && metaKey) { if (isBracketKey) { event.preventDefault(); handleLayoutSizeChangeOnKeypress(event.code === 'BracketRight' ? RESIZE_STEP_VALUE : -RESIZE_STEP_VALUE); } } else if (!areResizeMetaKeysPressed.current) { handleEscape(); } }, [handleEscape, handleLayoutSizeChangeOnKeypress]); var resizeHandleKeyUpHandler = useCallback(function (event) { if (event.altKey || event.metaKey) { areResizeMetaKeysPressed.current = false; } return; }, [areResizeMetaKeysPressed]); var resizerGlobalKeyDownHandler = useCallback(function (event) { if (!resizerRef.current) { return; } var resizeHandleThumbEl = resizerRef.current.getResizerThumbEl(); var metaKey = browser.mac ? event.metaKey : event.ctrlKey; var isTargetResizeHandle = event.target instanceof HTMLElement && event.target.classList.contains('resizer-handle-thumb'); if (event.altKey && event.shiftKey && metaKey && event.code === 'KeyR' || isTargetResizeHandle && (event.altKey || metaKey || event.shiftKey)) { event.preventDefault(); if (!resizeHandleThumbEl) { return; } resizeHandleThumbEl.focus(); resizeHandleThumbEl.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' }); } }, [resizerRef]); useLayoutEffect(function () { if (!resizerRef.current || !editorView) { return; } var resizeHandleThumbEl = resizerRef.current.getResizerThumbEl(); if (!resizeHandleThumbEl) { return; } if (fg('platform_editor_advanced_layouts_a11y')) { var editorViewDom = editorView.dom; var unbindEditorViewDom = bind(editorViewDom, { type: 'keydown', listener: resizerGlobalKeyDownHandler }); var unbindResizeHandle = bindAll(resizeHandleThumbEl, [{ type: 'keydown', listener: resizeHandleKeyDownHandler }, { type: 'keyup', listener: resizeHandleKeyUpHandler }]); return function () { unbindEditorViewDom(); unbindResizeHandle(); }; } }, [editorView, resizerGlobalKeyDownHandler, resizeHandleKeyDownHandler, resizeHandleKeyUpHandler]); if (disabled) { return /*#__PURE__*/React.createElement("div", { "data-testid": "breakout-resizer-editor-view-wrapper", ref: function ref(_ref2) { return getRef && getRef(_ref2); } }); } return /*#__PURE__*/React.createElement(Resizer, { ref: resizerRef, enable: { left: true, right: true }, handleStyles: getHandleStyle(nodeType), minWidth: minWidth, maxWidth: maxWidth // eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop , style: isResizing ? resizingStyles : defaultStyles, handleResizeStart: handleResizeStart, handleResizeStop: handleResizeStop, childrenDOMRef: getRef, resizeRatio: 2, isHandleVisible: isSelectionInNode, handleSize: "clamped", handleHighlight: "full-height", handlePositioning: "adjacent", handleAlignmentMethod: "sticky" }); }; export { BreakoutResizer };