diff --git a/src/blocks/index.ts b/src/blocks/index.ts index 69c7d83..8e2187f 100644 --- a/src/blocks/index.ts +++ b/src/blocks/index.ts @@ -7,6 +7,7 @@ import { registerBlockType } from '@wordpress/blocks'; * Import blocks. */ import * as table from './table'; +import * as tableRowContainer from './table-row-container'; import * as tableRow from './table-row'; import * as tableColumn from './table-column'; import * as tableCell from './table-cell'; @@ -21,6 +22,7 @@ import './block-toolbar'; */ const blocks = [ table, + tableRowContainer, tableRow, tableColumn, tableCell, diff --git a/src/blocks/table-column/edit.tsx b/src/blocks/table-column/edit.tsx index 1257e07..6df7efe 100644 --- a/src/blocks/table-column/edit.tsx +++ b/src/blocks/table-column/edit.tsx @@ -46,10 +46,15 @@ function TableColumnEdit( { className: classnames( className, 'travelopia-table__column' ), } ); - const tableId = context[ 'travelopia/table-id' ] as string; + const tableId: string = context[ 'travelopia/table-id' ] as string; + const rowContainerType: string = context[ 'travelopia/table-row-container-type' ] as string; const innerBlocksProps = useInnerBlocksProps( - { ...blockProps }, + { + ...blockProps, + colSpan: attributes.colSpan, + rowSpan: attributes.rowSpan, + }, { template: [ [ cellBlockName ] ], templateLock: false, @@ -78,6 +83,12 @@ function TableColumnEdit( { setAttributes( { row, column } ); }, [ row, column, setAttributes ] ); + // Determine tag. + let Tag: string = 'td'; + if ( 'tbody' !== rowContainerType ) { + Tag = 'th'; + } + return ( <> - ); diff --git a/src/blocks/table-column/index.tsx b/src/blocks/table-column/index.tsx index e09465c..97e770b 100644 --- a/src/blocks/table-column/index.tsx +++ b/src/blocks/table-column/index.tsx @@ -46,6 +46,9 @@ export const settings: BlockConfiguration = { 'travelopia/table-row': 'row' as never, 'travelopia/table-column': 'column' as never, }, + usesContext: [ + 'travelopia/table-row-container-type', + ], supports: { html: true, color: { @@ -55,6 +58,6 @@ export const settings: BlockConfiguration = { }, edit, save() { - return ; + return ; }, }; diff --git a/src/blocks/table-row-container/edit.tsx b/src/blocks/table-row-container/edit.tsx new file mode 100644 index 0000000..da97202 --- /dev/null +++ b/src/blocks/table-row-container/edit.tsx @@ -0,0 +1,70 @@ +/** + * WordPress dependencies. + */ +import { __ } from '@wordpress/i18n'; +import { + InspectorControls, + useBlockProps, + useInnerBlocksProps, +} from '@wordpress/block-editor'; +import { + PanelBody, + ToggleControl, +} from '@wordpress/components'; +import { + BlockEditProps, +} from '@wordpress/blocks'; + +/** + * External dependencies. + */ +import classnames from 'classnames'; + +/** + * Internal dependencies. + */ +import { name as rowBlockName } from '../table-row'; + +/** + * Edit function. + * + * @param {Object} props Edit properties. + * + * @return {JSX.Element} JSX Component. + */ +function TableRowContainerEdit( props: BlockEditProps ): JSX.Element { + // Block props. + const { className, attributes, setAttributes } = props; + + // Inner block props. + const blockProps = useBlockProps( { + className: classnames( className, 'travelopia-table__row-container' ), + } ); + const innerBlocksProps = useInnerBlocksProps( { ...blockProps }, { + allowedBlocks: [ rowBlockName ], + } ); + + // Determine tag. + const Tag: string = attributes.type; + + // Return component. + return ( + <> + { 'thead' === attributes.type && + + + setAttributes( { isSticky } ) } + help={ __( 'Is this container sticky?', 'tp' ) } + /> + + + } + + + ); +} + +export default TableRowContainerEdit; diff --git a/src/blocks/table-row-container/index.tsx b/src/blocks/table-row-container/index.tsx new file mode 100644 index 0000000..29d81e1 --- /dev/null +++ b/src/blocks/table-row-container/index.tsx @@ -0,0 +1,56 @@ +/** + * WordPress dependencies. + */ +import { __ } from '@wordpress/i18n'; +import { BlockConfiguration } from '@wordpress/blocks'; +import { + InnerBlocks, +} from '@wordpress/block-editor'; +import { + blockTable as icon, +} from '@wordpress/icons'; + +/** + * Internal dependencies. + */ +import edit from './edit'; + +/** + * Block data. + */ +export const name: string = 'travelopia/table-row-container'; + +export const settings: BlockConfiguration = { + apiVersion: 3, + icon, + title: __( 'Row Container', 'tp' ), + description: __( 'A container for a row (THEAD, TBODY, TFOOT).', 'tp' ), + parent: [ 'travelopia/table' ], + category: 'text', + keywords: [ + __( 'thead', 'tp' ), + __( 'tbody', 'tp' ), + __( 'tfoot', 'tp' ), + ], + attributes: { + type: { + type: 'string', + default: 'tbody', + }, + isSticky: { + type: 'boolean', + default: false, + }, + }, + providesContext: { + 'travelopia/table-row-container-type': 'type' as never, + 'travelopia/table-row-container-sticky': 'isSticky' as never, + }, + supports: { + html: false, + }, + edit, + save() { + return ; + }, +}; diff --git a/src/blocks/table-row/edit.tsx b/src/blocks/table-row/edit.tsx index 38a3843..5a69aed 100644 --- a/src/blocks/table-row/edit.tsx +++ b/src/blocks/table-row/edit.tsx @@ -1,11 +1,14 @@ /** * WordPress dependencies. */ +import { __ } from '@wordpress/i18n'; import { useBlockProps, useInnerBlocksProps, } from '@wordpress/block-editor'; -import { BlockEditProps } from '@wordpress/blocks'; +import { + BlockEditProps, +} from '@wordpress/blocks'; /** * External dependencies. @@ -25,12 +28,16 @@ import { name as columnBlockName } from '../table-column'; * @return {JSX.Element} JSX Component. */ function TableRowEdit( props: BlockEditProps ): JSX.Element { + // Block props. const { className } = props; + + // Inner block props. const blockProps = useBlockProps( { className: classnames( className, 'travelopia-table__row' ), } ); const innerBlocksProps = useInnerBlocksProps( { ...blockProps }, { allowedBlocks: [ columnBlockName ], + templateLock: false, } ); return ( diff --git a/src/blocks/table-row/index.tsx b/src/blocks/table-row/index.tsx index af79522..6b9177d 100644 --- a/src/blocks/table-row/index.tsx +++ b/src/blocks/table-row/index.tsx @@ -25,10 +25,13 @@ export const settings: BlockConfiguration = { icon, title: __( 'Row', 'tp' ), description: __( 'Individual row of the table.', 'tp' ), - parent: [ 'travelopia/table' ], + parent: [ 'travelopia/table-row-container' ], category: 'text', keywords: [ __( 'row', 'tp' ) ], attributes: {}, + usesContext: [ + 'travelopia/table-row-container-type', + ], supports: { html: true, color: { @@ -38,6 +41,6 @@ export const settings: BlockConfiguration = { }, edit, save() { - return ; + return ; }, }; diff --git a/src/blocks/table/edit.tsx b/src/blocks/table/edit.tsx index 0554099..49b9824 100644 --- a/src/blocks/table/edit.tsx +++ b/src/blocks/table/edit.tsx @@ -1,12 +1,25 @@ /** * WordPress dependencies. */ +import { __ } from '@wordpress/i18n'; import { + InspectorControls, useBlockProps, useInnerBlocksProps, } from '@wordpress/block-editor'; -import { BlockEditProps } from '@wordpress/blocks'; +import { + BlockEditProps, + createBlock, +} from '@wordpress/blocks'; import { useEffect } from '@wordpress/element'; +import { + PanelBody, + ToggleControl, +} from '@wordpress/components'; +import { + select, + dispatch, +} from '@wordpress/data'; /** * External dependencies. @@ -17,7 +30,78 @@ import classnames from 'classnames'; * Internal dependencies. */ import { TablePlaceholder } from './placeholder'; +import { name as rowContainerBlockName } from '../table-row-container'; import { name as rowBlockName } from '../table-row'; +import { name as columnBlockName } from '../table-column'; +import { name as cellBlockName } from '../table-cell'; + +/** + * Create and insert a row container. + * + * @param {string} type Row container type. + * @param {string} tableClientId The table block's client ID. + */ +export const createAndInsertRowContainer = ( type: string = 'tbody', tableClientId: string = '' ): void => { + // Get table block. + const tableBlock = select( 'core/block-editor' ).getBlock( tableClientId ); + if ( ! tableBlock ) { + return; + } + + // Create row container. + const rowContainerBlock = createBlock( rowContainerBlockName, { type } ); + + // Determine number of rows to create. + let totalRows = tableBlock.attributes.rows; + if ( 'tbody' !== type ) { + totalRows = 1; + } + + // Add rows and columns to it. + for ( let i: number = 0; i < totalRows; i++ ) { + const columnBlocks = []; + for ( let j: number = 0; j < tableBlock.attributes.columns; j++ ) { + columnBlocks.push( + createBlock( columnBlockName, {}, [ + createBlock( cellBlockName ), + ] ) + ); + } + + rowContainerBlock.innerBlocks.push( + createBlock( rowBlockName, {}, columnBlocks ) + ); + } + + // Add newly created row and column blocks to the table. + if ( 'tbody' === type ) { + dispatch( 'core/block-editor' ).replaceInnerBlocks( tableClientId, [ rowContainerBlock ] ); + } else { + const position = 'thead' === type ? 0 : tableBlock.innerBlocks.length; + dispatch( 'core/block-editor' ).insertBlock( rowContainerBlock, position, tableClientId ); + } +}; + +/** + * Delete row container child block. + * + * @param {string} type Row container type. + * @param {string} tableClientId The table block's client ID. + */ +export const deleteRowContainer = ( type: string = 'thead', tableClientId: string = '' ): void => { + // Get table block. + const tableBlock = select( 'core/block-editor' ).getBlock( tableClientId ); + if ( ! tableBlock || ! tableBlock.innerBlocks.length ) { + return; + } + + // Find the child block and delete it. + tableBlock.innerBlocks.forEach( ( innerBlock ) => { + if ( innerBlock.attributes?.type === type ) { + dispatch( 'core/block-editor' ).removeBlock( innerBlock.clientId ); + } + } ); +}; /** * Edit function. @@ -32,7 +116,8 @@ function TableEdit( props: BlockEditProps ): JSX.Element { className: classnames( className, 'travelopia-table' ), } ); const innerBlocksProps = useInnerBlocksProps( {}, { - allowedBlocks: [ rowBlockName ], + allowedBlocks: [ rowContainerBlockName ], + renderAppender: undefined, } ); // Set blockId attribute. @@ -40,18 +125,64 @@ function TableEdit( props: BlockEditProps ): JSX.Element { setAttributes( { blockId: clientId } ); }, [ clientId, setAttributes ] ); + /** + * Handle THEAD change. + * + * @param {boolean} hasThead Has THEAD. + */ + const handleTheadChange = ( hasThead: boolean ): void => { + if ( hasThead ) { + createAndInsertRowContainer( 'thead', clientId ); + } else { + deleteRowContainer( 'thead', clientId ); + } + setAttributes( { hasThead } ); + }; + + /** + * Handle TFOOT change. + * + * @param {boolean} hasTfoot Has TFOOT. + */ + const handleTfootChange = ( hasTfoot: boolean ): void => { + if ( hasTfoot ) { + createAndInsertRowContainer( 'tfoot', clientId ); + } else { + deleteRowContainer( 'tfoot', clientId ); + } + setAttributes( { hasTfoot } ); + }; + return ( -
- { - /* Placeholder for initial state. */ - ( 0 === attributes.rows || 0 === attributes.columns ) && - - } - { - ( 0 !== attributes.rows || 0 !== attributes.columns ) && - - } - + <> + + + + + + +
+ { + /* Placeholder for initial state. */ + ( 0 === attributes.rows || 0 === attributes.columns ) && + + } + { + ( 0 !== attributes.rows || 0 !== attributes.columns ) && +
+ } + + ); } diff --git a/src/blocks/table/index.tsx b/src/blocks/table/index.tsx index 46abaef..88a55bf 100644 --- a/src/blocks/table/index.tsx +++ b/src/blocks/table/index.tsx @@ -47,15 +47,27 @@ export const settings: BlockConfiguration = { blockId: { type: 'string', }, + hasThead: { + type: 'boolean', + default: false, + }, + hasTfoot: { + type: 'boolean', + default: false, + }, }, providesContext: { 'travelopia/table-id': 'blockId' as never, + 'travelopia/table-total-rows': 'rows' as never, + 'travelopia/table-total-columns': 'columns' as never, + 'travelopia/table-has-thead': 'hasThead' as never, + 'travelopia/table-has-tfoot': 'hasTfoot' as never, }, supports: { anchor: true, }, edit, save() { - return
; + return ; }, }; diff --git a/src/blocks/table/placeholder.tsx b/src/blocks/table/placeholder.tsx index 0f55ada..4da53f8 100644 --- a/src/blocks/table/placeholder.tsx +++ b/src/blocks/table/placeholder.tsx @@ -12,19 +12,12 @@ import { import { useState } from '@wordpress/element'; import { BlockEditProps, - createBlock, } from '@wordpress/blocks'; -import { - select, - dispatch, -} from '@wordpress/data'; /** * Internal dependencies. */ -import { name as rowBlockName } from '../table-row'; -import { name as columnBlockName } from '../table-column'; -import { name as cellBlockName } from '../table-cell'; +import { createAndInsertRowContainer } from './edit'; /** * Edit function. @@ -53,32 +46,8 @@ export function TablePlaceholder( props: BlockEditProps ): JSX.Element { // Set attributes. setAttributes( { rows, columns } ); - // Get current block. - const currentBlock = select( 'core/block-editor' ).getBlock( clientId ); - if ( ! currentBlock ) { - return; - } - - // Create row and column blocks. - const innerBlocks = []; - - for ( let i: number = 0; i < rows; i++ ) { - const columnBlocks = []; - for ( let j: number = 0; j < columns; j++ ) { - columnBlocks.push( - createBlock( columnBlockName, {}, [ - createBlock( cellBlockName ), - ] ) - ); - } - - innerBlocks.push( - createBlock( rowBlockName, {}, columnBlocks ) - ); - } - - // Add newly created row and column blocks to the table. - dispatch( 'core/block-editor' ).replaceInnerBlocks( clientId, innerBlocks ); + // Create and insert row container. + createAndInsertRowContainer( 'tbody', clientId ); } } >