uui-editor/src/plugins/tablePlugin/tablePlugin.tsx (175 lines of code) (raw):

import React from 'react'; import { Dropdown } from '@epam/uui-components'; import { useFocused, useReadOnly, useSelected } from 'slate-react'; import { useIsPluginActive, isTextSelected } from '../../helpers'; import { ReactComponent as TableIcon } from '../../icons/table-add.svg'; import { FloatingToolbar } from '../../implementation/PositionedToolbar'; import { ToolbarButton } from '../../implementation/ToolbarButton'; import { DeserializeHtml, PlateEditor, getPluginType, insertNodes, someNode, useEditorRef, withoutNormalizing, isElement, PlatePlugin, setNodes, } from '@udecode/plate-common'; import { ELEMENT_TABLE, ELEMENT_TD, ELEMENT_TH, ELEMENT_TR, TTableElement, TablePlugin, createTablePlugin, getTableColumnCount, getTableGridAbove, useTableMergeState, withTable, } from '@udecode/plate-table'; import { MergeToolbarContent } from './MergeToolbarContent'; import { TableToolbarContent } from './ToolbarContent'; import { createInitialTable, selectFirstCell } from './utils'; import { TableRowElement } from './TableRowElement'; import { TableCellElement } from './TableCellElement'; import { TableElement } from './TableElement'; import { WithToolbarButton } from '../../implementation/Toolbars'; import { TABLE_CELL_TYPE, TABLE_HEADER_CELL_TYPE, TABLE_TYPE, TABLE_ROW_TYPE, DEFAULT_COL_WIDTH } from './constants'; import { normalizeTableCellElement, normalizeTableElement } from '../../migrations/normalizers'; import { DeprecatedTTableCellElement } from '../../migrations/types'; const noop = () => {}; function TableRenderer(props: any) { const editor = useEditorRef(); const isReadonly = useReadOnly(); const isFocused = useFocused(); const isSelected = useSelected(); const cellEntries = getTableGridAbove(editor, { format: 'cell' }); const hasEntries = !!cellEntries?.length; const showToolbar = !isReadonly && isSelected && isFocused && hasEntries; const { canMerge, canUnmerge } = useTableMergeState(); return ( <Dropdown renderTarget={ (innerProps: any) => ( <div ref={ innerProps.ref }> <TableElement { ...props } /> </div> ) } renderBody={ () => ( <FloatingToolbar placement="bottom" children={ canMerge ? ( <MergeToolbarContent /> ) : ( <TableToolbarContent canUnmerge={ canUnmerge } /> ) } editor={ editor } isTable /> ) } onValueChange={ noop } value={ showToolbar } placement="top" /> ); } const getDefaultColWidths = (columnsNumber: number) => Array.from({ length: columnsNumber }, () => DEFAULT_COL_WIDTH); const initDefaultTableColWidth = (tableNode: DeprecatedTTableCellElement): TTableElement => { if (!tableNode.colSizes) { return { ...tableNode, colSizes: getDefaultColWidths(getTableColumnCount(tableNode)), }; } return tableNode; }; // TODO: move to plate const createGetNodeFunc = (type: string) => { const getNode: DeserializeHtml['getNode'] = (element) => { const background = element.style.background || element.style.backgroundColor; if (background) { return { type, background, }; } return { type }; }; return getNode; }; type TablePLuginOptions = WithToolbarButton & TablePlugin; export const tablePlugin = (): PlatePlugin<TablePLuginOptions> => createTablePlugin<TablePLuginOptions>({ overrideByKey: { [ELEMENT_TABLE]: { type: TABLE_TYPE, component: TableRenderer, }, [ELEMENT_TR]: { type: TABLE_ROW_TYPE, component: TableRowElement, }, [ELEMENT_TD]: { type: TABLE_CELL_TYPE, component: TableCellElement, deserializeHtml: { getNode: createGetNodeFunc(TABLE_CELL_TYPE), }, }, [ELEMENT_TH]: { type: TABLE_HEADER_CELL_TYPE, component: TableCellElement, deserializeHtml: { getNode: createGetNodeFunc(TABLE_HEADER_CELL_TYPE), }, }, }, options: { enableMerging: true, bottomBarButton: TableButton, }, withOverrides: (editor, plugin) => { // eslint-disable-next-line no-param-reassign editor = withTable(editor, plugin); const { normalizeNode } = editor; editor.normalizeNode = (entry) => { const [node, path] = entry; if (isElement(node) && node.type === TABLE_TYPE) { const normalized = initDefaultTableColWidth(normalizeTableElement(entry) as DeprecatedTTableCellElement); setNodes( editor, normalized, { at: path }, ); return; } if (isElement(node) && (TABLE_CELL_TYPE === node.type || TABLE_CELL_TYPE === node.type)) { normalizeTableCellElement(editor, entry); } normalizeNode(entry); }; return editor; }, }); export function TableButton({ editor }: { editor: PlateEditor }) { if (!useIsPluginActive(ELEMENT_TABLE)) return null; const onCreateTable = async () => { if (!editor) return; withoutNormalizing(editor, () => { const isCurrentTableSelection = !!someNode(editor, { match: { type: getPluginType(editor, ELEMENT_TABLE) }, }); if (!isCurrentTableSelection) { insertNodes(editor, createInitialTable(editor)); selectFirstCell(editor); } }); }; return ( <ToolbarButton isDisabled={ isTextSelected(editor, true) } onClick={ onCreateTable } icon={ TableIcon } /> ); }