@lexical/table
Version:
This package provides the Table feature for Lexical.
135 lines (129 loc) • 4.37 kB
text/typescript
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import {effect, namedSignals} from '@lexical/extension';
import {CoreImportExtension, DOMImportExtension} from '@lexical/html';
import {
$fullReconcile,
configExtension,
defineExtension,
mergeRegister,
safeCast,
} from 'lexical';
import {TableCellNode} from './LexicalTableCellNode';
import {
$isScrollableTablesActive,
setScrollableTablesActive,
TableNode,
} from './LexicalTableNode';
import {
registerTableCellUnmergeTransform,
registerTablePlugin,
registerTableSelectionObserver,
} from './LexicalTablePluginHelpers';
import {TableRowNode} from './LexicalTableRowNode';
import {TableImportRules} from './TableImportExtension';
export interface TableConfig {
/**
* When `false` (default `true`), merged cell support (colspan and rowspan) will be disabled and all
* tables will be forced into a regular grid with 1x1 table cells.
*/
hasCellMerge: boolean;
/**
* When `false` (default `true`), the background color of TableCellNode will always be removed.
*/
hasCellBackgroundColor: boolean;
/**
* When `true` (default `true`), the tab key can be used to navigate table cells.
*/
hasTabHandler: boolean;
/**
* When `true` (default `true`), tables will be wrapped in a `<div>` to enable horizontal scrolling
*/
hasHorizontalScroll: boolean;
/**
* When `true` (default `false`), nested tables will be allowed.
*
* @experimental Nested tables are not officially supported.
*/
hasNestedTables: boolean;
}
/**
* Configures {@link TableNode}, {@link TableRowNode}, {@link TableCellNode} and
* registers table behaviors (see {@link TableConfig})
*/
export const TableExtension = /* @__PURE__ */ defineExtension({
build(editor, config, state) {
return namedSignals(config);
},
config: /* @__PURE__ */ safeCast<TableConfig>({
hasCellBackgroundColor: true,
hasCellMerge: true,
hasHorizontalScroll: true,
hasNestedTables: false,
hasTabHandler: true,
}),
dependencies: [
// DOMImportExtension support for the nodes registered here. Inert
// unless the editor routes HTML through the pipeline (e.g. via
// ClipboardDOMImportExtension or $generateNodesFromDOMViaExtension).
CoreImportExtension,
/* @__PURE__ */ configExtension(DOMImportExtension, {
rules: TableImportRules,
}),
],
name: '@lexical/table/Table',
nodes: () => [TableNode, TableRowNode, TableCellNode],
register(editor, config, state) {
const stores = state.getOutput();
return mergeRegister(
effect(() => {
const hasHorizontalScroll = stores.hasHorizontalScroll.value;
const hadHorizontalScroll = $isScrollableTablesActive(editor);
if (hadHorizontalScroll !== hasHorizontalScroll) {
setScrollableTablesActive(editor, hasHorizontalScroll);
// Re-render existing tables through the new scroll-wrapper config
// without cloning every TableNode the way marking them dirty would. A
// full reconcile marks no nodes dirty, so it's deferred (no
// synchronous render from this effect) and produces no history entry.
editor.update($fullReconcile);
}
}),
registerTablePlugin(editor, stores),
effect(() =>
registerTableSelectionObserver(editor, stores.hasTabHandler.value),
),
effect(() =>
stores.hasCellMerge.value
? undefined
: registerTableCellUnmergeTransform(editor),
),
effect(() =>
stores.hasCellBackgroundColor.value
? undefined
: editor.registerNodeTransform(TableCellNode, node => {
if (node.getBackgroundColor() !== null) {
node.setBackgroundColor(null);
}
}),
),
);
},
});
/**
* Bundles {@link TableImportRules} together with the runtime
* {@link TableExtension}.
*
* @experimental
* @deprecated {@link TableExtension} now registers
* {@link TableImportRules} (and `CoreImportExtension`) itself — depend on
* it directly instead.
*/
export const TableImportExtension = /* @__PURE__ */ defineExtension({
dependencies: [TableExtension],
name: '@lexical/table/Import',
});