@atlaskit/editor-plugin-table
Version:
Table plugin for the @atlaskit/editor
312 lines (308 loc) • 13.9 kB
JavaScript
/* eslint-disable @atlaskit/design-system/prefer-primitives */
import React, { Fragment, useCallback, useEffect, useMemo, useState } from 'react';
import { useSharedPluginStateWithSelector } from '@atlaskit/editor-common/hooks';
import { CellSelection } from '@atlaskit/editor-tables';
import { getSelectionRect } from '@atlaskit/editor-tables/utils';
import { monitorForElements } from '@atlaskit/pragmatic-drag-and-drop/element/adapter';
import { expValEquals } from '@atlaskit/tmp-editor-statsig/exp-val-equals';
import { clearHoverSelection } from '../../../pm-plugins/commands';
import { toggleDragMenuWithAnalytics } from '../../../pm-plugins/drag-and-drop/commands-with-analytics';
import { getPluginState as getTablePluginState } from '../../../pm-plugins/plugin-factory';
import { getRowHeights, getRowsParams } from '../../../pm-plugins/utils/row-controls';
import { getSelectedRowIndexes } from '../../../pm-plugins/utils/selection';
import { TableCssClassName as ClassName } from '../../../types';
import { dragRowControlsWidth, dropTargetExtendedWidth } from '../../consts';
import { DragHandle } from '../../DragHandle';
import RowDropTarget from '../RowDropTarget';
const getSelectedRows = selection => {
if (selection instanceof CellSelection && selection.isRowSelection()) {
const rect = getSelectionRect(selection);
if (!rect) {
return [];
}
return getSelectedRowIndexes(rect);
}
return [];
};
export const DragControls = ({
tableRef,
tableNode,
tableWidth,
hoveredCell,
tableActive,
editorView,
isInDanger,
isResizing,
isTableHovered,
hoverRows,
selectRow,
selectRows,
updateCellHoverLocation,
api,
selection
}) => {
var _tableNode$attrs$loca, _tableNode$attrs, _api$analytics2;
const [isDragging, setIsDragging] = useState(false);
const rowHeights = getRowHeights(tableRef);
const rowsParams = getRowsParams(rowHeights);
const heights = rowHeights.map(height => `${height - 1}px`).join(' ');
const selectedRowIndexes = getSelectedRows(selection !== null && selection !== void 0 ? selection : editorView.state.selection);
const currentNodeLocalId = (_tableNode$attrs$loca = tableNode === null || tableNode === void 0 ? void 0 : (_tableNode$attrs = tableNode.attrs) === null || _tableNode$attrs === void 0 ? void 0 : _tableNode$attrs.localId) !== null && _tableNode$attrs$loca !== void 0 ? _tableNode$attrs$loca : '';
useEffect(() => {
return monitorForElements({
canMonitor({
source
}) {
const {
type,
localId,
indexes
} = source.data;
if (!indexes || !localId || type !== 'table-row') {
return false;
}
const {
tableNode
} = getTablePluginState(editorView.state);
// If the draggable localId is the same as the current selected table localId then we will allow the monitor
// watch for changes
return localId === (tableNode === null || tableNode === void 0 ? void 0 : tableNode.attrs.localId);
},
onDragStart() {
setIsDragging(true);
},
onDrop() {
setIsDragging(false);
}
});
}, [editorView]);
const toggleDragMenuHandler = useCallback((trigger, event) => {
var _api$analytics;
if (event !== null && event !== void 0 && event.shiftKey) {
return;
}
toggleDragMenuWithAnalytics(api === null || api === void 0 ? void 0 : (_api$analytics = api.analytics) === null || _api$analytics === void 0 ? void 0 : _api$analytics.actions)(undefined, 'row', hoveredCell === null || hoveredCell === void 0 ? void 0 : hoveredCell.rowIndex, trigger)(editorView.state, editorView.dispatch);
}, [editorView, hoveredCell === null || hoveredCell === void 0 ? void 0 : hoveredCell.rowIndex, api === null || api === void 0 ? void 0 : (_api$analytics2 = api.analytics) === null || _api$analytics2 === void 0 ? void 0 : _api$analytics2.actions]);
const rowIndex = hoveredCell === null || hoveredCell === void 0 ? void 0 : hoveredCell.rowIndex;
const handleMouseOut = useCallback(() => {
if (tableActive) {
const {
state,
dispatch
} = editorView;
clearHoverSelection()(state, dispatch);
}
}, [editorView, tableActive]);
const handleMouseMove = useCallback(e => {
const target = e.nativeEvent.target instanceof Element ? e.nativeEvent.target : null;
const isParentDragControls = target === null || target === void 0 ? void 0 : target.closest(`.${ClassName.DRAG_ROW_CONTROLS}`);
const rowIndex = target === null || target === void 0 ? void 0 : target.getAttribute('data-start-index');
// avoid updating if event target is not related
if (!isParentDragControls || !rowIndex) {
return;
}
updateCellHoverLocation(Number(rowIndex));
}, [updateCellHoverLocation]);
const rowIndexes = useMemo(() => {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return [rowIndex];
}, [rowIndex]);
const handleMouseOver = useCallback(() => {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
hoverRows([rowIndex]);
}, [hoverRows, rowIndex]);
const handleClick = useCallback(e => {
const isClickOutsideSelectedRows =
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
selectedRowIndexes.length >= 1 && !selectedRowIndexes.includes(rowIndex);
if (!selectedRowIndexes || selectedRowIndexes.length === 0 || isClickOutsideSelectedRows) {
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
selectRow(rowIndex, e.shiftKey);
}
// Ignored via go/ees005
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
if (selectedRowIndexes.length > 1 && selectedRowIndexes.includes(rowIndex) && !e.shiftKey) {
selectRows(selectedRowIndexes);
}
}, [rowIndex, selectRow, selectRows, selectedRowIndexes]);
const generateHandleByType = (type, appearance, gridRow, indexes) => {
const isHover = type === 'hover';
const previewHeight = rowHeights.reduce((sum, v, i) => sum + v * (indexes.includes(i) ? 1 : 0), 0);
return /*#__PURE__*/React.createElement("div", {
key: type,
style: {
gridRow,
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
gridColumn: '2',
// DragHandle uses `transform: rotate(90)`, which doesn't affect its parent (this div) causing the width of this element to be the true height of the drag handle
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
display: 'flex',
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
width: '9px',
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
height: '100%',
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
position: 'relative',
// eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage/preview, @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
right: '-0.5px',
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop
pointerEvents: 'none'
}
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop
,
className: expValEquals('platform_editor_table_sticky_header_improvements', 'cohort', 'test_with_overflow') ? ClassName.DRAG_ROW_FLOATING_DRAG_HANDLE : undefined,
"data-testid": `table-floating-row-${isHover ? rowIndex : selectedRowIndexes[0]}-drag-handle`,
"data-row-index": rowIndex,
"data-selected-row-index": selectedRowIndexes[0],
"data-handle-appearance": appearance
}, /*#__PURE__*/React.createElement(DragHandle, {
isDragMenuTarget: !isHover,
direction: "row",
tableLocalId: currentNodeLocalId,
indexes: indexes,
forceDefaultHandle: !isHover,
previewWidth: tableWidth,
previewHeight: previewHeight,
appearance: appearance,
hoveredCell: hoveredCell,
onClick: handleClick,
onMouseOver: handleMouseOver,
onMouseOut: handleMouseOut,
onBlur: expValEquals('platform_editor_table_a11y_eslint_fix', 'isEnabled', true) ? handleMouseOut : undefined,
onFocus: expValEquals('platform_editor_table_a11y_eslint_fix', 'isEnabled', true) ? handleMouseOver : undefined,
toggleDragMenu: toggleDragMenuHandler,
editorView: editorView
}));
};
const rowHandles = () => {
const handles = [];
const isRowSelected = selectedRowIndexes.length > 0;
const isEntireTableSelected = rowHeights.length > selectedRowIndexes.length;
if (!tableActive) {
return null;
}
// placeholder / selected need to always render at least one handle
// so it can be focused via keyboard shortcuts
handles.push(generateHandleByType('selected', isRowSelected && isEntireTableSelected ? isInDanger ? 'danger' : 'selected' : 'placeholder', `${selectedRowIndexes[0] + 1} / span ${selectedRowIndexes.length}`, selectedRowIndexes));
if (hoveredCell && isTableHovered && rowIndex !== undefined && !selectedRowIndexes.includes(rowIndex) && rowIndex < rowHeights.length) {
handles.push(generateHandleByType('hover', 'default', `${rowIndex + 1} / span 1`, rowIndexes));
}
return handles;
};
if (isResizing) {
return null;
}
return /*#__PURE__*/React.createElement("div", {
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
className: ClassName.DRAG_ROW_CONTROLS,
style: {
gridTemplateRows: heights,
gridTemplateColumns: isDragging ?
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
`${dropTargetExtendedWidth}px ${dragRowControlsWidth}px ${tableWidth}px` :
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values
`0px ${dragRowControlsWidth}px 0px`,
// eslint-disable-next-line @atlaskit/design-system/ensure-design-token-usage/preview
left: isDragging ?
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-imported-style-values -- Ignored via go/DSP-18766
`-${dropTargetExtendedWidth + 2}px` : "var(--ds-space-negative-025, -2px)"
},
onMouseMove: handleMouseMove,
contentEditable: false
}, rowsParams.map(({
startIndex,
endIndex
}, index) =>
/*#__PURE__*/
// Ignored via go/ees005
// eslint-disable-next-line react/no-array-index-key
React.createElement(Fragment, {
key: index
}, /*#__PURE__*/React.createElement("div", {
style: {
gridRow: `${index + 1} / span 1`,
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
gridColumn: '2'
},
"data-start-index": startIndex,
"data-end-index": endIndex
// eslint-disable-next-line @atlaskit/ui-styling-standard/no-classname-prop -- Ignored via go/DSP-18766
,
className: ClassName.DRAG_ROW_FLOATING_INSERT_DOT_WRAPPER,
contentEditable: false
// Ignored via go/ees005
// eslint-disable-next-line react/no-array-index-key
,
key: `insert-dot-${index}`
}, /*#__PURE__*/React.createElement("div", {
className: ClassName.DRAG_ROW_FLOATING_INSERT_DOT
})), isDragging && /*#__PURE__*/React.createElement(RowDropTarget
// Ignored via go/ees005
// eslint-disable-next-line react/no-array-index-key
, {
key: `drop-target-${index}`,
index: index,
localId: currentNodeLocalId
// eslint-disable-next-line @atlassian/perf-linting/no-unstable-inline-props -- Ignored via go/ees017 (to be fixed)
,
style: {
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
gridColumn: '1 / span 3',
gridRow: `${index + 1} / span 1`,
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
height: '100%',
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
pointerEvents: 'auto',
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
position: 'relative',
// eslint-disable-next-line @atlaskit/ui-styling-standard/enforce-style-prop -- Ignored via go/DSP-18766
left: "var(--ds-space-negative-100, -8px)"
}
}))), rowHandles());
};
export const DragControlsWithSelection = ({
editorView,
tableRef,
tableNode,
tableWidth,
tableActive,
hoveredCell,
isInDanger,
isTableHovered,
isResizing,
hoverRows,
selectRow,
selectRows,
updateCellHoverLocation,
api
}) => {
const {
selection
} = useSharedPluginStateWithSelector(api, ['selection'], states => {
var _states$selectionStat;
return {
selection: (_states$selectionStat = states.selectionState) === null || _states$selectionStat === void 0 ? void 0 : _states$selectionStat.selection
};
});
return /*#__PURE__*/React.createElement(DragControls, {
editorView: editorView,
tableRef: tableRef,
tableNode: tableNode,
tableWidth: tableWidth,
tableActive: tableActive,
hoveredCell: hoveredCell,
isInDanger: isInDanger,
isTableHovered: isTableHovered,
isResizing: isResizing,
hoverRows: hoverRows,
selectRow: selectRow,
selectRows: selectRows,
updateCellHoverLocation: updateCellHoverLocation,
api: api,
selection: selection
});
};