slate-edit-table
Version:
A Slate plugin to handle keyboard events in tables.
129 lines (117 loc) • 4.41 kB
Flow
// @flow
import { Block, type Change } from 'slate';
import {
CHILD_OBJECT_INVALID,
CHILD_TYPE_INVALID,
PARENT_TYPE_INVALID
} from 'slate-schema-violations';
import { createRow, createCell } from '../utils';
import type Options from '../options';
/*
* Returns a schema definition for the plugin
*/
function schema(opts: Options): Object {
return {
blocks: {
[opts.typeTable]: {
nodes: [{ types: [opts.typeRow] }],
normalize(change: Change, violation: string, context: Object) {
switch (violation) {
case CHILD_TYPE_INVALID:
return onlyRowsInTable(opts, change, context);
default:
return undefined;
}
}
},
[opts.typeRow]: {
nodes: [{ types: [opts.typeCell] }],
parent: { types: [opts.typeTable] },
normalize(change: Change, violation: string, context: Object) {
switch (violation) {
case CHILD_TYPE_INVALID:
return onlyCellsInRow(opts, change, context);
case PARENT_TYPE_INVALID:
return rowOnlyInTable(opts, change, context);
default:
return undefined;
}
}
},
[opts.typeCell]: {
nodes: [{ objects: ['block'] }],
parent: { types: [opts.typeRow] },
normalize(change: Change, violation: string, context: Object) {
switch (violation) {
case CHILD_OBJECT_INVALID:
return onlyBlocksInCell(opts, change, context);
case PARENT_TYPE_INVALID:
return cellOnlyInRow(opts, change, context);
default:
return undefined;
}
}
}
}
};
}
/*
* Non-row nodes will be removed by slate but if all of the table's
* children are invalids then we seed it with a basic structure.
*/
function onlyRowsInTable(opts: Options, change: Change, context: Object) {
const invalids = context.node.nodes.filter(
child => child.type !== opts.typeRow
);
if (invalids.size === context.node.nodes.size) {
invalids.forEach(invalid =>
change.removeNodeByKey(invalid.key, { normalize: false })
);
change.insertNodeByKey(context.node.key, 0, createRow(opts, 1), {
normalize: false
});
}
// No operations are made which means Slate will handle the normalization
return undefined;
}
/*
* A row's children must be cells.
* If they're not then we wrap them within a cell.
*/
function onlyCellsInRow(opts: Options, change: Change, context: Object) {
const cell = createCell(opts, []);
const index = context.node.nodes.findIndex(
child => child.key === context.child.key
);
change.insertNodeByKey(context.node.key, index, cell, { normalize: false });
change.moveNodeByKey(context.child.key, cell.key, 0, { normalize: false });
}
/*
* Rows can't live outside a table, if one is found then we wrap it within a table.
*/
function rowOnlyInTable(opts: Options, change: Change, context: Object) {
return change.wrapBlockByKey(context.node.key, opts.typeTable);
}
/*
* A cell's children must be "block"s.
* If they're not then we wrap them within a block with a type of opts.typeContent
*/
function onlyBlocksInCell(opts: Options, change: Change, context: Object) {
const block = Block.create({
type: opts.typeContent
});
change.insertNodeByKey(context.node.key, 0, block, { normalize: false });
const inlines = context.node.nodes.filter(node => node.object !== 'block');
inlines.forEach((inline, index) => {
change.moveNodeByKey(inline.key, block.key, index, {
normalize: false
});
});
}
/*
* Cells can't live outside a row, if one is found then we wrap it within a row.
*/
function cellOnlyInRow(opts: Options, change: Change, context: Object) {
return change.wrapBlockByKey(context.node.key, opts.typeRow);
}
export default schema;