UNPKG

@wordpress/block-library

Version:
314 lines (298 loc) 9.55 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createTable = createTable; exports.deleteColumn = deleteColumn; exports.deleteRow = deleteRow; exports.getCellAttribute = getCellAttribute; exports.getFirstRow = getFirstRow; exports.insertColumn = insertColumn; exports.insertRow = insertRow; exports.isCellSelected = isCellSelected; exports.isEmptyRow = isEmptyRow; exports.isEmptyTableSection = isEmptyTableSection; exports.toggleSection = toggleSection; exports.updateSelectedCell = updateSelectedCell; const INHERITED_COLUMN_ATTRIBUTES = ['align']; /** * Creates a table state. * * @param {Object} options * @param {number} options.rowCount Row count for the table to create. * @param {number} options.columnCount Column count for the table to create. * * @return {Object} New table state. */ function createTable({ rowCount, columnCount }) { return { body: Array.from({ length: rowCount }).map(() => ({ cells: Array.from({ length: columnCount }).map(() => ({ content: '', tag: 'td' })) })) }; } /** * Returns the first row in the table. * * @param {Object} state Current table state. * * @return {Object | undefined} The first table row. */ function getFirstRow(state) { if (!isEmptyTableSection(state.head)) { return state.head[0]; } if (!isEmptyTableSection(state.body)) { return state.body[0]; } if (!isEmptyTableSection(state.foot)) { return state.foot[0]; } } /** * Gets an attribute for a cell. * * @param {Object} state Current table state. * @param {Object} cellLocation The location of the cell * @param {string} attributeName The name of the attribute to get the value of. * * @return {*} The attribute value. */ function getCellAttribute(state, cellLocation, attributeName) { const { sectionName, rowIndex, columnIndex } = cellLocation; return state[sectionName]?.[rowIndex]?.cells?.[columnIndex]?.[attributeName]; } /** * Returns updated cell attributes after applying the `updateCell` function to the selection. * * @param {Object} state The block attributes. * @param {Object} selection The selection of cells to update. * @param {Function} updateCell A function to update the selected cell attributes. * * @return {Object} New table state including the updated cells. */ function updateSelectedCell(state, selection, updateCell) { if (!selection) { return state; } const tableSections = Object.fromEntries(Object.entries(state).filter(([key]) => ['head', 'body', 'foot'].includes(key))); const { sectionName: selectionSectionName, rowIndex: selectionRowIndex } = selection; return Object.fromEntries(Object.entries(tableSections).map(([sectionName, section]) => { if (selectionSectionName && selectionSectionName !== sectionName) { return [sectionName, section]; } return [sectionName, section.map((row, rowIndex) => { if (selectionRowIndex && selectionRowIndex !== rowIndex) { return row; } return { cells: row.cells.map((cellAttributes, columnIndex) => { const cellLocation = { sectionName, columnIndex, rowIndex }; if (!isCellSelected(cellLocation, selection)) { return cellAttributes; } return updateCell(cellAttributes); }) }; })]; })); } /** * Returns whether the cell at `cellLocation` is included in the selection `selection`. * * @param {Object} cellLocation An object containing cell location properties. * @param {Object} selection An object containing selection properties. * * @return {boolean} True if the cell is selected, false otherwise. */ function isCellSelected(cellLocation, selection) { if (!cellLocation || !selection) { return false; } switch (selection.type) { case 'column': return selection.type === 'column' && cellLocation.columnIndex === selection.columnIndex; case 'cell': return selection.type === 'cell' && cellLocation.sectionName === selection.sectionName && cellLocation.columnIndex === selection.columnIndex && cellLocation.rowIndex === selection.rowIndex; } } /** * Inserts a row in the table state. * * @param {Object} state Current table state. * @param {Object} options * @param {string} options.sectionName Section in which to insert the row. * @param {number} options.rowIndex Row index at which to insert the row. * @param {number} options.columnCount Column count for the table to create. * * @return {Object} New table state. */ function insertRow(state, { sectionName, rowIndex, columnCount }) { const firstRow = getFirstRow(state); const cellCount = columnCount === undefined ? firstRow?.cells?.length : columnCount; // Bail early if the function cannot determine how many cells to add. if (!cellCount) { return state; } return { [sectionName]: [...state[sectionName].slice(0, rowIndex), { cells: Array.from({ length: cellCount }).map((_, index) => { var _firstRow$cells$index; const firstCellInColumn = (_firstRow$cells$index = firstRow?.cells?.[index]) !== null && _firstRow$cells$index !== void 0 ? _firstRow$cells$index : {}; const inheritedAttributes = Object.fromEntries(Object.entries(firstCellInColumn).filter(([key]) => INHERITED_COLUMN_ATTRIBUTES.includes(key))); return { ...inheritedAttributes, content: '', tag: sectionName === 'head' ? 'th' : 'td' }; }) }, ...state[sectionName].slice(rowIndex)] }; } /** * Deletes a row from the table state. * * @param {Object} state Current table state. * @param {Object} options * @param {string} options.sectionName Section in which to delete the row. * @param {number} options.rowIndex Row index to delete. * * @return {Object} New table state. */ function deleteRow(state, { sectionName, rowIndex }) { return { [sectionName]: state[sectionName].filter((row, index) => index !== rowIndex) }; } /** * Inserts a column in the table state. * * @param {Object} state Current table state. * @param {Object} options * @param {number} options.columnIndex Column index at which to insert the column. * * @return {Object} New table state. */ function insertColumn(state, { columnIndex }) { const tableSections = Object.fromEntries(Object.entries(state).filter(([key]) => ['head', 'body', 'foot'].includes(key))); return Object.fromEntries(Object.entries(tableSections).map(([sectionName, section]) => { // Bail early if the table section is empty. if (isEmptyTableSection(section)) { return [sectionName, section]; } return [sectionName, section.map(row => { // Bail early if the row is empty or it's an attempt to insert past // the last possible index of the array. if (isEmptyRow(row) || row.cells.length < columnIndex) { return row; } return { cells: [...row.cells.slice(0, columnIndex), { content: '', tag: sectionName === 'head' ? 'th' : 'td' }, ...row.cells.slice(columnIndex)] }; })]; })); } /** * Deletes a column from the table state. * * @param {Object} state Current table state. * @param {Object} options * @param {number} options.columnIndex Column index to delete. * * @return {Object} New table state. */ function deleteColumn(state, { columnIndex }) { const tableSections = Object.fromEntries(Object.entries(state).filter(([key]) => ['head', 'body', 'foot'].includes(key))); return Object.fromEntries(Object.entries(tableSections).map(([sectionName, section]) => { // Bail early if the table section is empty. if (isEmptyTableSection(section)) { return [sectionName, section]; } return [sectionName, section.map(row => ({ cells: row.cells.length >= columnIndex ? row.cells.filter((cell, index) => index !== columnIndex) : row.cells })).filter(row => row.cells.length)]; })); } /** * Toggles the existence of a section. * * @param {Object} state Current table state. * @param {string} sectionName Name of the section to toggle. * * @return {Object} New table state. */ function toggleSection(state, sectionName) { var _state$body$0$cells$l; // Section exists, replace it with an empty row to remove it. if (!isEmptyTableSection(state[sectionName])) { return { [sectionName]: [] }; } // Get the length of the first row of the body to use when creating the header. const columnCount = (_state$body$0$cells$l = state.body?.[0]?.cells?.length) !== null && _state$body$0$cells$l !== void 0 ? _state$body$0$cells$l : 1; // Section doesn't exist, insert an empty row to create the section. return insertRow(state, { sectionName, rowIndex: 0, columnCount }); } /** * Determines whether a table section is empty. * * @param {Object} section Table section state. * * @return {boolean} True if the table section is empty, false otherwise. */ function isEmptyTableSection(section) { return !section || !section.length || section.every(isEmptyRow); } /** * Determines whether a table row is empty. * * @param {Object} row Table row state. * * @return {boolean} True if the table section is empty, false otherwise. */ function isEmptyRow(row) { return !(row.cells && row.cells.length); } //# sourceMappingURL=state.js.map