import * as React from 'react'; import { TableBodyProps, isRowExpanded, IRowData, IExtraRowData, TableBody, IRow, IComputedData } from '@patternfly/react-table'; import { showIdWarnings, TableEditConfirmation } from '../../utils'; import { IEditedCellData } from '../InlineEdit/editableTableBody'; const resolveCascadeEditability = (rows: ExtendedIRow[]) => { // eslint-disable-next-line no-undef const isRowExpandedIndexes = new Set( rows.map((row, idx) => (isRowExpanded(row, rows) ? idx : null)).filter(row => row !== null) ); // flag parents and their children which are edited together rows .filter( (row, idx) => row.parent !== undefined && row.isEditing && isRowExpandedIndexes.has(idx) && row.isEditableTogetherWithParent && rows[row.parent].isEditing ) .forEach(row => { rows[row.parent].isChildEditing = true; row.isParentEditing = true; }); const lastVisibleRow = rows.filter((row, idx) => !row.parent || isRowExpandedIndexes.has(idx)).pop(); // flag last parent row if there are only descendants under it if (lastVisibleRow && lastVisibleRow.isParentEditing) { let parentRow = lastVisibleRow; while (parentRow.parent !== undefined && parentRow.isEditableTogetherWithParent) { parentRow = rows[parentRow.parent]; } parentRow.isLastVisibleParent = true; } }; const onRow = ( event: React.MouseEvent, row: IRow, rowProps: IExtraRowData, computedData: IComputedData, { onRowClick, editConfig }: InlineEditBodyProps ) => { const target = event.target as any; const cell = target.closest('[data-key]'); // eslint-disable-next-line radix const cellNumber = parseInt(cell && cell.getAttribute('data-key'), 10); const hasCellNumber = !Number.isNaN(cellNumber); let onEditCellClicked: () => void; if (hasCellNumber && editConfig && typeof editConfig.onEditCellClicked === 'function') { // resolve closest (e.g. for dropdowns) usable id of a clicked element inside a cell const idElement = target.closest('[id]'); const elementId = idElement && cell.contains(idElement) ? idElement.getAttribute('id') || null : null; if (!elementId) { showIdWarnings(row, target); } onEditCellClicked = () => { editConfig.onEditCellClicked(event as any, row, { ...rowProps, columnIndex: cellNumber, elementId }); }; } // give priority to fire onChange/onBlur callbacks setTimeout(() => { if (!row.isEditing) { onRowClick(event, row, rowProps, computedData); if (onEditCellClicked) { // edit cell after rerender setTimeout(onEditCellClicked, 0); } } else if (onEditCellClicked) { onEditCellClicked(); } }, 0); }; export interface EditConfig { editConfirmationType?: typeof TableEditConfirmation | keyof typeof TableEditConfirmation; onEditCellClicked?: (value: React.MouseEvent, row: IRowData, extra: IEditedCellData) => void; onEditConfirmed?: (value: React.MouseEvent, row: IRowData, rowProps: IExtraRowData) => void; onEditCanceled?: (value: React.MouseEvent, row: IRowData, rowProps: IExtraRowData) => void; } export interface InlineEditBodyProps extends TableBodyProps { editConfig: EditConfig; } export interface BodyProps extends TableBodyProps { BodyComponent: typeof TableBody; editConfig: EditConfig; } interface ExtendedIRow extends IRow { editConfig: EditConfig; isTableEditing: boolean; } export const Body = ({ BodyComponent, rows = [], editConfig, onRowClick = () => {}, ...props }: BodyProps) => { const isTableEditing = rows.some(row => row.isEditing); const mappedRows = rows.map(row => ({ ...row, editConfig, isTableEditing })) as ExtendedIRow[]; resolveCascadeEditability(mappedRows); return ( onRow(event as any, row, rowProps, computedData, { onRowClick, editConfig }) } /> ); };