/* eslint no-param-reassign: ["error", { "props": false }] */ import { ChangeEvent, useCallback, useMemo } from 'react'; import { actions, makePropGetter, ensurePluginOrder, useGetLatest, useMountedLayoutEffect, Hooks, TableInstance, TableState, ActionType, ReducerTableState, IdType, Row, PropGetter, TableToggleRowsSelectedProps, TableToggleAllRowsSelectedProps, } from 'react-table'; type DefaultType = Record; interface UseRowSelectTableInstance extends TableInstance { isAllRowSelected: boolean; selectSubRows: boolean; getSubRows(row: Row): Row[]; isRowSelectable?(row: Row): boolean; } const pluginName = 'useRowSelect'; // Actions actions.resetSelectedRows = 'resetSelectedRows'; actions.toggleAllRowsSelected = 'toggleAllRowsSelected'; actions.toggleRowSelected = 'toggleRowSelected'; actions.toggleAllPageRowsSelected = 'toggleAllPageRowsSelected'; export function useRowSelect(hooks: Hooks) { hooks.getToggleRowSelectedProps = [ defaultGetToggleRowSelectedProps as PropGetter< D, TableToggleRowsSelectedProps >, ]; hooks.getToggleAllRowsSelectedProps = [ defaultGetToggleAllRowsSelectedProps as PropGetter< D, TableToggleAllRowsSelectedProps >, ]; hooks.getToggleAllPageRowsSelectedProps = [ defaultGetToggleAllPageRowsSelectedProps as PropGetter< D, TableToggleAllRowsSelectedProps >, ]; hooks.stateReducers.push( reducer as ( newState: TableState, action: ActionType, previousState?: TableState, instance?: TableInstance ) => ReducerTableState | undefined ); hooks.useInstance.push(useInstance as (instance: TableInstance) => void); hooks.prepareRow.push(prepareRow); } useRowSelect.pluginName = pluginName; function defaultGetToggleRowSelectedProps( props: D, { instance, row }: { instance: UseRowSelectTableInstance; row: Row } ) { const { manualRowSelectedKey = 'isSelected', isRowSelectable = defaultIsRowSelectable, } = instance; let checked = false; if (row.original && row.original[manualRowSelectedKey]) { checked = true; } else { checked = row.isSelected; } return [ props, { onChange: (e: ChangeEvent) => { row.toggleRowSelected(e.target.checked); }, style: { cursor: 'pointer', }, checked, title: 'Toggle Row Selected', indeterminate: row.isSomeSelected, disabled: !isRowSelectable(row), }, ]; } function defaultGetToggleAllRowsSelectedProps( props: D, { instance }: { instance: UseRowSelectTableInstance } ) { return [ props, { onChange: (e: ChangeEvent) => { instance.toggleAllRowsSelected(e.target.checked); }, style: { cursor: 'pointer', }, checked: instance.isAllRowsSelected, title: 'Toggle All Rows Selected', indeterminate: Boolean( !instance.isAllRowsSelected && Object.keys(instance.state.selectedRowIds).length ), }, ]; } function defaultGetToggleAllPageRowsSelectedProps( props: D, { instance }: { instance: UseRowSelectTableInstance } ) { return [ props, { onChange(e: ChangeEvent) { instance.toggleAllPageRowsSelected(e.target.checked); }, style: { cursor: 'pointer', }, checked: instance.isAllPageRowsSelected, title: 'Toggle All Current Page Rows Selected', indeterminate: Boolean( !instance.isAllPageRowsSelected && instance.page.some(({ id }) => instance.state.selectedRowIds[id]) ), }, ]; } function reducer>( state: TableState, action: ActionType, _previousState?: TableState, instance?: UseRowSelectTableInstance ) { if (action.type === actions.init) { return { ...state, selectedRowIds: , boolean>>{}, }; } if (action.type === actions.resetSelectedRows) { return { ...state, selectedRowIds: instance?.initialState.selectedRowIds || {}, }; } if (action.type === actions.toggleAllRowsSelected) { const { value: setSelected } = action; if (!instance) { return state; } const { isAllRowsSelected, rowsById, nonGroupedRowsById = rowsById, isRowSelectable = defaultIsRowSelectable, } = instance; const selectAll = typeof setSelected !== 'undefined' ? setSelected : !isAllRowsSelected; // Only remove/add the rows that are visible on the screen // Leave all the other rows that are selected alone. const selectedRowIds = { ...state.selectedRowIds }; Object.keys(nonGroupedRowsById).forEach((rowId: IdType) => { if (selectAll) { const row = rowsById[rowId]; if (isRowSelectable(row)) { selectedRowIds[rowId] = true; } } else { delete selectedRowIds[rowId]; } }); return { ...state, selectedRowIds, }; } if (action.type === actions.toggleRowSelected) { if (!instance) { return state; } const { id, value: setSelected } = action; const { rowsById, selectSubRows = true, getSubRows, isRowSelectable = defaultIsRowSelectable, } = instance; const isSelected = state.selectedRowIds[id]; const shouldExist = typeof setSelected !== 'undefined' ? setSelected : !isSelected; if (isSelected === shouldExist) { return state; } const newSelectedRowIds = { ...state.selectedRowIds }; // eslint-disable-next-line no-inner-declarations function handleRowById(id: IdType) { const row = rowsById[id]; if (!isRowSelectable(row)) { return; } if (!row.isGrouped) { if (shouldExist) { newSelectedRowIds[id] = true; } else { delete newSelectedRowIds[id]; } } if (selectSubRows && getSubRows(row)) { getSubRows(row).forEach((row) => handleRowById(row.id)); } } handleRowById(id); return { ...state, selectedRowIds: newSelectedRowIds, }; } if (action.type === actions.toggleAllPageRowsSelected) { if (!instance) { return state; } const { value: setSelected } = action; const { page, rowsById, selectSubRows = true, isAllPageRowsSelected, getSubRows, } = instance; const selectAll = typeof setSelected !== 'undefined' ? setSelected : !isAllPageRowsSelected; const newSelectedRowIds = { ...state.selectedRowIds }; // eslint-disable-next-line no-inner-declarations function handleRowById(id: IdType) { const row = rowsById[id]; if (!row.isGrouped) { if (selectAll) { newSelectedRowIds[id] = true; } else { delete newSelectedRowIds[id]; } } if (selectSubRows && getSubRows(row)) { getSubRows(row).forEach((row) => handleRowById(row.id)); } } page.forEach((row) => handleRowById(row.id)); return { ...state, selectedRowIds: newSelectedRowIds, }; } return state; } function useInstance>( instance: UseRowSelectTableInstance ) { const { data, rows, getHooks, plugins, rowsById, nonGroupedRowsById = rowsById, autoResetSelectedRows = true, state: { selectedRowIds }, selectSubRows = true, dispatch, page, getSubRows, isRowSelectable = defaultIsRowSelectable, } = instance; ensurePluginOrder( plugins, ['useFilters', 'useGroupBy', 'useSortBy', 'useExpanded', 'usePagination'], 'useRowSelect' ); const selectedFlatRows = useMemo(() => { const selectedFlatRows = >>[]; rows.forEach((row) => { const isSelected = selectSubRows ? getRowIsSelected(row, selectedRowIds, getSubRows) : !!selectedRowIds[row.id]; row.isSelected = !!isSelected; row.isSomeSelected = isSelected === null; if (isSelected) { selectedFlatRows.push(row); } }); return selectedFlatRows; }, [rows, selectSubRows, selectedRowIds, getSubRows]); let isAllRowsSelected = Boolean( Object.keys(nonGroupedRowsById).length && Object.keys(selectedRowIds).length ); let isAllPageRowsSelected = isAllRowsSelected; if (isAllRowsSelected) { if ( Object.keys(nonGroupedRowsById).some((id) => { const row = rowsById[id]; return !selectedRowIds[id] && isRowSelectable(row); }) ) { isAllRowsSelected = false; } } if (!isAllRowsSelected) { if ( page && page.length && page.some(({ id }) => { const row = rowsById[id]; return !selectedRowIds[id] && isRowSelectable(row); }) ) { isAllPageRowsSelected = false; } } 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); const getToggleAllRowsSelectedProps = makePropGetter( getHooks().getToggleAllRowsSelectedProps, { instance: getInstance() } ); const getToggleAllPageRowsSelectedProps = makePropGetter( getHooks().getToggleAllPageRowsSelectedProps, { instance: getInstance() } ); Object.assign(instance, { selectedFlatRows, isAllRowsSelected, isAllPageRowsSelected, toggleRowSelected, toggleAllRowsSelected, getToggleAllRowsSelectedProps, getToggleAllPageRowsSelectedProps, toggleAllPageRowsSelected, }); } function prepareRow>( row: Row, { instance }: { instance: TableInstance } ) { row.toggleRowSelected = (set) => instance.toggleRowSelected(row.id, set); row.getToggleRowSelectedProps = makePropGetter( instance.getHooks().getToggleRowSelectedProps, { instance, row } ); } function getRowIsSelected>( row: Row, selectedRowIds: Record, boolean>, getSubRows: (row: Row) => Array> ) { if (selectedRowIds[row.id]) { return true; } const subRows = getSubRows(row); if (subRows && subRows.length) { let allChildrenSelected = true; let someSelected = false; subRows.forEach((subRow) => { // Bail out early if we know both of these if (someSelected && !allChildrenSelected) { return; } if (getRowIsSelected(subRow, selectedRowIds, getSubRows)) { someSelected = true; } else { allChildrenSelected = false; } }); if (allChildrenSelected) { return true; } return someSelected ? null : false; } return false; } function defaultIsRowSelectable(row: Row) { return !row.original.disabled; }