slate-edit-table
Version:
A Slate plugin to handle keyboard events in tables.
212 lines (174 loc) • 4.71 kB
Flow
// @flow
import { Record } from 'immutable';
import { Block, type Document } from 'slate';
import type Options from '../options';
class TablePosition extends Record({
tableBlock: null,
rowBlock: null,
cellBlock: null,
contentBlock: null
}) {
// Block container for the table
tableBlock: ?Block;
// Block for current row
rowBlock: ?Block;
// Block for current cell
cellBlock: ?Block;
// Current content block in the cell
contentBlock: ?Block;
/**
* Create a new instance of a TablePosition from a Slate document
* and a node key.
*/
static create(
opts: Options,
document: Document,
key: string
): TablePosition {
const node = document.getDescendant(key);
const ancestors = document.getAncestors(key).push(node);
const tableBlock = ancestors.findLast(p => p.type === opts.typeTable);
const rowBlock = ancestors.findLast(p => p.type === opts.typeRow);
const cellBlock = ancestors.findLast(p => p.type === opts.typeCell);
const contentBlock = ancestors
.skipUntil(ancestor => ancestor === cellBlock)
.skip(1)
.first();
return new TablePosition({
tableBlock,
rowBlock,
cellBlock,
contentBlock
});
}
get table(): Block {
if (!this.tableBlock) {
throw new Error('Not in a table');
}
return this.tableBlock;
}
get row(): Block {
if (!this.rowBlock) {
throw new Error('Not in a row');
}
return this.rowBlock;
}
get cell(): Block {
if (!this.cellBlock) {
throw new Error('Not in a cell');
}
return this.cellBlock;
}
/**
* Check to see if this position is within a cell
*/
isInCell(): boolean {
return Boolean(this.cellBlock);
}
/**
* Check to see if this position is within a row
*/
isInRow(): boolean {
return Boolean(this.rowBlock);
}
/**
* Check to see if this position is within a table
*/
isInTable(): boolean {
return Boolean(this.tableBlock);
}
/**
* Check to see if this position is at the top of the cell.
*/
isTopOfCell(): boolean {
const { contentBlock, cellBlock } = this;
if (!contentBlock || !cellBlock) {
return false;
}
const { nodes } = cellBlock;
const index = nodes.findIndex(block => block.key == contentBlock.key);
return index == 0;
}
/**
* Check to see if this position is at the bottom of the cell.
*/
isBottomOfCell(): boolean {
const { contentBlock, cellBlock } = this;
if (!contentBlock || !cellBlock) {
return false;
}
const { nodes } = cellBlock;
const index = nodes.findIndex(block => block.key == contentBlock.key);
return index == nodes.size - 1;
}
/**
* Get count of columns
*/
getWidth(): number {
const { table } = this;
const rows = table.nodes;
const cells = rows.get(0).nodes;
return cells.size;
}
/**
* Get count of rows
*/
getHeight(): number {
const { table } = this;
const rows = table.nodes;
return rows.size;
}
/**
* Get index of current row in the table.
*/
getRowIndex(): number {
const { table, row } = this;
const rows = table.nodes;
return rows.findIndex(x => x === row);
}
/**
* Get index of current column in the row.
*/
getColumnIndex(): number {
const { row, cell } = this;
const cells = row.nodes;
return cells.findIndex(x => x === cell);
}
/**
* True if on first cell of the table
*/
isFirstCell(): boolean {
return this.isFirstRow() && this.isFirstColumn();
}
/**
* True if on last cell of the table
*/
isLastCell(): boolean {
return this.isLastRow() && this.isLastColumn();
}
/**
* True if on first row
*/
isFirstRow(): boolean {
return this.getRowIndex() === 0;
}
/**
* True if on last row
*/
isLastRow(): boolean {
return this.getRowIndex() === this.getHeight() - 1;
}
/**
* True if on first column
*/
isFirstColumn(): boolean {
return this.getColumnIndex() === 0;
}
/**
* True if on last column
*/
isLastColumn(): boolean {
return this.getColumnIndex() === this.getWidth() - 1;
}
}
export default TablePosition;