UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

151 lines (145 loc) 6.63 kB
import { INPUT_METHOD } from '@atlaskit/editor-common/analytics'; import { isSelectionTableNestedInTable } from '@atlaskit/editor-common/nesting'; import { NodeSelection, TextSelection } from '@atlaskit/editor-prosemirror/state'; import { findParentNodeOfType } from '@atlaskit/editor-prosemirror/utils'; import { TableMap } from '@atlaskit/editor-tables/table-map'; import { goToNextCell as baseGotoNextCell, findCellClosestToPos, findTable, findTableClosestToPos, isTableSelected } from '@atlaskit/editor-tables/utils'; import { getPluginState } from '../plugin-factory'; import { stopKeyboardColumnResizing } from './column-resize'; import { insertRowWithAnalytics } from './commands-with-analytics'; var TAB_FORWARD_DIRECTION = 1; var TAB_BACKWARD_DIRECTION = -1; export var goToNextCell = function goToNextCell(editorAnalyticsAPI, ariaNotify, getIntl) { return function (direction) { return function (state, dispatch, view) { var _getPluginState; var table = findTable(state.selection); if (!table) { return false; } var isColumnResizing = (_getPluginState = getPluginState(state)) === null || _getPluginState === void 0 ? void 0 : _getPluginState.isKeyboardResize; if (isColumnResizing) { stopKeyboardColumnResizing({ ariaNotify: ariaNotify, getIntl: getIntl })(state, dispatch, view); return true; } var map = TableMap.get(table.node); var _state$schema$nodes = state.schema.nodes, tableCell = _state$schema$nodes.tableCell, tableHeader = _state$schema$nodes.tableHeader; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-non-null-assertion var cell = findParentNodeOfType([tableCell, tableHeader])(state.selection); var firstCellPos = map.positionAt(0, 0, table.node) + table.start; var lastCellPos = map.positionAt(map.height - 1, map.width - 1, table.node) + table.start; // When tabbing backwards at first cell (top left), insert row at the start of table if (firstCellPos === cell.pos && direction === TAB_BACKWARD_DIRECTION) { insertRowWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.KEYBOARD, { index: 0, moveCursorToInsertedRow: true })(state, dispatch); return true; } // When tabbing forwards at last cell (bottom right), insert row at the end of table if (lastCellPos === cell.pos && direction === TAB_FORWARD_DIRECTION) { insertRowWithAnalytics(editorAnalyticsAPI)(INPUT_METHOD.KEYBOARD, { index: map.height, moveCursorToInsertedRow: true })(state, dispatch); return true; } if (dispatch) { return baseGotoNextCell(direction)(state, dispatch); } return true; }; }; }; /** * Moves the cursor vertically from a NodeSelection within a table cell. * - If content exists above/below within the cell, lets ProseMirror handle it. * - Otherwise, moves to the next cell (down to start, up to last line). */ export var goToNextCellVertical = function goToNextCellVertical(direction) { return function (state, dispatch) { var selection = state.selection; var nestedTableSelection = isSelectionTableNestedInTable(state); if (!(selection instanceof NodeSelection) && !nestedTableSelection) { return false; // Let ProseMirror handle other selection types } var table = findTable(selection); var cell = findCellClosestToPos(state.doc.resolve(selection.from)); var selectionPos = selection.from; // Handle when we have nested table fully selected if (table && nestedTableSelection && isTableSelected(selection)) { var parentTablePos = table.pos; table = findTableClosestToPos(state.doc.resolve(parentTablePos)); cell = findCellClosestToPos(state.doc.resolve(parentTablePos)); selectionPos = parentTablePos; } if (!table || !cell || !cell.pos) { return false; } var _state$schema$nodes2 = state.schema.nodes, tableCell = _state$schema$nodes2.tableCell, tableHeader = _state$schema$nodes2.tableHeader; // Let ProseMirror handle movement within the cell if content exists above/below if (cellHasContentInDirection(cell.node, cell.pos, selectionPos, direction)) { return false; } // Move to the next cell vertically var map = TableMap.get(table.node); var nextCellPos = map.nextCell(cell.pos - table.start, 'vert', direction); if (dispatch && nextCellPos) { var _$nextCell$nodeAfter, _$nextCell$nodeAfter2; var nextCellStart = table.start + nextCellPos; var $nextCell = state.doc.resolve(nextCellStart); if ((_$nextCell$nodeAfter = $nextCell.nodeAfter) !== null && _$nextCell$nodeAfter !== void 0 && _$nextCell$nodeAfter.type && [tableCell, tableHeader].includes((_$nextCell$nodeAfter2 = $nextCell.nodeAfter) === null || _$nextCell$nodeAfter2 === void 0 ? void 0 : _$nextCell$nodeAfter2.type)) { var contentPos = getTargetPositionInNextCell($nextCell.nodeAfter, nextCellStart, direction); dispatch(state.tr.setSelection(TextSelection.create(state.doc, contentPos))); return true; } return false; } return false; // No next cell found }; }; function cellHasContentInDirection(cellNode, cellPos, selectionPos, direction) { var hasContent = false; cellNode.content.forEach(function (node, offset) { var nodeStart = cellPos + 1 + offset; var nodeEnd = nodeStart + node.nodeSize; if (direction === 1 && nodeStart > selectionPos && node.isBlock) { hasContent = true; // Content below } else if (direction === -1 && nodeEnd <= selectionPos && node.isBlock) { hasContent = true; // Content above } }); return hasContent; } function getTargetPositionInNextCell(cellNode, nextCellStart, direction) { var contentPos = nextCellStart + 1; // Default: just inside the cell if (cellNode.content.size > 0) { if (direction === 1) { var firstChild = cellNode.firstChild; if (firstChild && firstChild.isBlock) { contentPos = nextCellStart + 1 + (firstChild.isLeaf ? 0 : 1); // Down: Start of first block } } else if (direction === -1) { var lastBlock; var lastOffset = 0; cellNode.content.forEach(function (node, offset) { if (node.isBlock) { lastBlock = node; lastOffset = offset; } }); if (lastBlock) { contentPos = nextCellStart + 1 + lastOffset + (lastBlock.isLeaf ? 0 : lastBlock.nodeSize - 1); } } } return contentPos; }