UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

248 lines (246 loc) 12.2 kB
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);