import React from 'react' import { actions, mergeProps, applyPropHooks, ensurePluginOrder, useGetLatest, useMountedLayoutEffect, } from '../utils' const pluginName = 'useRowSelect' // Actions actions.resetSelectedRows = 'resetSelectedRows' actions.toggleRowSelectedAll = 'toggleRowSelectedAll' actions.toggleRowSelected = 'toggleRowSelected' export const useRowSelect = hooks => { hooks.getToggleRowSelectedProps = [] hooks.getToggleAllRowsSelectedProps = [] hooks.stateReducers.push(reducer) hooks.useRows.push(useRows) hooks.useInstance.push(useInstance) } useRowSelect.pluginName = pluginName function reducer(state, action, previousState, instanceRef) { if (action.type === actions.init) { return { selectedRowPaths: new Set(), ...state, } } if (action.type === actions.resetSelectedRows) { return { ...state, selectedRowPaths: new Set(), } } if (action.type === actions.toggleRowSelectedAll) { const { selected } = action const { isAllRowsSelected, flatRowPaths } = instanceRef.current const selectAll = typeof selected !== 'undefined' ? selected : !isAllRowsSelected return { ...state, selectedRowPaths: selectAll ? new Set(flatRowPaths) : new Set(), } } if (action.type === actions.toggleRowSelected) { const { path, selected } = action const { flatRowPaths } = instanceRef.current const key = path.join('.') const childRowPrefixKey = [key, '.'].join('') // Join the paths of deep rows // to make a key, then manage all of the keys // in a flat object const exists = state.selectedRowPaths.has(key) const shouldExist = typeof set !== 'undefined' ? selected : !exists let newSelectedRowPaths = new Set(state.selectedRowPaths) if (!exists && shouldExist) { flatRowPaths.forEach(rowPath => { if (rowPath === key || rowPath.startsWith(childRowPrefixKey)) { newSelectedRowPaths.add(rowPath) } }) } else if (exists && !shouldExist) { flatRowPaths.forEach(rowPath => { if (rowPath === key || rowPath.startsWith(childRowPrefixKey)) { newSelectedRowPaths.delete(rowPath) } }) } else { return state } const updateParentRow = (selectedRowPaths, path) => { const parentPath = path.slice(0, path.length - 1) const parentKey = parentPath.join('.') const selected = flatRowPaths.filter(rowPath => { const path = rowPath return ( path !== parentKey && path.startsWith(parentKey) && !selectedRowPaths.has(path) ) }).length === 0 if (selected) { selectedRowPaths.add(parentKey) } else { selectedRowPaths.delete(parentKey) } if (parentPath.length > 1) updateParentRow(selectedRowPaths, parentPath) } // If the row is a subRow update // its parent row to reflect changes if (path.length > 1) updateParentRow(newSelectedRowPaths, path) return { ...state, selectedRowPaths: newSelectedRowPaths, } } } function useRows(rows, instance) { const { state: { selectedRowPaths }, } = instance instance.selectedFlatRows = React.useMemo(() => { const selectedFlatRows = [] rows.forEach(row => { row.isSelected = getRowIsSelected(row, selectedRowPaths) if (row.isSelected) { selectedFlatRows.push(row) } }) return selectedFlatRows }, [rows, selectedRowPaths]) return rows } function useInstance(instance) { const { data, hooks, manualRowSelectedKey = 'isSelected', plugins, flatRows, autoResetSelectedRows = true, state: { selectedRowPaths }, dispatch, } = instance ensurePluginOrder( plugins, ['useFilters', 'useGroupBy', 'useSortBy'], 'useRowSelect', [] ) const flatRowPaths = flatRows.map(d => d.path.join('.')) let isAllRowsSelected = !!flatRowPaths.length && !!selectedRowPaths.size if (isAllRowsSelected) { if (flatRowPaths.some(d => !selectedRowPaths.has(d))) { isAllRowsSelected = false } } const getAutoResetSelectedRows = useGetLatest(autoResetSelectedRows) useMountedLayoutEffect(() => { if (getAutoResetSelectedRows()) { dispatch({ type: actions.resetSelectedRows }) } }, [dispatch, data]) const toggleRowSelectedAll = selected => dispatch({ type: actions.toggleRowSelectedAll, selected }) const toggleRowSelected = (path, selected) => dispatch({ type: actions.toggleRowSelected, path, selected }) // use reference to avoid memory leak in #1608 const instanceRef = React.useRef() instanceRef.current = instance const getToggleAllRowsSelectedProps = props => { return mergeProps( { onChange: e => { toggleRowSelectedAll(e.target.checked) }, style: { cursor: 'pointer', }, checked: isAllRowsSelected, title: 'Toggle All Rows Selected', }, applyPropHooks( instanceRef.current.hooks.getToggleAllRowsSelectedProps, instanceRef.current ), props ) } hooks.prepareRow.push(row => { row.toggleRowSelected = set => toggleRowSelected(row.path, set) row.getToggleRowSelectedProps = props => { let checked = false if (row.original && row.original[manualRowSelectedKey]) { checked = true } else { checked = row.isSelected } return mergeProps( { onChange: e => { row.toggleRowSelected(e.target.checked) }, style: { cursor: 'pointer', }, checked, title: 'Toggle Row Selected', }, applyPropHooks( instanceRef.current.hooks.getToggleRowSelectedProps, row, instanceRef.current ), props ) } return row }) return { ...instance, flatRowPaths, toggleRowSelected, toggleRowSelectedAll, getToggleAllRowsSelectedProps, isAllRowsSelected, } } function getRowIsSelected(row, selectedRowPaths) { if (row.isAggregated) { return row.subRows.every(subRow => getRowIsSelected(subRow, selectedRowPaths) ) } return selectedRowPaths.has(row.path.join('.')) }