UNPKG

@ui5/webcomponents-react

Version:

React Wrapper for UI5 Web Components and additional components

180 lines (176 loc) 6.86 kB
'use client'; import { useEffect, useRef } from 'react'; import { AnalyticalTableSelectionBehavior } from '../../../enums/AnalyticalTableSelectionBehavior.js'; import { AnalyticalTableSelectionMode } from '../../../enums/AnalyticalTableSelectionMode.js'; const getParentRow = (id, rowsById) => { let lastDotIndex = id.lastIndexOf('.'); if (lastDotIndex === -1) { lastDotIndex = Infinity; } const parentRowId = id.slice(0, lastDotIndex); return [rowsById[parentRowId], lastDotIndex]; }; const getIndeterminateRowIds = id => { const indeterminateRowsById = {}; const lastDotIndex = id.lastIndexOf('.'); indeterminateRowsById[id] = true; if (lastDotIndex !== -1) { // set all parent rows to indeterminate Object.assign(indeterminateRowsById, getIndeterminateRowIds(id.slice(0, lastDotIndex))); } return indeterminateRowsById; }; const getIndeterminate = (rows, rowsById, state) => { const indeterminateRowsById = {}; let usedParentIndex = ''; const getIndeterminateRecursive = (subRows, rowIdScope = null) => { for (const row of subRows) { if (row.subRows.length > 0) { // find leaf nodes getIndeterminateRecursive(row.subRows, row.id); } else if (rowIdScope !== null && usedParentIndex !== rowIdScope) { usedParentIndex = rowIdScope; const checkIndeterminate = rowId => { const [parentRow, dotIndex] = getParentRow(rowId, rowsById); const selectedRows = parentRow.subRows.filter(item => state.selectedRowIds[item.id]); const areAllSelected = parentRow.subRows.length === selectedRows.length; const isOneSelected = selectedRows.length > 0; // if not all, but at least one subRow is selected, set the parent row's state to indeterminate if (isOneSelected && !areAllSelected) { const parentRowId = parentRow.id; Object.assign(indeterminateRowsById, getIndeterminateRowIds(parentRowId)); return; } if (dotIndex !== Infinity) { // recursively check indeterminate state until root nodes are reached checkIndeterminate(parentRow.id); } return; }; checkIndeterminate(row.id); } } }; getIndeterminateRecursive(rows); return indeterminateRowsById; }; /** * A plugin hook that marks parent rows as indeterminate if a child row is selected in `Multiple` mode. * When using this hook, it is recommended to also select all sub-rows when selecting a row. (`reactTableOptions={{ selectSubRows: true }}`) * * __Note:__ * - This functionality is not covered by SAP UXC design guidelines and should be avoided if UXC is required. * - The `indeterminate` state has a higher priority than the `selected` state. Therefore, a row can be selected and indeterminate at the same time. This can for example happen, if `selectSubRows: true` is set and a row with sub-rows is selected and then a sub-row is unselected. * - This hook has to traverse the whole data tree on each selection, which can lead to performance degradation with large datasets. Please use with caution! * * @param {event} onIndeterminateChange Fired when the indeterminate state of rows is changed. */ export const useIndeterminateRowSelection = onIndeterminateChange => { const toggleRowProps = (rowProps, { row, instance }) => { let indeterminate; if (instance.isAllRowsSelected) { indeterminate = false; } else { indeterminate = instance?.state?.indeterminateRows?.[row.id] ?? false; } return [rowProps, { indeterminate: indeterminate, checked: indeterminate ? true : rowProps.checked }]; }; const stateReducer = (newState, action, _prevState, instance) => { const { rowsById, rows } = instance; // check if parent row should be auto-selected if (action.type === 'toggleRowSelected') { const rowId = action.id; const isSelected = newState.selectedRowIds[rowId]; if (isSelected) { // check if row has parent and if all subRows are selected const parentId = rowId.substring(0, rowId.lastIndexOf('.')); if (parentId && rowsById[parentId]) { const parentRow = rowsById[parentId]; const allSiblingsSelected = parentRow.subRows?.every(subRow => newState.selectedRowIds[subRow.id]); if (allSiblingsSelected && !newState.selectedRowIds[parentId]) { // auto-select parent row return { ...newState, selectedRowIds: { ...newState.selectedRowIds, [parentId]: true } }; } } } } if (action.type === 'INDETERMINATE_ROW_IDS') { if (action.payload === 'reset') { return { ...newState, indeterminateRows: {} }; } const indeterminateRowsById = getIndeterminate(rows, rowsById, { selectedRowIds: newState.selectedRowIds }); return { ...newState, indeterminateRows: indeterminateRowsById }; } }; const useInstanceAfterData = instance => { const { data, dispatch, rowsById, state: { selectedRowIds, indeterminateRows }, webComponentsReactProperties: { selectionMode, selectionBehavior, isTreeTable } } = instance; const lastProcessedSelectedRowIdsRef = useRef(selectedRowIds); useEffect(() => { if (lastProcessedSelectedRowIdsRef.current === selectedRowIds) { return; } lastProcessedSelectedRowIdsRef.current = selectedRowIds; if (isTreeTable && selectionMode === AnalyticalTableSelectionMode.Multiple && selectionBehavior !== AnalyticalTableSelectionBehavior.RowOnly && Object.keys(selectedRowIds).length && Object.keys(rowsById).length !== Object.keys(selectedRowIds).length) { dispatch({ type: 'INDETERMINATE_ROW_IDS' }); } else if (typeof indeterminateRows === 'object' && Object.keys(indeterminateRows).length) { dispatch({ type: 'INDETERMINATE_ROW_IDS', payload: 'reset' }); } }, [data, selectedRowIds, isTreeTable, selectionMode, selectionBehavior, dispatch]); useEffect(() => { if (typeof onIndeterminateChange === 'function' && indeterminateRows) { onIndeterminateChange({ indeterminateRowsById: indeterminateRows, tableInstance: instance }); } }, [indeterminateRows]); }; const useIndeterminate = hooks => { hooks.getToggleRowSelectedProps.push(toggleRowProps); hooks.stateReducers.push(stateReducer); hooks.useInstanceAfterData.push(useInstanceAfterData); }; useIndeterminate.pluginName = 'useIndeterminate'; return useIndeterminate; };