@ui5/webcomponents-react
Version:
React Wrapper for UI5 Web Components and additional components
360 lines (354 loc) • 11 kB
JavaScript
import { useCallback, useMemo } from 'react';
import { AnalyticalTableSelectionMode } from '../../../enums/AnalyticalTableSelectionMode.js';
import { actions, makePropGetter, ensurePluginOrder, useGetLatest, useMountedLayoutEffect } from '../react-table/index.js';
const pluginName = 'useRowSelect';
// Actions
actions.resetSelectedRows = 'resetSelectedRows';
actions.toggleAllRowsSelected = 'toggleAllRowsSelected';
actions.toggleRowSelected = 'toggleRowSelected';
actions.toggleAllPageRowsSelected = 'toggleAllPageRowsSelected';
const noopToggle = () => {};
const noopGetProps = () => ({});
const emptyArray = [];
/**
* UI5WCR optimized version of react-table v7's useRowSelect hook.
* Original source: https://github.com/TanStack/table/blob/v7/src/plugin-hooks/useRowSelect.js
*
* This is a fork of react-table's `useRowSelect` with performance optimizations:
* - Early exit when `selectionMode` is 'None'
* - Skips `selectedFlatRows` computation, `isAllRowsSelected` checks, and `prepareRow` overhead when selection is disabled
* - `selectedFlatRows` computation is memoized
* - Uses stable noop references when disabled
* - Fixes select-all indeterminate state considering filtered-out rows (now only visible rows are considered)
* - Removed `manualRowSelectedKey` display override from `defaultGetToggleRowSelectedProps` (`row.isSelected` is the single source of truth)
*
* _Pagination specific implementation where adjusted as well, although they are currently not being used_
*/
export const useRowSelect = hooks => {
hooks.getToggleRowSelectedProps = [defaultGetToggleRowSelectedProps];
hooks.getToggleAllRowsSelectedProps = [defaultGetToggleAllRowsSelectedProps];
hooks.getToggleAllPageRowsSelectedProps = [defaultGetToggleAllPageRowsSelectedProps];
hooks.stateReducers.push(reducer);
hooks.useInstance.push(useInstance);
hooks.prepareRow.push(prepareRow);
};
useRowSelect.pluginName = pluginName;
const defaultGetToggleRowSelectedProps = (props, {
instance,
row
}) => {
// UI5WCR: use className instead of inline style; removed `manualRowSelectedKey` display override
const {
classes
} = instance.webComponentsReactProperties;
return [props, {
onChange: e => {
row.toggleRowSelected(e.target.checked);
},
// UI5WCR: removed style/title, added className
className: classes.checkBox,
checked: row.isSelected,
indeterminate: row.isSomeSelected
}];
};
const defaultGetToggleAllRowsSelectedProps = (props, {
instance
}) => {
// UI5WCR: use className instead of inline style
const {
classes
} = instance.webComponentsReactProperties;
return [props, {
onChange: e => {
instance.toggleAllRowsSelected?.(e.target.checked);
},
// UI5WCR: removed style/title, added className
className: classes.checkBox,
checked: instance.isAllRowsSelected,
// UI5WCR: use selectedFlatRows (visible rows only) instead of selectedRowIds
indeterminate: Boolean(!instance.isAllRowsSelected && instance.selectedFlatRows?.length)
}];
};
const defaultGetToggleAllPageRowsSelectedProps = (props, {
instance
}) => {
// UI5WCR: use className instead of inline style
const {
classes
} = instance.webComponentsReactProperties;
return [props, {
onChange: e => {
instance.toggleAllPageRowsSelected?.(e.target.checked);
},
// UI5WCR: removed style/title, added className
className: classes.checkBox,
checked: instance.isAllPageRowsSelected,
indeterminate: Boolean(!instance.isAllPageRowsSelected && instance.page?.some(({
id
}) => instance.state.selectedRowIds[id]))
}];
};
const reducer = (state, action, _previousState, instance) => {
if (action.type === actions.init) {
return {
selectedRowIds: {},
...state
};
}
if (action.type === actions.resetSelectedRows) {
return {
...state,
selectedRowIds: instance.initialState.selectedRowIds || {}
};
}
if (action.type === actions.toggleAllRowsSelected) {
const {
value: setSelected
} = action;
const {
isAllRowsSelected,
rowsById,
nonGroupedRowsById = rowsById
} = instance;
const selectAll = typeof setSelected !== 'undefined' ? setSelected : !isAllRowsSelected;
const selectedRowIds = {
...state.selectedRowIds
};
for (const rowId in nonGroupedRowsById) {
if (selectAll) {
selectedRowIds[rowId] = true;
} else {
delete selectedRowIds[rowId];
}
}
return {
...state,
selectedRowIds
};
}
if (action.type === actions.toggleRowSelected) {
const {
id,
value: setSelected
} = action;
const {
rowsById,
selectSubRows = true,
getSubRows
} = instance;
const isSelected = state.selectedRowIds[id];
const shouldExist = typeof setSelected !== 'undefined' ? setSelected : !isSelected;
if (isSelected === shouldExist) {
return state;
}
const newSelectedRowIds = {
...state.selectedRowIds
};
const handleRowById = rowId => {
const row = rowsById[rowId];
if (row) {
if (!row.isGrouped) {
if (shouldExist) {
newSelectedRowIds[rowId] = true;
} else {
delete newSelectedRowIds[rowId];
}
}
if (selectSubRows) {
const subRows = getSubRows(row);
if (subRows) {
subRows.forEach(r => {
handleRowById(r.id);
});
}
}
}
};
handleRowById(id);
return {
...state,
selectedRowIds: newSelectedRowIds
};
}
if (action.type === actions.toggleAllPageRowsSelected) {
const {
value: setSelected
} = action;
const {
page,
rowsById,
selectSubRows = true,
isAllPageRowsSelected,
getSubRows
} = instance;
const selectAll = typeof setSelected !== 'undefined' ? setSelected : !isAllPageRowsSelected;
const newSelectedRowIds = {
...state.selectedRowIds
};
const handleRowById = rowId => {
const row = rowsById[rowId];
if (!row.isGrouped) {
if (selectAll) {
newSelectedRowIds[rowId] = true;
} else {
delete newSelectedRowIds[rowId];
}
}
if (selectSubRows) {
const subRows = getSubRows(row);
if (subRows) {
subRows.forEach(r => {
handleRowById(r.id);
});
}
}
};
if (page) {
page.forEach(row => {
handleRowById(row.id);
});
}
return {
...state,
selectedRowIds: newSelectedRowIds
};
}
return state;
};
function useInstance(instance) {
const {
data,
rows,
getHooks,
plugins,
rowsById,
nonGroupedRowsById = rowsById,
autoResetSelectedRows = true,
state: {
selectedRowIds
},
selectSubRows = true,
dispatch,
page,
getSubRows,
webComponentsReactProperties
} = instance;
const {
selectionMode
} = webComponentsReactProperties;
const isSelectionEnabled = selectionMode !== AnalyticalTableSelectionMode.None;
ensurePluginOrder(plugins, ['useFilters', 'useGroupBy', 'useSortBy', 'useExpanded', 'usePagination'], 'useRowSelect');
// UI5WCR: memoized computation with early exit when selection disabled.
const selectedFlatRows = useMemo(() => {
if (!isSelectionEnabled) {
return emptyArray;
}
const result = [];
rows.forEach(row => {
const isSelected = selectSubRows ? getRowIsSelected(row, selectedRowIds, getSubRows) : !!selectedRowIds[row.id];
row.isSelected = !!isSelected;
row.isSomeSelected = isSelected === null;
if (isSelected) {
result.push(row);
}
});
return result;
}, [rows, selectSubRows, selectedRowIds, getSubRows, isSelectionEnabled]);
let isAllRowsSelected = false;
let isAllPageRowsSelected = false;
if (isSelectionEnabled) {
// isAllRowsSelected
const rowIds = Object.keys(nonGroupedRowsById);
const selectedIds = Object.keys(selectedRowIds);
if (rowIds.length && selectedIds.length) {
isAllRowsSelected = rowIds.every(id => selectedRowIds[id]);
}
// isAllPageRowsSelected
if (page?.length) {
isAllPageRowsSelected = isAllRowsSelected || page.every(row => selectedRowIds[row.id]);
}
}
const getAutoResetSelectedRows = useGetLatest(autoResetSelectedRows);
useMountedLayoutEffect(() => {
if (getAutoResetSelectedRows()) {
dispatch({
type: actions.resetSelectedRows
});
}
}, [dispatch, data]);
const toggleAllRowsSelected = useCallback(value => dispatch({
type: actions.toggleAllRowsSelected,
value
}), [dispatch]);
const toggleAllPageRowsSelected = useCallback(value => dispatch({
type: actions.toggleAllPageRowsSelected,
value
}), [dispatch]);
const toggleRowSelected = useCallback((id, value) => dispatch({
type: actions.toggleRowSelected,
id,
value
}), [dispatch]);
const getInstance = useGetLatest(instance);
// UI5WCR: use noop when selection disabled
const getToggleAllRowsSelectedProps = isSelectionEnabled ? makePropGetter(getHooks().getToggleAllRowsSelectedProps, {
instance: getInstance()
}) : noopGetProps;
const getToggleAllPageRowsSelectedProps = isSelectionEnabled ? makePropGetter(getHooks().getToggleAllPageRowsSelectedProps, {
instance: getInstance()
}) : noopGetProps;
Object.assign(instance, {
selectedFlatRows,
isAllRowsSelected,
isAllPageRowsSelected,
toggleRowSelected,
toggleAllRowsSelected,
getToggleAllRowsSelectedProps,
getToggleAllPageRowsSelectedProps,
toggleAllPageRowsSelected
});
}
function prepareRow(row, {
instance
}) {
const {
selectionMode
} = instance.webComponentsReactProperties;
const isSelectionEnabled = selectionMode !== AnalyticalTableSelectionMode.None;
// UI5WCR: skip per-row setup when selection disabled
if (!isSelectionEnabled) {
row.isSelected = false;
row.isSomeSelected = false;
row.toggleRowSelected = noopToggle;
row.getToggleRowSelectedProps = noopGetProps;
return;
}
row.toggleRowSelected = set => instance.toggleRowSelected?.(row.id, set);
row.getToggleRowSelectedProps = makePropGetter(instance.getHooks().getToggleRowSelectedProps, {
instance,
row
});
}
function getRowIsSelected(row, selectedRowIds, getSubRows) {
if (selectedRowIds[row.id]) {
return true;
}
const subRows = getSubRows(row);
if (subRows && subRows.length) {
let allChildrenSelected = true;
let someSelected = false;
for (const subRow of subRows) {
// Bail out early if we know both of these
if (someSelected && !allChildrenSelected) {
break;
}
if (getRowIsSelected(subRow, selectedRowIds, getSubRows)) {
someSelected = true;
} else {
allChildrenSelected = false;
}
}
return allChildrenSelected ? true : someSelected ? null : false;
}
return false;
}