UNPKG

@atlaskit/editor-plugin-table

Version:

Table plugin for the @atlaskit/editor

212 lines (210 loc) 7.17 kB
import { parsePx } from '@atlaskit/editor-common/utils'; import { safeInsert } from '@atlaskit/editor-prosemirror/utils'; import { TableMap } from '@atlaskit/editor-tables/table-map'; import { findTable, getSelectionRect, isRowSelected } from '@atlaskit/editor-tables/utils'; import { TableCssClassName as ClassName } from '../../types'; import { tableDeleteButtonSize } from '../../ui/consts'; export const getRowHeights = tableRef => { const heights = []; const tableBody = tableRef.querySelector('tbody'); if (tableBody) { const rows = tableBody.childNodes; for (let i = 0, count = rows.length; i < count; i++) { const row = rows[i]; heights[i] = row.getBoundingClientRect().height + 1; // padding only gets applied when the container has sticky if (row.classList.contains('sticky') && i === 0) { const styles = window.getComputedStyle(row); const paddingTop = parsePx(styles.paddingTop || ''); heights[i] -= paddingTop ? paddingTop + 1 : +1; } } } return heights; }; export const getRowDeleteButtonParams = (rowsHeights, selection, offsetTop = 0) => { const rect = getSelectionRect(selection); if (!rect) { return null; } let height = 0; let offset = offsetTop; // find the rows before the selection for (let i = 0; i < rect.top; i++) { const rowHeight = rowsHeights[i]; if (rowHeight) { offset += rowHeight - 1; } } // these are the selected rows widths const indexes = []; for (let i = rect.top; i < rect.bottom; i++) { const rowHeight = rowsHeights[i]; if (rowHeight) { height += rowHeight - 1; indexes.push(i); } } const top = offset + height / 2 - tableDeleteButtonSize / 2; return { top, indexes }; }; export const getRowsParams = rowsHeights => { const rows = []; for (let i = 0, count = rowsHeights.length; i < count; i++) { const height = rowsHeights[i]; if (!height) { continue; } let endIndex = rowsHeights.length; for (let k = i + 1, count = rowsHeights.length; k < count; k++) { if (rowsHeights[k]) { endIndex = k; break; } } rows.push({ startIndex: i, endIndex, height }); } return rows; }; /** * Returns the visual row index that the mouse pointer is over, by walking the row heights * inside `tbody` and finding the row whose vertical range contains `mouseEvent.clientY`. * * When `rowIndexRange` is provided, the search is restricted to rows in that range (the * `endIndex` is exclusive). This is the hot path used on `mousemove` when hovering over a * row-spanned cell — restricting the range to `[startIndex, endIndex)` keeps the number * of forced layout reads bounded by the row-span size, not the table size. * * Returns `undefined` when the mouse is above the search range or below it (so callers can * fall back to the HTML row index). */ export const getRowIndexByMousePosition = (tableRef, mouseEvent, rowIndexRange) => { var _rowIndexRange$startI, _rowIndexRange$endInd; const tableBody = tableRef.querySelector('tbody'); if (!tableBody) { return undefined; } const rows = tableBody.children; const startIndex = (_rowIndexRange$startI = rowIndexRange === null || rowIndexRange === void 0 ? void 0 : rowIndexRange.startIndex) !== null && _rowIndexRange$startI !== void 0 ? _rowIndexRange$startI : 0; const endIndex = Math.min((_rowIndexRange$endInd = rowIndexRange === null || rowIndexRange === void 0 ? void 0 : rowIndexRange.endIndex) !== null && _rowIndexRange$endInd !== void 0 ? _rowIndexRange$endInd : rows.length, rows.length); if (startIndex >= endIndex) { return undefined; } const firstRowRect = rows[startIndex].getBoundingClientRect(); if (mouseEvent.clientY < firstRowRect.top) { return undefined; } let rowBottom = firstRowRect.bottom; if (mouseEvent.clientY < rowBottom) { return startIndex; } for (let rowIndex = startIndex + 1; rowIndex < endIndex; rowIndex++) { const rowRect = rows[rowIndex].getBoundingClientRect(); rowBottom = rowRect.bottom; if (mouseEvent.clientY < rowBottom) { return rowIndex; } } return undefined; }; export const getRowClassNames = (index, selection, hoveredRows = [], isInDanger, isResizing) => { const classNames = []; if (isRowSelected(index)(selection) || hoveredRows.indexOf(index) > -1 && !isResizing) { classNames.push(ClassName.HOVERED_CELL_ACTIVE); if (isInDanger) { classNames.push(ClassName.HOVERED_CELL_IN_DANGER); } } return classNames.join(' '); }; export const copyPreviousRow = schema => insertNewRowIndex => tr => { const table = findTable(tr.selection); if (!table) { return tr; } const map = TableMap.get(table.node); const copyPreviousRowIndex = insertNewRowIndex - 1; if (insertNewRowIndex <= 0) { throw Error(`Row Index less or equal 0 isn't not allowed since there is not a previous to copy`); } if (insertNewRowIndex > map.height) { return tr; } const tableNode = table.node; const { nodes: { tableRow } } = schema; const cellsInRow = map.cellsInRect({ left: 0, right: map.width, top: copyPreviousRowIndex, bottom: copyPreviousRowIndex + 1 }); const offsetIndexPosition = copyPreviousRowIndex * map.width; const offsetNextLineIndexPosition = insertNewRowIndex * map.width; const cellsPositionsInOriginalRow = map.map.slice(offsetIndexPosition, offsetIndexPosition + map.width); const cellsPositionsInNextRow = map.map.slice(offsetNextLineIndexPosition, offsetNextLineIndexPosition + map.width); const cells = []; const fixRowspans = []; for (let i = 0; i < cellsPositionsInOriginalRow.length;) { const pos = cellsPositionsInOriginalRow[i]; const documentCellPos = pos + table.start; const node = tr.doc.nodeAt(documentCellPos); if (!node) { continue; } const attributes = { ...node.attrs, colspan: 1, rowspan: 1 }; const newCell = node.type.createAndFill(attributes); if (!newCell) { return tr; } if (cellsPositionsInNextRow.indexOf(pos) > -1) { fixRowspans.push({ pos: documentCellPos, node }); } else if (cellsInRow.indexOf(pos) > -1) { if (node.attrs.colspan > 1) { const newCellWithColspanFixed = node.type.createAndFill({ ...attributes, colspan: node.attrs.colspan }); if (!newCellWithColspanFixed) { return tr; } cells.push(newCellWithColspanFixed); i = i + node.attrs.colspan; continue; } cells.push(newCell); } else { cells.push(newCell); } i++; } fixRowspans.forEach(cell => { tr.setNodeMarkup(cell.pos, undefined, { ...cell.node.attrs, rowspan: cell.node.attrs.rowspan + 1 }); }); const cloneRow = tableNode.child(copyPreviousRowIndex); let rowPos = table.start; for (let i = 0; i < insertNewRowIndex; i++) { rowPos += tableNode.child(i).nodeSize; } return safeInsert(tableRow.createChecked(cloneRow.attrs, cells, cloneRow.marks), rowPos)(tr); };