@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
298 lines (295 loc) • 10.8 kB
JavaScript
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 };