@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
278 lines (276 loc) • 11.8 kB
JavaScript
import _extends from "@babel/runtime/helpers/extends";
import React, { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
import classnames from 'classnames';
import { Resizable } from 're-resizable';
import { useIntl } from 'react-intl';
// eslint-disable-next-line @atlaskit/design-system/no-emotion-primitives -- to be migrated to @atlaskit/primitives/compiled – go/akcss
import { Box, xcss } from '@atlaskit/primitives';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { editorExperiment } from '@atlaskit/tmp-editor-statsig/experiments';
import Tooltip from '@atlaskit/tooltip';
import { messages } from '../messages/breakout';
import { handleWrapperClass, resizerDangerClassName, resizerExtendedZone, resizerHandleClassName, resizerHandleThumbClassName, resizerHandleTrackClassName, resizerHandleZIndex, resizerHoverZoneClassName, resizerItemClassName } from '../styles/shared/resizer';
const resizerLabelStyles = xcss({
position: 'absolute',
bottom: "var(--ds-space-0, 0px)",
width: '100%',
overflow: 'visible',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: "var(--ds-space-0, 0px)",
zIndex: 'layer' // 400 same z-index as the floating toolbar
});
const SUPPORTED_HANDLES = ['left', 'right'];
const SUPPORTED_HANDLES_FOR_VERTICAL_RESIZE = ['left', 'right', 'bottom'];
const inheritedCSS = {
position: 'inherit',
height: 'inherit',
width: 'inherit',
display: 'inherit',
flexDirection: 'inherit',
justifyContent: 'inherit',
alignItems: 'inherit'
};
const ResizerNext = (props, ref) => {
const [isResizing, setIsResizing] = useState(false);
const resizable = useRef(null);
const resizeHandleThumbRef = useRef(null);
useImperativeHandle(ref, () => {
return {
getResizerThumbEl() {
return resizeHandleThumbRef.current;
}
};
}, [resizeHandleThumbRef]);
const {
width,
height,
children,
handleClassName,
className,
handleResize,
handleResizeStart,
handleResizeStop,
handleSize = 'medium',
handleAlignmentMethod = 'center',
handlePositioning = 'overlap',
appearance,
handleStyles,
resizeRatio = 1,
snap,
snapGap,
isHandleVisible = false,
handleHighlight = 'none',
handleTooltipContent,
needExtendedResizeZone = true,
childrenDOMRef,
labelComponent,
...otherProps
} = props;
const supportedHandles = expValEquals('databases-native-embeds-v2', 'isEnabled', true) ? SUPPORTED_HANDLES_FOR_VERTICAL_RESIZE : SUPPORTED_HANDLES;
const onResizeStart = useCallback(event => {
// prevent creating a drag event on Firefox
event.preventDefault();
setIsResizing(true);
handleResizeStart();
}, [handleResizeStart]);
const onResize = useCallback((_event, direction, _elementRef, delta) => {
if (!handleResize) {
return;
}
const resizableCurrent = resizable.current;
if (!resizableCurrent || !resizableCurrent.state.original) {
return;
}
const originalState = {
x: resizableCurrent.state.original.x,
y: resizableCurrent.state.original.y,
width: resizableCurrent.state.original.width,
height: resizableCurrent.state.original.height
};
if (expValEquals('databases-native-embeds-v2', 'isEnabled', true)) {
handleResize(originalState, delta, direction);
} else {
handleResize(originalState, delta);
}
}, [handleResize]);
const onResizeStop = useCallback((_event, direction, _elementRef, delta) => {
const resizableCurrent = resizable.current;
if (!resizableCurrent || !resizableCurrent.state.original) {
return;
}
const originalState = {
x: resizableCurrent.state.original.x,
y: resizableCurrent.state.original.y,
width: resizableCurrent.state.original.width,
height: resizableCurrent.state.original.height
};
setIsResizing(false);
if (expValEquals('databases-native-embeds-v2', 'isEnabled', true)) {
handleResizeStop(originalState, delta, direction);
} else {
handleResizeStop(originalState, delta);
}
}, [handleResizeStop]);
const handles = useMemo(() => supportedHandles.reduce((result, position) => ({
...result,
[position]: classnames(handleClassName !== null && handleClassName !== void 0 ? handleClassName : resizerHandleClassName, position, handleSize, position === 'bottom' && expValEquals('databases-native-embeds-v2', 'isEnabled', true) ? undefined : handleAlignmentMethod)
}), {}), [handleClassName, handleSize, handleAlignmentMethod, supportedHandles]);
const handleWidth = handlePositioning === 'adjacent' ? "var(--ds-space-100, 8px)" : "var(--ds-space-300, 24px)";
const baseHorizontalHandleStyles = {
width: handleWidth,
zIndex: resizerHandleZIndex,
pointerEvents: 'auto',
alignItems: handlePositioning === 'adjacent' ? 'center' : undefined
};
const baseBottomHandleStyles = {
height: handleWidth,
zIndex: resizerHandleZIndex,
pointerEvents: 'auto',
justifyContent: handlePositioning === 'adjacent' ? 'center' : undefined
};
const memoizedBaseHorizontalHandleStyles = useMemo(() => ({
width: handleWidth,
zIndex: resizerHandleZIndex,
pointerEvents: 'auto',
alignItems: handlePositioning === 'adjacent' ? 'center' : undefined
}), [handleWidth, handlePositioning]);
const memoizedBaseBottomHandleStyles = useMemo(() => ({
height: handleWidth,
zIndex: resizerHandleZIndex,
pointerEvents: 'auto',
justifyContent: handlePositioning === 'adjacent' ? 'center' : undefined
}), [handleWidth, handlePositioning]);
const offset = handlePositioning === 'adjacent' ? `calc(${handleWidth} * -1)` : `calc(${handleWidth} * -0.5)`;
const memoizedNextHandleStyles = useMemo(() => supportedHandles.reduce((result, position) => ({
...result,
[position]: {
...(position === 'bottom' ? memoizedBaseBottomHandleStyles : memoizedBaseHorizontalHandleStyles),
[position]: offset,
...(handleStyles === null || handleStyles === void 0 ? void 0 : handleStyles[position])
}
}), {}), [memoizedBaseBottomHandleStyles, memoizedBaseHorizontalHandleStyles, offset, handleStyles, supportedHandles]);
const nextHandleStyles = expValEquals('platform_editor_perf_lint_cleanup', 'isEnabled', true) ? memoizedNextHandleStyles :
// eslint-disable-next-line @atlassian/perf-linting/no-expensive-computations-in-render -- intentional fallback for experiment off path
supportedHandles.reduce((result, position) => ({
...result,
[position]: {
...(position === 'bottom' ? baseBottomHandleStyles : baseHorizontalHandleStyles),
[position]: offset,
...(handleStyles === null || handleStyles === void 0 ? void 0 : handleStyles[position])
}
}), {});
const resizerClassName = classnames(className, resizerItemClassName, {
'is-resizing': isResizing,
'display-handle': isHandleVisible,
[resizerDangerClassName]: appearance === 'danger'
});
const resizerZoneClassName = classnames(resizerHoverZoneClassName, {
[resizerExtendedZone]: needExtendedResizeZone
});
const {
formatMessage
} = useIntl();
const handleComponent = useMemo(() => {
return supportedHandles.reduce((result, position) => {
const thumb = /*#__PURE__*/React.createElement("button", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
className: resizerHandleThumbClassName,
"data-testid": `resizer-handle-${position}-thumb`,
"aria-label": formatMessage(messages.resizeHandle),
contentEditable: false,
ref: resizeHandleThumbRef,
type: "button",
tabIndex: -1 //We want to control focus on this button ourselves
});
if ((!handleHighlight || handleHighlight === 'none') && !handleTooltipContent) {
return {
...result,
[position]: thumb
};
}
const thumbWithTrack =
/*#__PURE__*/
//It's important to have {thumb} element before the div, the thumb element is the one that gets focus and only the 1st element recives aria-descibedby attribute which is important for screen reader users
React.createElement(React.Fragment, null, thumb, /*#__PURE__*/React.createElement("div", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
className: classnames(resizerHandleTrackClassName, handleHighlight),
"data-testid": `resizer-handle-${position}-track`
}));
if (!!handleTooltipContent) {
return {
...result,
[position]:
/*#__PURE__*/
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
React.createElement("div", {
contentEditable: false,
style: inheritedCSS
}, /*#__PURE__*/React.createElement(Tooltip, {
content: handleTooltipContent,
hideTooltipOnClick: true,
position: "mouse",
mousePosition: "auto-start",
testId: `resizer-handle-${position}-tooltip`
}, thumbWithTrack))
};
}
return {
...result,
[position]:
/*#__PURE__*/
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
React.createElement("div", {
contentEditable: false,
style: inheritedCSS
}, thumbWithTrack)
};
}, {});
}, [handleHighlight, handleTooltipContent, formatMessage, supportedHandles]);
// snapGap is usually a constant, if snap.x?.length is 0 and snapGap has a value resizer cannot be resized
const snapGapActual = useMemo(() => {
var _snap$x, _snap$y;
if (!snap || ((_snap$x = snap.x) === null || _snap$x === void 0 ? void 0 : _snap$x.length) === 0 && ((_snap$y = snap.y) === null || _snap$y === void 0 ? void 0 : _snap$y.length) === 0) {
return undefined;
}
return snapGap;
}, [snap, snapGap]);
const resolvedHeight = expValEquals('databases-native-embeds-v2', 'isEnabled', true) ? height !== null && height !== void 0 ? height : 'auto' : 'auto';
const resizerAutoSize = useMemo(() => ({
width: width !== null && width !== void 0 ? width : 'auto',
height: resolvedHeight
}), [resolvedHeight, width]);
const resizerSize = expValEquals('platform_editor_perf_lint_cleanup', 'isEnabled', true) ? resizerAutoSize :
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- intentional fallback for experiment off path
{
width: width !== null && width !== void 0 ? width : 'auto',
height: resolvedHeight
};
return /*#__PURE__*/React.createElement(Resizable, _extends({
ref: resizable,
size: resizerSize
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
,
className: resizerClassName,
handleClasses: handles,
handleWrapperClass: handleWrapperClass,
handleStyles: nextHandleStyles,
onResizeStart: onResizeStart,
onResize: onResize,
onResizeStop: onResizeStop,
resizeRatio: resizeRatio,
snapGap: snapGapActual,
snap: snap,
handleComponent: handleComponent
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
}, otherProps), /*#__PURE__*/React.createElement("span", {
className: resizerZoneClassName,
ref: ref => childrenDOMRef && childrenDOMRef(ref)
}, children), labelComponent && editorExperiment('single_column_layouts', true) && /*#__PURE__*/React.createElement(Box, {
xcss: resizerLabelStyles
}, labelComponent));
};
const _default_1 = /*#__PURE__*/forwardRef(ResizerNext);
export default _default_1;