Skip to content

Commit

Permalink
In-place DOM update for TableNode and make TableObserver management r…
Browse files Browse the repository at this point in the history
…obust to updateDOM returning true
  • Loading branch information
etrepum committed Aug 27, 2024
1 parent bdfd45b commit 69a96a2
Show file tree
Hide file tree
Showing 3 changed files with 77 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -136,13 +136,12 @@ function TableCellResizer({editor}: {editor: LexicalEditor}): JSX.Element {

const removeRootListener = editor.registerRootListener(
(rootElement, prevRootElement) => {
rootElement?.addEventListener('mousemove', onMouseMove);
rootElement?.addEventListener('mousedown', onMouseDown);
rootElement?.addEventListener('mouseup', onMouseUp);

prevRootElement?.removeEventListener('mousemove', onMouseMove);
prevRootElement?.removeEventListener('mousedown', onMouseDown);
prevRootElement?.removeEventListener('mouseup', onMouseUp);
rootElement?.addEventListener('mousemove', onMouseMove);
rootElement?.addEventListener('mousedown', onMouseDown);
rootElement?.addEventListener('mouseup', onMouseUp);
},
);

Expand Down
61 changes: 37 additions & 24 deletions packages/lexical-react/src/LexicalTablePlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,40 +110,53 @@ export function TablePlugin({
}, [editor]);

useEffect(() => {
const tableSelections = new Map<NodeKey, TableObserver>();
const tableSelections = new Map<
NodeKey,
[TableObserver, HTMLTableElementWithWithTableSelectionState]
>();

const initializeTableNode = (tableNode: TableNode) => {
const nodeKey = tableNode.getKey();
const tableElement = editor.getElementByKey(
nodeKey,
) as HTMLTableElementWithWithTableSelectionState;
if (tableElement && !tableSelections.has(nodeKey)) {
const tableSelection = applyTableHandlers(
tableNode,
tableElement,
editor,
hasTabHandler,
);
tableSelections.set(nodeKey, tableSelection);
}
const initializeTableNode = (
tableNode: TableNode,
nodeKey: NodeKey,
dom: HTMLElement,
) => {
const tableElement = dom as HTMLTableElementWithWithTableSelectionState;
const tableSelection = applyTableHandlers(
tableNode,
tableElement,
editor,
hasTabHandler,
);
tableSelections.set(nodeKey, [tableSelection, tableElement]);
};

const unregisterMutationListener = editor.registerMutationListener(
TableNode,
(nodeMutations) => {
for (const [nodeKey, mutation] of nodeMutations) {
if (mutation === 'created') {
editor.getEditorState().read(() => {
const tableNode = $getNodeByKey<TableNode>(nodeKey);
if ($isTableNode(tableNode)) {
initializeTableNode(tableNode);
if (mutation === 'created' || mutation === 'updated') {
const tableSelection = tableSelections.get(nodeKey);
const dom = editor.getElementByKey(nodeKey);
if (!(tableSelection && dom === tableSelection[1])) {
// The update created a new DOM node, destroy the existing TableObserver
if (tableSelection) {
tableSelection[0].removeListeners();
tableSelections.delete(nodeKey);
}
});
if (dom !== null) {
// Create a new TableObserver
editor.getEditorState().read(() => {
const tableNode = $getNodeByKey<TableNode>(nodeKey);
if ($isTableNode(tableNode)) {
initializeTableNode(tableNode, nodeKey, dom);
}
});
}
}
} else if (mutation === 'destroyed') {
const tableSelection = tableSelections.get(nodeKey);

if (tableSelection !== undefined) {
tableSelection.removeListeners();
tableSelection[0].removeListeners();
tableSelections.delete(nodeKey);
}
}
Expand All @@ -156,7 +169,7 @@ export function TablePlugin({
unregisterMutationListener();
// Hook might be called multiple times so cleaning up tables listeners as well,
// as it'll be reinitialized during recurring call
for (const [, tableSelection] of tableSelections) {
for (const [, [tableSelection]] of tableSelections) {
tableSelection.removeListeners();
}
};
Expand Down
48 changes: 37 additions & 11 deletions packages/lexical-table/src/LexicalTableNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
*
*/

import type {TableCellNode} from './LexicalTableCellNode';
import type {
DOMConversionMap,
DOMConversionOutput,
Expand All @@ -19,14 +18,18 @@ import type {
Spread,
} from 'lexical';

import {addClassNamesToElement, isHTMLElement} from '@lexical/utils';
import {
addClassNamesToElement,
isHTMLElement,
removeClassNamesFromElement,
} from '@lexical/utils';
import {
$applyNodeReplacement,
$getNearestNodeFromDOMNode,
ElementNode,
} from 'lexical';

import {$isTableCellNode} from './LexicalTableCellNode';
import {$isTableCellNode, TableCellNode} from './LexicalTableCellNode';
import {TableDOMCell, TableDOMTable} from './LexicalTableObserver';
import {$isTableRowNode, TableRowNode} from './LexicalTableRowNode';
import {getTable} from './LexicalTableSelectionHelpers';
Expand All @@ -38,19 +41,36 @@ export type SerializedTableNode = Spread<
SerializedElementNode
>;

function setRowStriping(
dom: HTMLElement,
config: EditorConfig,
rowStriping: boolean,
) {
if (rowStriping) {
addClassNamesToElement(dom, config.theme.tableRowStriping);
dom.setAttribute('data-lexical-row-striping', 'true');
} else {
removeClassNamesFromElement(dom, config.theme.tableRowStriping);
dom.removeAttribute('data-lexical-row-striping');
}
}

/** @noInheritDoc */
export class TableNode extends ElementNode {
/** @internal */
__rowStriping?: boolean;
__rowStriping: boolean;

static getType(): string {
return 'table';
}

static clone(node: TableNode): TableNode {
const tableNode = new TableNode(node.__key);
tableNode.__rowStriping = node.__rowStriping;
return tableNode;
return new TableNode(node.__key);
}

afterCloneFrom(prevNode: this) {
super.afterCloneFrom(prevNode);
this.__rowStriping = prevNode.__rowStriping;
}

static importDOM(): DOMConversionMap | null {
Expand Down Expand Up @@ -87,15 +107,21 @@ export class TableNode extends ElementNode {

addClassNamesToElement(tableElement, config.theme.table);
if (this.__rowStriping) {
addClassNamesToElement(tableElement, config.theme.tableRowStriping);
tableElement.setAttribute('data-lexical-row-striping', 'true');
setRowStriping(tableElement, config, true);
}

return tableElement;
}

updateDOM(prevNode: TableNode): boolean {
return prevNode.__rowStriping !== this.__rowStriping;
updateDOM(
prevNode: TableNode,
dom: HTMLElement,
config: EditorConfig,
): boolean {
if (prevNode.__rowStriping !== this.__rowStriping) {
setRowStriping(dom, config, this.__rowStriping);
}
return false;
}

exportDOM(editor: LexicalEditor): DOMExportOutput {
Expand Down

0 comments on commit 69a96a2

Please sign in to comment.