UNPKG

@atlaskit/editor-common

Version:

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

278 lines (276 loc) 11.8 kB
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;