diff --git a/packages/lexical-playground/src/Editor.tsx b/packages/lexical-playground/src/Editor.tsx index 31d8a38d433..a112120f17a 100644 --- a/packages/lexical-playground/src/Editor.tsx +++ b/packages/lexical-playground/src/Editor.tsx @@ -92,6 +92,7 @@ export default function Editor(): JSX.Element { shouldPreserveNewLinesInMarkdown, tableCellMerge, tableCellBackgroundColor, + tableHorizontalScroll, }, } = useSettings(); const isEditable = useLexicalEditable(); @@ -182,6 +183,7 @@ export default function Editor(): JSX.Element { diff --git a/packages/lexical-playground/src/Settings.tsx b/packages/lexical-playground/src/Settings.tsx index b015f570d9d..a11bc794032 100644 --- a/packages/lexical-playground/src/Settings.tsx +++ b/packages/lexical-playground/src/Settings.tsx @@ -32,6 +32,7 @@ export default function Settings(): JSX.Element { showTableOfContents, shouldUseLexicalContextMenu, shouldPreserveNewLinesInMarkdown, + tableHorizontalScroll, }, } = useSettings(); useEffect(() => { @@ -167,6 +168,13 @@ export default function Settings(): JSX.Element { checked={shouldPreserveNewLinesInMarkdown} text="Preserve newlines in Markdown" /> + { + setOption('tableHorizontalScroll', !tableHorizontalScroll); + }} + checked={tableHorizontalScroll} + text="Tables have horizontal scroll" + /> ) : null} diff --git a/packages/lexical-playground/src/appSettings.ts b/packages/lexical-playground/src/appSettings.ts index d698af382c2..696fb961144 100644 --- a/packages/lexical-playground/src/appSettings.ts +++ b/packages/lexical-playground/src/appSettings.ts @@ -29,6 +29,7 @@ export const DEFAULT_SETTINGS = { showTreeView: true, tableCellBackgroundColor: true, tableCellMerge: true, + tableHorizontalScroll: false, } as const; // These are mutated in setupEnv diff --git a/packages/lexical-playground/src/index.css b/packages/lexical-playground/src/index.css index 40b443d0976..6ca4b2786c7 100644 --- a/packages/lexical-playground/src/index.css +++ b/packages/lexical-playground/src/index.css @@ -74,17 +74,18 @@ header h1 { .editor-scroller { min-height: 150px; + max-width: 100%; border: 0; display: flex; position: relative; outline: 0; z-index: 0; - overflow: auto; resize: vertical; } .editor { flex: auto; + max-width: 100%; position: relative; resize: vertical; z-index: -1; diff --git a/packages/lexical-playground/src/plugins/TableActionMenuPlugin/index.tsx b/packages/lexical-playground/src/plugins/TableActionMenuPlugin/index.tsx index 60a09ab8a09..add698b24d3 100644 --- a/packages/lexical-playground/src/plugins/TableActionMenuPlugin/index.tsx +++ b/packages/lexical-playground/src/plugins/TableActionMenuPlugin/index.tsx @@ -229,13 +229,13 @@ function TableActionMenu({ editor.update(() => { if (tableCellNode.isAttached()) { const tableNode = $getTableNodeFromLexicalNodeOrThrow(tableCellNode); - const tableElement = editor.getElementByKey( - tableNode.getKey(), - ) as HTMLTableElementWithWithTableSelectionState; + const tableNodeElement = editor.getElementByKey(tableNode.getKey()); - if (!tableElement) { + if (!tableNodeElement) { throw new Error('Expected to find tableElement in DOM'); } + const tableElement = tableNode.getDOMSlot(tableNodeElement) + .element as HTMLTableElementWithWithTableSelectionState; const tableObserver = getTableObserverFromTableElement(tableElement); if (tableObserver !== null) { diff --git a/packages/lexical-react/src/LexicalTablePlugin.ts b/packages/lexical-react/src/LexicalTablePlugin.ts index e8b512eb790..6b8a8027b05 100644 --- a/packages/lexical-react/src/LexicalTablePlugin.ts +++ b/packages/lexical-react/src/LexicalTablePlugin.ts @@ -25,6 +25,7 @@ import { $isTableRowNode, applyTableHandlers, INSERT_TABLE_COMMAND, + setScrollableTablesActive, TableCellNode, TableNode, TableRowNode, @@ -47,13 +48,19 @@ export function TablePlugin({ hasCellMerge = true, hasCellBackgroundColor = true, hasTabHandler = true, + hasHorizontalScroll = false, }: { hasCellMerge?: boolean; hasCellBackgroundColor?: boolean; hasTabHandler?: boolean; + hasHorizontalScroll?: boolean; }): JSX.Element | null { const [editor] = useLexicalComposerContext(); + useEffect(() => { + setScrollableTablesActive(editor, hasHorizontalScroll); + }, [editor, hasHorizontalScroll]); + useEffect(() => { if (!editor.hasNodes([TableNode, TableCellNode, TableRowNode])) { invariant( diff --git a/packages/lexical-table/src/LexicalTableNode.ts b/packages/lexical-table/src/LexicalTableNode.ts index be988c5af96..47f9d0812f7 100644 --- a/packages/lexical-table/src/LexicalTableNode.ts +++ b/packages/lexical-table/src/LexicalTableNode.ts @@ -6,18 +6,6 @@ * */ -import type { - DOMConversionMap, - DOMConversionOutput, - DOMExportOutput, - EditorConfig, - LexicalEditor, - LexicalNode, - NodeKey, - SerializedElementNode, - Spread, -} from 'lexical'; - import { addClassNamesToElement, isHTMLElement, @@ -25,8 +13,20 @@ import { } from '@lexical/utils'; import { $applyNodeReplacement, + $getEditor, $getNearestNodeFromDOMNode, + DOMConversionMap, + DOMConversionOutput, + DOMExportOutput, + EditorConfig, + ElementDOMSlot, ElementNode, + LexicalEditor, + LexicalNode, + NodeKey, + SerializedElementNode, + setDOMUnmanaged, + Spread, } from 'lexical'; import {PIXEL_VALUE_REG_EXP} from './constants'; @@ -79,6 +79,25 @@ function setRowStriping( } } +const scrollableEditors = new WeakSet(); + +export function $isScrollableTablesActive( + editor: LexicalEditor = $getEditor(), +): boolean { + return scrollableEditors.has(editor); +} + +export function setScrollableTablesActive( + editor: LexicalEditor, + active: boolean, +) { + if (active) { + scrollableEditors.add(editor); + } else { + scrollableEditors.delete(editor); + } +} + /** @noInheritDoc */ export class TableNode extends ElementNode { /** @internal */ @@ -142,6 +161,16 @@ export class TableNode extends ElementNode { }; } + getDOMSlot(element: HTMLElement): ElementDOMSlot { + const tableElement = + element.dataset.lexicalScrollable === 'true' + ? element.querySelector('table') || element + : element; + return super + .getDOMSlot(tableElement) + .withAfter(tableElement.querySelector('colgroup')); + } + createDOM(config: EditorConfig, editor?: LexicalEditor): HTMLElement { const tableElement = document.createElement('table'); const colGroup = document.createElement('colgroup'); @@ -152,11 +181,19 @@ export class TableNode extends ElementNode { this.getColumnCount(), this.getColWidths(), ); + setDOMUnmanaged(colGroup); addClassNamesToElement(tableElement, config.theme.table); if (this.__rowStriping) { setRowStriping(tableElement, config, true); } + if ($isScrollableTablesActive()) { + const wrapperElement = document.createElement('div'); + wrapperElement.dataset.lexicalScrollable = 'true'; + wrapperElement.style.overflowX = 'auto'; + wrapperElement.appendChild(tableElement); + return wrapperElement; + } return tableElement; } @@ -177,6 +214,13 @@ export class TableNode extends ElementNode { return { ...super.exportDOM(editor), after: (tableElement) => { + if ( + tableElement && + isHTMLElement(tableElement) && + tableElement.dataset.lexicalScrollable === 'true' + ) { + tableElement = tableElement.querySelector('table'); + } if (tableElement) { const newElement = tableElement.cloneNode() as ParentNode; const colGroup = document.createElement('colgroup'); diff --git a/packages/lexical-table/src/index.ts b/packages/lexical-table/src/index.ts index 2429eb608a9..8ab05308268 100644 --- a/packages/lexical-table/src/index.ts +++ b/packages/lexical-table/src/index.ts @@ -22,7 +22,9 @@ export type {SerializedTableNode} from './LexicalTableNode'; export { $createTableNode, $getElementForTableNode, + $isScrollableTablesActive, $isTableNode, + setScrollableTablesActive, TableNode, } from './LexicalTableNode'; export type {TableDOMCell} from './LexicalTableObserver';