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';