/**
* 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 {mergeRegister} from '@lexical/utils';
import {$fullReconcile, defineExtension, safeCast} from 'lexical';
import {TableCellNode} from './LexicalTableCellNode';
import {
$isScrollableTablesActive,
setScrollableTablesActive,
TableNode,
} from './LexicalTableNode';
import {
registerTableCellUnmergeTransform,
registerTablePlugin,
registerTableSelectionObserver,
} from './LexicalTablePluginHelpers';
import {TableRowNode} from './LexicalTableRowNode';
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 `
` 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 = defineExtension({
build(editor, config, state) {
return namedSignals(config);
},
config: safeCast
({
hasCellBackgroundColor: true,
hasCellMerge: true,
hasHorizontalScroll: true,
hasNestedTables: false,
hasTabHandler: true,
}),
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);
}
}),
),
);
},
});