@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
248 lines (246 loc) • 12.2 kB
JavaScript
import _defineProperty from "@babel/runtime/helpers/defineProperty";
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
/* eslint-disable @atlaskit/design-system/no-html-button */
import React, { useEffect, useMemo, useRef, useState } from 'react';
import classnames from 'classnames';
import ReactDOM from 'react-dom';
import { injectIntl } from 'react-intl';
import { getBrowserInfo } from '@atlaskit/editor-common/browser';
import { tableMessages as messages } from '@atlaskit/editor-common/messages';
import { TextSelection } from '@atlaskit/editor-prosemirror/state';
import { findTable, TableMap } from '@atlaskit/editor-tables';
import { draggable } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import { setCustomNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/set-custom-native-drag-preview';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { getPluginState as getDnDPluginState } from '../../pm-plugins/drag-and-drop/plugin-factory';
import { findDuplicatePosition, hasMergedCellsInSelection } from '../../pm-plugins/utils/merged-cells';
import { TableCssClassName as ClassName } from '../../types';
import { dragTableInsertColumnButtonSize } from '../consts';
import { DragPreview } from '../DragPreview';
import { HandleIconComponent } from './HandleIconComponent';
var DragHandleComponent = function DragHandleComponent(_ref) {
var isDragMenuTarget = _ref.isDragMenuTarget,
tableLocalId = _ref.tableLocalId,
_ref$direction = _ref.direction,
direction = _ref$direction === void 0 ? 'row' : _ref$direction,
_ref$appearance = _ref.appearance,
appearance = _ref$appearance === void 0 ? 'default' : _ref$appearance,
indexes = _ref.indexes,
_ref$forceDefaultHand = _ref.forceDefaultHandle,
forceDefaultHandle = _ref$forceDefaultHand === void 0 ? false : _ref$forceDefaultHand,
previewWidth = _ref.previewWidth,
previewHeight = _ref.previewHeight,
_onMouseOver = _ref.onMouseOver,
_onMouseOut = _ref.onMouseOut,
onFocus = _ref.onFocus,
onBlur = _ref.onBlur,
toggleDragMenu = _ref.toggleDragMenu,
hoveredCell = _ref.hoveredCell,
onClick = _ref.onClick,
editorView = _ref.editorView,
formatMessage = _ref.intl.formatMessage;
var dragHandleDivRef = useRef(null);
var _useState = useState(null),
_useState2 = _slicedToArray(_useState, 2),
previewContainer = _useState2[0],
setPreviewContainer = _useState2[1];
var state = editorView.state,
selection = editorView.state.selection;
var _getDnDPluginState = getDnDPluginState(state),
_getDnDPluginState$is = _getDnDPluginState.isDragMenuOpen,
isDragMenuOpen = _getDnDPluginState$is === void 0 ? false : _getDnDPluginState$is;
var _useState3 = useState(false),
_useState4 = _slicedToArray(_useState3, 2),
isHovered = _useState4[0],
setIsHovered = _useState4[1];
var isRow = direction === 'row';
var isColumn = direction === 'column';
var hasMergedCells = useMemo(function () {
var table = findTable(selection);
if (!table) {
return false;
}
var map = TableMap.get(table === null || table === void 0 ? void 0 : table.node);
if (!map.hasMergedCells() || indexes.length < 1) {
return false;
}
var mapByColumn = map.mapByColumn,
mapByRow = map.mapByRow;
// this handle when hover to first column or row which has merged cells.
if (hoveredCell && hoveredCell.rowIndex !== undefined && hoveredCell.colIndex !== undefined && selection instanceof TextSelection) {
var rowIndex = hoveredCell.rowIndex,
colIndex = hoveredCell.colIndex;
var mergedPositionInRow = findDuplicatePosition(mapByRow[rowIndex]);
var mergedPositionInCol = findDuplicatePosition(mapByColumn[colIndex]);
var hasMergedCellsInFirstRowOrColumn = direction === 'column' ? mergedPositionInRow.includes(mapByRow[0][colIndex]) : mergedPositionInCol.includes(mapByColumn[0][rowIndex]);
var isHoveredOnFirstRowOrColumn = direction === 'column' ? hoveredCell.rowIndex === 0 && hasMergedCellsInFirstRowOrColumn : hoveredCell.colIndex === 0 && hasMergedCellsInFirstRowOrColumn;
if (isHoveredOnFirstRowOrColumn) {
var mergedSizes = direction === 'column' ? mapByRow[0].filter(function (el) {
return el === mapByRow[0][colIndex];
}).length : mapByColumn[0].filter(function (el) {
return el === mapByColumn[0][rowIndex];
}).length;
var mergedSelection = hasMergedCellsInSelection(direction === 'column' ? [colIndex, colIndex + mergedSizes - 1] : [rowIndex, rowIndex + mergedSizes - 1], direction)(selection);
return mergedSelection;
}
}
return hasMergedCellsInSelection(indexes, direction)(selection);
}, [indexes, selection, direction, hoveredCell]);
var handleIconProps = {
forceDefaultHandle: forceDefaultHandle,
isHandleHovered: isHovered,
hasMergedCells: hasMergedCells
};
useEffect(function () {
var dragHandleDivRefCurrent = dragHandleDivRef.current;
var browser = getBrowserInfo();
if (dragHandleDivRefCurrent) {
return draggable({
element: dragHandleDivRefCurrent,
canDrag: function canDrag() {
return !hasMergedCells;
},
getInitialData: function getInitialData() {
return {
localId: tableLocalId,
type: "table-".concat(direction),
indexes: indexes
};
},
onGenerateDragPreview: function onGenerateDragPreview(_ref2) {
var nativeSetDragImage = _ref2.nativeSetDragImage;
setCustomNativeDragPreview({
getOffset: function getOffset(_ref3) {
var container = _ref3.container;
var rect = container.getBoundingClientRect();
if (browser.safari) {
// See: https://product-fabric.atlassian.net/browse/ED-21442
// We need to ensure that the preview is not overlaying screen content when the snapshot is taken, otherwise
// safari will composite the screen text elements into the bitmap snapshot. The container is a wrapper which is already
// positioned fixed at top/left 0.
// IMPORTANT: we must not exceed more then the width of the container off-screen otherwise not preview will
// be generated.
container.style.left = "-".concat(rect.width - 0.0001, "px");
}
if (isRow) {
return {
x: 12,
y: rect.height / 2
};
} else {
return {
x: rect.width / 2 + 4,
y: 12
};
}
},
render: function render(_ref4) {
var container = _ref4.container;
setPreviewContainer(container);
return function () {
return setPreviewContainer(null);
};
},
nativeSetDragImage: nativeSetDragImage
});
}
});
}
}, [tableLocalId, direction, indexes, isRow, editorView.state.selection, hasMergedCells]);
var showDragMenuAnchorId = isRow ? 'drag-handle-button-row' : 'drag-handle-button-column';
var browser = getBrowserInfo();
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("button", {
type: "button"
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
,
className: ClassName.DRAG_HANDLE_BUTTON_CLICKABLE_ZONE,
"data-testid": "table-drag-handle-clickable-zone-button",
style: {
height: isRow ? // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
"calc(100% - ".concat(dragTableInsertColumnButtonSize, "px)") : "var(--ds-space-200, 16px)",
// 16px here because it's the size of drag handle button's large side
width: isRow ? "var(--ds-space-200, 16px)" // 16px here because it's the size of drag handle button's large side
: // eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
"calc(100% - ".concat(dragTableInsertColumnButtonSize, "px)"),
left: isRow ? "var(--ds-space-050, 4px)" : undefined,
bottom: isColumn ? "var(--ds-space-0, 0px)" : undefined,
alignSelf: isColumn ? 'none' : 'center',
zIndex: isColumn ? '-1' : 'auto',
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
pointerEvents: 'auto'
},
onMouseUp: function onMouseUp(e) {
// should toggle menu if current drag menu open.
// return focus to editor so copying table selections whilst still works, i cannot call e.preventDefault in a mousemove event as this stops dragstart events from firing
// -> this is bad for a11y but is the current standard new copy/paste keyboard shortcuts should be introduced instead
editorView.focus();
if (isDragMenuOpen) {
toggleDragMenu && toggleDragMenu('mouse', e);
}
},
onClick: onClick,
"aria-label": formatMessage(messages.dragHandleZone)
}), /*#__PURE__*/React.createElement("button", {
type: "button",
id: isDragMenuTarget ? showDragMenuAnchorId : undefined
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
,
className: classnames(ClassName.DRAG_HANDLE_BUTTON_CONTAINER, appearance, _defineProperty({}, ClassName.DRAG_HANDLE_DISABLED, hasMergedCells)),
ref: dragHandleDivRef,
style: {
transform: isColumn ? 'none' : 'rotate(90deg)',
alignSelf: isColumn ? 'none' : 'center'
},
"data-testid": "table-drag-handle-button",
"aria-label": formatMessage(isRow ? messages.rowDragHandle : messages.columnDragHandle),
"aria-expanded": isDragMenuOpen && isDragMenuTarget ? 'true' : 'false',
"aria-haspopup": "menu",
onMouseOver: function onMouseOver(e) {
setIsHovered(true);
_onMouseOver && _onMouseOver(e);
},
onMouseOut: function onMouseOut(e) {
setIsHovered(false);
_onMouseOut && _onMouseOut(e);
},
onFocus: expValEquals('platform_editor_table_a11y_eslint_fix', 'isEnabled', true) ? function (e) {
onFocus && onFocus(e);
} : undefined,
onBlur: expValEquals('platform_editor_table_a11y_eslint_fix', 'isEnabled', true) ? function (e) {
onBlur && onBlur(e);
} : undefined,
onMouseUp: function onMouseUp(e) {
// return focus to editor so copying table selections whilst still works, i cannot call e.preventDefault in a mousemove event as this stops dragstart events from firing
// -> this is bad for a11y but is the current standard new copy/paste keyboard shortcuts should be introduced instead
editorView.focus();
toggleDragMenu && toggleDragMenu('mouse', e);
},
onClick: onClick,
onKeyDown: function onKeyDown(e) {
if (e.key === 'Enter' || e.key === ' ') {
toggleDragMenu && toggleDragMenu('keyboard');
}
}
}, appearance !== 'placeholder' ?
// cannot block pointer events in Firefox as it breaks Dragging functionality
browser.gecko ?
/*#__PURE__*/
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
React.createElement(HandleIconComponent, handleIconProps) :
/*#__PURE__*/
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
React.createElement("span", {
style: {
pointerEvents: 'none'
}
}, /*#__PURE__*/React.createElement(HandleIconComponent
// Ignored via go/ees005
// eslint-disable-next-line react/jsx-props-no-spreading
, handleIconProps)) : null), previewContainer && previewWidth !== undefined && previewHeight !== undefined && /*#__PURE__*/ReactDOM.createPortal( /*#__PURE__*/React.createElement(DragPreview, {
direction: direction,
width: previewWidth,
height: previewHeight
}), previewContainer));
};
export var DragHandle = injectIntl(DragHandleComponent);