/** * External dependencies */ import classnames from 'classnames'; /** * WordPress dependencies */ import { __ } from '@wordpress/i18n'; import { useState } from '@wordpress/element'; import { useSelect } from '@wordpress/data'; // @ts-ignore import { InspectorControls, BlockControls, useBlockProps } from '@wordpress/block-editor'; // @ts-ignore import { ToolbarDropdownMenu, PanelBody, Toolbar, Slot } from '@wordpress/components'; import { blockTable, justifyLeft, justifyCenter, justifyRight, tableColumnAfter, tableColumnBefore, tableColumnDelete, tableRowAfter, tableRowBefore, tableRowDelete, } from '@wordpress/icons'; /** * Internal dependencies */ import './editor.scss'; import { STORE_NAME } from './store'; import { TableSettings, TableCaptionSettings, TableCellSettings } from './settings'; import { Table, TablePlaceholder, TableCaption } from './elements'; import { insertRow, deleteRow, insertColumn, deleteColumn, mergeCells, splitMergedCells, hasMergedCells, isRectangleSelected, toTableAttributes, toVirtualTable, isEmptySection, VCell, } from './utils/table-state'; import { convertToObject } from './utils/style-converter'; import { mergeCell, splitCell } from './icons'; import type { BlockEditProps } from '@wordpress/blocks'; import type { BlockAttributes, SectionName } from './BlockAttributes'; import type { KeyboardEvent } from 'react'; const justifyIcons = { left: justifyLeft, center: justifyCenter, right: justifyRight, }; function TableEdit( props: BlockEditProps< BlockAttributes > ) { const { attributes, setAttributes } = props; const { contentJustification, tableStyles, captionStyles, captionSide } = attributes; const [ selectedCells, setSelectedCells ] = useState< VCell[] | undefined >(); const [ selectedLine, setSelectedLine ] = useState< | { sectionName?: SectionName; rowIndex?: number; } | { vColIndex: number } >(); const [ selectMode, setSelectMode ] = useState< string >( '' ); const tableStylesObj = convertToObject( tableStyles ); const captionStylesObj = convertToObject( captionStyles ); const options = useSelect< { // eslint-disable-next-line camelcase show_dot_on_th: boolean; // eslint-disable-next-line camelcase show_control_button: boolean; } >( ( select ) => select( STORE_NAME ).getOptions() ); // Create virtual table object with the cells placed in positions based on how they actually look. const vTable = toVirtualTable( attributes ); // Monitor pressed key to determine whether multi-select mode or range select mode. const onKeyDown = ( event: KeyboardEvent ) => { if ( event.shiftKey ) { setSelectMode( 'range' ); } else if ( event.ctrlKey || event.metaKey ) { setSelectMode( 'multi' ); } }; const onKeyUp = () => { setSelectMode( '' ); }; const onChangeContentJustification = ( value: keyof typeof justifyIcons ) => { const newValue = contentJustification === value ? undefined : value; setAttributes( { contentJustification: newValue } ); }; const onInsertRow = ( offset: number ) => { if ( ! selectedCells || selectedCells.length !== 1 ) return; const { sectionName, rowIndex, rowSpan } = selectedCells[ 0 ]; // Calculate row index to be inserted considering rowspan of the selected cell. const insertRowIndex = offset === 0 ? rowIndex : rowIndex + offset + rowSpan - 1; const newVTable = insertRow( vTable, { sectionName, rowIndex: insertRowIndex } ); setAttributes( toTableAttributes( newVTable ) ); setSelectedCells( undefined ); setSelectedLine( undefined ); }; const onDeleteRow = () => { if ( ! selectedCells || selectedCells.length !== 1 ) return; const { sectionName, rowIndex } = selectedCells[ 0 ]; // Do not allow tbody to be empty for table with thead /tfoot sections. if ( sectionName === 'body' && vTable.body.length === 1 && ( ! isEmptySection( vTable.head ) || ! isEmptySection( vTable.foot ) ) ) { // eslint-disable-next-line no-alert, no-undef alert( __( 'The table body must have one or more rows.', 'flexible-table-block' ) ); return; } const newVTable = deleteRow( vTable, { sectionName, rowIndex } ); setAttributes( toTableAttributes( newVTable ) ); setSelectedCells( undefined ); setSelectedLine( undefined ); }; const onInsertColumn = ( offset: number ) => { if ( ! selectedCells || selectedCells.length !== 1 ) return; const { vColIndex, colSpan } = selectedCells[ 0 ]; // Calculate column index to be inserted considering colspan of the selected cell. const insertVColIndex = offset === 0 ? vColIndex : vColIndex + offset + colSpan - 1; const newVTable = insertColumn( vTable, { vColIndex: insertVColIndex } ); setAttributes( toTableAttributes( newVTable ) ); setSelectedCells( undefined ); setSelectedLine( undefined ); }; const onDeleteColumn = () => { if ( ! selectedCells || selectedCells.length !== 1 ) return; const { vColIndex } = selectedCells[ 0 ]; const newVTable = deleteColumn( vTable, { vColIndex } ); setAttributes( toTableAttributes( newVTable ) ); setSelectedCells( undefined ); setSelectedLine( undefined ); }; const onMergeCells = () => { const newVTable = mergeCells( vTable, selectedCells ); setAttributes( toTableAttributes( newVTable ) ); setSelectedCells( undefined ); setSelectedLine( undefined ); }; const onSplitMergedCells = () => { const newVTable = splitMergedCells( vTable, selectedCells ); setAttributes( toTableAttributes( newVTable ) ); setSelectedCells( undefined ); setSelectedLine( undefined ); }; const TableJustifyControls = [ { icon: justifyLeft, title: __( 'Justify table left', 'flexible-table-block' ), isActive: contentJustification === 'left', onClick: () => onChangeContentJustification( 'left' ), }, { icon: justifyCenter, title: __( 'Justify table center', 'flexible-table-block' ), isActive: contentJustification === 'center', onClick: () => onChangeContentJustification( 'center' ), }, { icon: justifyRight, title: __( 'Justify table right', 'flexible-table-block' ), isActive: contentJustification === 'right', onClick: () => onChangeContentJustification( 'right' ), }, ]; const TableEditControls = [ { icon: tableRowBefore, title: __( 'Insert row before', 'flexible-table-block' ), isDisabled: ( selectedCells || [] ).length !== 1, onClick: () => onInsertRow( 0 ), }, { icon: tableRowAfter, title: __( 'Insert row after', 'flexible-table-block' ), isDisabled: ( selectedCells || [] ).length !== 1, onClick: () => onInsertRow( 1 ), }, { icon: tableRowDelete, title: __( 'Delete row', 'flexible-table-block' ), isDisabled: ( selectedCells || [] ).length !== 1, onClick: () => onDeleteRow(), }, { icon: tableColumnBefore, title: __( 'Insert column before', 'flexible-table-block' ), isDisabled: ( selectedCells || [] ).length !== 1, onClick: () => onInsertColumn( 0 ), }, { icon: tableColumnAfter, title: __( 'Insert column after', 'flexible-table-block' ), isDisabled: ( selectedCells || [] ).length !== 1, onClick: () => onInsertColumn( 1 ), }, { icon: tableColumnDelete, title: __( 'Delete column', 'flexible-table-block' ), isDisabled: ( selectedCells || [] ).length !== 1, onClick: () => onDeleteColumn(), }, { icon: splitCell, title: __( 'Split Merged Cells', 'flexible-table-block' ), isDisabled: ! selectedCells || ! hasMergedCells( selectedCells ), onClick: () => onSplitMergedCells(), }, { icon: mergeCell, title: __( 'Merge Cells', 'flexible-table-block' ), isDisabled: ! selectedCells || ! isRectangleSelected( selectedCells ), onClick: () => onMergeCells(), }, ]; const isEmpty = ! [ 'head', 'body', 'foot' ].filter( ( sectionName ) => ! isEmptySection( vTable[ sectionName as SectionName ] ) ).length; const tablePlaceholderProps = useBlockProps(); const tableFigureProps = useBlockProps( { className: classnames( `is-caption-side-${ captionSide }`, { [ `is-content-justification-${ contentJustification }` ]: contentJustification, 'show-dot-on-th': options.show_dot_on_th, 'show-control-button': options.show_control_button, } ), } ); const tableProps = { ...props, options, vTable, tableStylesObj, selectMode, selectedCells, setSelectedCells, selectedLine, setSelectedLine, }; const tableSettingsProps = { ...props, vTable, tableStylesObj, }; const tableCellSettingsProps = { ...props, vTable, selectedCells, }; const tableCellSettingsLabel = selectedCells && selectedCells.length > 1 ? __( 'Multi Cells Settings', 'flexible-table-block' ) : __( 'Cell Settings', 'flexible-table-block' ); const tableCaptionProps = { ...props, captionStylesObj, setSelectedLine, setSelectedCells, }; const tableCaptionSettingProps = { ...props, captionStylesObj, }; return ( <> { isEmpty && (
) } { ! isEmpty && ( // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
{ /* @ts-ignore */ } { !! selectedCells?.length && ( ) } { 'top' === captionSide && } { 'bottom' === captionSide && } ) } ); } export default TableEdit;