Lexical: Added table creator UI

This commit is contained in:
Dan Brown 2024-06-21 16:18:44 +01:00
parent f47f7dd9d2
commit ac01c62e6e
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
5 changed files with 119 additions and 5 deletions

View File

@ -1,13 +1,14 @@
import { import {
$createParagraphNode, $createParagraphNode, $getRoot,
$getSelection, $getSelection,
$isTextNode, $isTextNode,
BaseSelection, BaseSelection, ElementNode,
LexicalEditor, LexicalNode, TextFormatType LexicalEditor, LexicalNode, TextFormatType
} from "lexical"; } from "lexical";
import {LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes"; import {LexicalElementNodeCreator, LexicalNodeMatcher} from "./nodes";
import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils"; import {$getNearestBlockElementAncestorOrThrow} from "@lexical/utils";
import {$setBlocksType} from "@lexical/selection"; import {$setBlocksType} from "@lexical/selection";
import {$createDetailsNode} from "./nodes/details";
export function el(tag: string, attrs: Record<string, string|null> = {}, children: (string|HTMLElement)[] = []): HTMLElement { export function el(tag: string, attrs: Record<string, string|null> = {}, children: (string|HTMLElement)[] = []): HTMLElement {
const el = document.createElement(tag); const el = document.createElement(tag);
@ -77,4 +78,15 @@ export function toggleSelectionBlockNodeType(editor: LexicalEditor, matcher: Lex
$setBlocksType(selection, creator); $setBlocksType(selection, creator);
} }
}); });
}
export function insertNewBlockNodeAtSelection(node: LexicalNode) {
const selection = $getSelection();
const blockElement = selection ? $getNearestBlockElementAncestorOrThrow(selection.getNodes()[0]) : null;
if (blockElement) {
blockElement.insertAfter(node);
} else {
$getRoot().append(node);
}
} }

View File

@ -236,6 +236,10 @@ export const link: EditorButtonDefinition = {
} }
}; };
export const table: EditorBasicButtonDefinition = {
label: 'Table',
};
export const image: EditorButtonDefinition = { export const image: EditorButtonDefinition = {
label: 'Insert/Edit Image', label: 'Insert/Edit Image',
icon: imageIcon, icon: imageIcon,

View File

@ -0,0 +1,80 @@
import {el, insertNewBlockNodeAtSelection} from "../../../helpers";
import {EditorUiElement} from "../core";
import {$createTableNodeWithDimensions} from "@lexical/table";
export class EditorTableCreator extends EditorUiElement {
buildDOM(): HTMLElement {
const size = 10;
const rows: HTMLElement[] = [];
const cells: HTMLElement[] = [];
for (let row = 1; row < size + 1; row++) {
const rowCells = [];
for (let column = 1; column < size + 1; column++) {
const cell = el('div', {
class: 'editor-table-creator-cell',
'data-rows': String(row),
'data-columns': String(column),
});
rowCells.push(cell);
cells.push(cell);
}
rows.push(el('div', {
class: 'editor-table-creator-row'
}, rowCells));
}
const display = el('div', {class: 'editor-table-creator-display'}, ['0 x 0']);
const grid = el('div', {class: 'editor-table-creator-grid'}, rows);
grid.addEventListener('mousemove', event => {
const cell = (event.target as HTMLElement).closest('.editor-table-creator-cell') as HTMLElement|null;
if (cell) {
const row = Number(cell.dataset.rows || 0);
const column = Number(cell.dataset.columns || 0);
this.updateGridSelection(row, column, cells, display)
}
});
grid.addEventListener('click', event => {
const cell = (event.target as HTMLElement).closest('.editor-table-creator-cell');
if (cell) {
this.onCellClick(cell as HTMLElement);
}
});
grid.addEventListener('mouseleave', event => {
this.updateGridSelection(0, 0, cells, display);
});
return el('div', {
class: 'editor-table-creator',
}, [
grid,
display,
]);
}
updateGridSelection(rows: number, columns: number, cells: HTMLElement[], display: HTMLElement) {
for (const cell of cells) {
const active = Number(cell.dataset.rows) <= rows && Number(cell.dataset.columns) <= columns;
cell.classList.toggle('active', active);
}
display.textContent = `${rows} x ${columns}`;
}
onCellClick(cell: HTMLElement) {
const rows = Number(cell.dataset.rows || 0);
const columns = Number(cell.dataset.columns || 0);
if (rows < 1 || columns < 1) {
return;
}
this.getContext().editor.update(() => {
const table = $createTableNodeWithDimensions(rows, columns, false);
insertNewBlockNodeAtSelection(table);
});
}
}

View File

@ -5,7 +5,7 @@ import {
h2, h3, h4, h5, highlightColor, image, h2, h3, h4, h5, highlightColor, image,
infoCallout, italic, link, numberList, paragraph, infoCallout, italic, link, numberList, paragraph,
redo, source, strikethrough, subscript, redo, source, strikethrough, subscript,
successCallout, superscript, taskList, textColor, underline, successCallout, superscript, table, taskList, textColor, underline,
undo, undo,
warningCallout warningCallout
} from "./defaults/button-definitions"; } from "./defaults/button-definitions";
@ -15,8 +15,7 @@ import {EditorFormatMenu} from "./framework/blocks/format-menu";
import {FormatPreviewButton} from "./framework/blocks/format-preview-button"; import {FormatPreviewButton} from "./framework/blocks/format-preview-button";
import {EditorDropdownButton} from "./framework/blocks/dropdown-button"; import {EditorDropdownButton} from "./framework/blocks/dropdown-button";
import {EditorColorPicker} from "./framework/blocks/color-picker"; import {EditorColorPicker} from "./framework/blocks/color-picker";
import {EditorTableCreator} from "./framework/blocks/table-creator";
console.log(undo);
export function getMainEditorFullToolbar(): EditorContainerUiElement { export function getMainEditorFullToolbar(): EditorContainerUiElement {
return new EditorSimpleClassContainer('editor-toolbar-main', [ return new EditorSimpleClassContainer('editor-toolbar-main', [
@ -61,6 +60,9 @@ export function getMainEditorFullToolbar(): EditorContainerUiElement {
// Insert types // Insert types
new EditorButton(link), new EditorButton(link),
new EditorDropdownButton(table, [
new EditorTableCreator(),
]),
new EditorButton(image), new EditorButton(image),
new EditorButton(details), new EditorButton(details),

View File

@ -100,6 +100,22 @@
z-index: 3; z-index: 3;
box-shadow: 0 0 4px 1px rgba(0, 0, 0, 0.25); box-shadow: 0 0 4px 1px rgba(0, 0, 0, 0.25);
} }
.editor-table-creator-row {
display: flex;
}
.editor-table-creator-cell {
border: 1px solid #DDD;
width: 15px;
height: 15px;
cursor: pointer;
&.active {
background-color: var(--editor-color-primary);
}
}
.editor-table-creator-display {
text-align: center;
padding: 0.2em;
}
// In-editor elements // In-editor elements
.editor-image-wrap { .editor-image-wrap {