@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
365 lines (362 loc) • 14.9 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ignoreResizerMutations = exports.BreakoutResizer = void 0;
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _react = _interopRequireWildcard(require("react"));
var _bindEventListener = require("bind-event-listener");
var _editorSharedStyles = require("@atlaskit/editor-shared-styles");
var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
var _expValEquals = require("@atlaskit/tmp-editor-statsig/exp-val-equals");
var _experiments = require("@atlaskit/tmp-editor-statsig/experiments");
var _analytics = require("../analytics");
var _styles = require("../styles");
var _browser = require("../utils/browser");
var _Resizer = _interopRequireDefault(require("./Resizer"));
var _ResizerBreakoutModeLabel = require("./ResizerBreakoutModeLabel");
var _useBreakoutGuidelines = require("./useBreakoutGuidelines");
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
var breakoutSupportedNodes = ['layoutSection', 'expand', 'codeBlock'];
var getHandleStyle = function getHandleStyle(node, hidden) {
var layoutMarginOffset = 12;
if (hidden) {
return {
left: {
display: 'none'
},
right: {
display: 'none'
}
};
}
switch (node) {
case 'codeBlock':
return {
left: {
left: '-12px'
},
right: {
right: '-12px'
}
};
// expand and layout section elements have a negative margin applied
default:
var handleOffset = (0, _platformFeatureFlags.fg)('platform_editor_nested_dnd_styles_changes') ? _styles.LAYOUT_SECTION_MARGIN * 2 + layoutMarginOffset : _styles.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'
}
};
}
};
var ignoreResizerMutations = exports.ignoreResizerMutations = function ignoreResizerMutations(mutation) {
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';
};
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;
var RESIZER_ENABLE_HANDLES = {
left: true,
right: true
};
/**
* 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
* @param root0
* @param root0.editorView
* @param root0.nodeType
* @param root0.getPos
* @param root0.getRef
* @param root0.disabled
* @param root0.getEditorWidth
* @param root0.parentRef
* @param root0.displayGuidelines
* @param root0.editorAnalyticsApi
* @param root0.displayGapCursor
* @param root0.onResizeStart
* @param root0.dynamicFullWidthGuidelineOffset
* @param root0.hidden Hide the resizer handles without outright unrendering them
* @returns BreakoutResizer component
* @example
*/
var BreakoutResizer = exports.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,
displayGuidelines = _ref.displayGuidelines,
editorAnalyticsApi = _ref.editorAnalyticsApi,
displayGapCursor = _ref.displayGapCursor,
onResizeStart = _ref.onResizeStart,
dynamicFullWidthGuidelineOffset = _ref.dynamicFullWidthGuidelineOffset,
_ref$hidden = _ref.hidden,
hidden = _ref$hidden === void 0 ? false : _ref$hidden;
var _useState = (0, _react.useState)({
minWidth: undefined,
maxWidth: undefined,
isResizing: false
}),
_useState2 = (0, _slicedToArray2.default)(_useState, 2),
_useState2$ = _useState2[0],
minWidth = _useState2$.minWidth,
maxWidth = _useState2$.maxWidth,
isResizing = _useState2$.isResizing,
setResizingState = _useState2[1];
var areResizeMetaKeysPressed = (0, _react.useRef)(false);
var resizerRef = (0, _react.useRef)(null);
var _useBreakoutGuideline = (0, _useBreakoutGuidelines.useBreakoutGuidelines)(getEditorWidth, isResizing && (0, _experiments.editorExperiment)('single_column_layouts', true), dynamicFullWidthGuidelineOffset),
snaps = _useBreakoutGuideline.snaps,
currentLayout = _useBreakoutGuideline.currentLayout,
guidelines = _useBreakoutGuideline.guidelines,
setCurrentWidth = _useBreakoutGuideline.setCurrentWidth;
var browser = (0, _browser.getBrowserInfo)();
(0, _react.useEffect)(function () {
if (displayGuidelines) {
displayGuidelines(guidelines || []);
}
}, [displayGuidelines, guidelines]);
// Relying on re-renders caused by selection changes inside/around node
var isSelectionInNode = (0, _react.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 = (0, _react.useCallback)(function () {
onResizeStart === null || onResizeStart === void 0 || onResizeStart();
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) {
var padding = widthState.width <= _editorSharedStyles.akEditorFullPageNarrowBreakout && (0, _experiments.editorExperiment)('platform_editor_preview_panel_responsiveness', true, {
exposure: true
}) ? _editorSharedStyles.akEditorGutterPaddingReduced : (0, _editorSharedStyles.akEditorGutterPaddingDynamic)();
newMaxWidth = Math.min(widthState.width - padding * 2 - _editorSharedStyles.akEditorGutterPadding, _editorSharedStyles.akEditorFullWidthLayoutWidth);
newMinWidth = Math.min(widthState.lineLength, _editorSharedStyles.akEditorDefaultLayoutWidth, newMaxWidth);
}
setResizingState({
isResizing: true,
minWidth: newMinWidth,
maxWidth: newMaxWidth
});
dispatch(state.tr.setMeta('is-resizer-resizing', true));
}, [onResizeStart, getEditorWidth, editorView, displayGapCursor]);
var handleResize = (0, _react.useCallback)(function (originalState, delta) {
if ((0, _experiments.editorExperiment)('single_column_layouts', true)) {
var newWidth = originalState.width + delta.width;
setCurrentWidth(newWidth);
}
}, [setCurrentWidth]);
var handleResizeStop = (0, _react.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)) {
if (currentLayout && ['wide', 'full-width'].includes(currentLayout) && (0, _experiments.editorExperiment)('single_column_layouts', true)) {
newTr.setNodeMarkup(pos, node.type, node.attrs, [breakout.create({
mode: currentLayout,
width: null
})]);
} else {
var newBreakoutWidth = Math.max(newWidth, _editorSharedStyles.akEditorDefaultLayoutWidth);
newTr.setNodeMarkup(pos, node.type, node.attrs, [breakout.create({
width: newBreakoutWidth
})]);
var breakoutResizePayload = {
action: _analytics.ACTION.RESIZED,
actionSubject: _analytics.ACTION_SUBJECT.ELEMENT,
eventType: _analytics.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
});
setCurrentWidth(null);
}, [getPos, editorView, displayGapCursor, setCurrentWidth, currentLayout, editorAnalyticsApi]);
var handleEscape = (0, _react.useCallback)(function () {
editorView === null || editorView === void 0 || editorView.focus();
}, [editorView]);
var handleLayoutSizeChangeOnKeypress = (0, _react.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 = (0, _react.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, browser]);
var resizeHandleKeyUpHandler = (0, _react.useCallback)(function (event) {
if (event.altKey || event.metaKey) {
areResizeMetaKeysPressed.current = false;
}
return;
}, [areResizeMetaKeysPressed]);
var resizerGlobalKeyDownHandler = (0, _react.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, browser]);
(0, _react.useLayoutEffect)(function () {
if (!resizerRef.current || !editorView) {
return;
}
var resizeHandleThumbEl = resizerRef.current.getResizerThumbEl();
if (!resizeHandleThumbEl) {
return;
}
var editorViewDom = editorView.dom;
var unbindEditorViewDom = (0, _bindEventListener.bind)(editorViewDom, {
type: 'keydown',
listener: resizerGlobalKeyDownHandler
});
var unbindResizeHandle = (0, _bindEventListener.bindAll)(resizeHandleThumbEl, [{
type: 'keydown',
listener: resizeHandleKeyDownHandler
}, {
type: 'keyup',
listener: resizeHandleKeyUpHandler
}]);
return function () {
unbindEditorViewDom();
unbindResizeHandle();
};
}, [editorView, resizerGlobalKeyDownHandler, resizeHandleKeyDownHandler, resizeHandleKeyUpHandler]);
if (disabled) {
return /*#__PURE__*/_react.default.createElement("div", {
"data-testid": "breakout-resizer-editor-view-wrapper",
ref: function ref(_ref2) {
return getRef && getRef(_ref2);
}
});
}
return /*#__PURE__*/_react.default.createElement(_Resizer.default, {
ref: resizerRef,
enable: (0, _expValEquals.expValEquals)('platform_editor_perf_lint_cleanup', 'isEnabled', true) ? RESIZER_ENABLE_HANDLES : {
left: true,
right: true
},
snap: snaps || undefined,
snapGap: _useBreakoutGuidelines.SNAP_GAP,
handleStyles: getHandleStyle(nodeType, hidden),
minWidth: minWidth,
maxWidth: maxWidth
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
,
style: isResizing ? resizingStyles : defaultStyles,
handleResizeStart: handleResizeStart,
handleResizeStop: handleResizeStop,
handleResize: handleResize,
childrenDOMRef: getRef,
resizeRatio: 2,
isHandleVisible: isSelectionInNode,
handleSize: "clamped",
handleHighlight: "full-height",
handlePositioning: "adjacent",
handleAlignmentMethod: "sticky",
labelComponent: currentLayout && (0, _experiments.editorExperiment)('single_column_layouts', true) && ['full-width', 'wide'].includes(currentLayout || '') && /*#__PURE__*/_react.default.createElement(_ResizerBreakoutModeLabel.ResizerBreakoutModeLabel, {
layout: currentLayout
})
});
};