All Downloads are FREE. Search and download functionalities are using the official Maven repository.

package.src.features.RowSelection.ts Maven / Gradle / Ivy

The newest version!
import { TableFeature } from '../core/table'
import { OnChangeFn, Table, Row, RowModel, Updater, RowData } from '../types'
import { makeStateUpdater, memo } from '../utils'

export type RowSelectionState = Record

export interface RowSelectionTableState {
  rowSelection: RowSelectionState
}

export interface RowSelectionOptions {
  enableRowSelection?: boolean | ((row: Row) => boolean)
  enableMultiRowSelection?: boolean | ((row: Row) => boolean)
  enableSubRowSelection?: boolean | ((row: Row) => boolean)
  onRowSelectionChange?: OnChangeFn
  // enableGroupingRowSelection?:
  //   | boolean
  //   | ((
  //       row: Row
  //     ) => boolean)
  // isAdditiveSelectEvent?: (e: unknown) => boolean
  // isInclusiveSelectEvent?: (e: unknown) => boolean
  // selectRowsFn?: (
  //   table: Table,
  //   rowModel: RowModel
  // ) => RowModel
}

export interface RowSelectionRow {
  getIsSelected: () => boolean
  getIsSomeSelected: () => boolean
  getIsAllSubRowsSelected: () => boolean
  getCanSelect: () => boolean
  getCanMultiSelect: () => boolean
  getCanSelectSubRows: () => boolean
  toggleSelected: (value?: boolean) => void
  getToggleSelectedHandler: () => (event: unknown) => void
}

export interface RowSelectionInstance {
  getToggleAllRowsSelectedHandler: () => (event: unknown) => void
  getToggleAllPageRowsSelectedHandler: () => (event: unknown) => void
  setRowSelection: (updater: Updater) => void
  resetRowSelection: (defaultState?: boolean) => void
  getIsAllRowsSelected: () => boolean
  getIsAllPageRowsSelected: () => boolean
  getIsSomeRowsSelected: () => boolean
  getIsSomePageRowsSelected: () => boolean
  toggleAllRowsSelected: (value?: boolean) => void
  toggleAllPageRowsSelected: (value?: boolean) => void
  getPreSelectedRowModel: () => RowModel
  getSelectedRowModel: () => RowModel
  getFilteredSelectedRowModel: () => RowModel
  getGroupedSelectedRowModel: () => RowModel
}

//

export const RowSelection: TableFeature = {
  getInitialState: (state): RowSelectionTableState => {
    return {
      rowSelection: {},
      ...state,
    }
  },

  getDefaultOptions: (
    table: Table
  ): RowSelectionOptions => {
    return {
      onRowSelectionChange: makeStateUpdater('rowSelection', table),
      enableRowSelection: true,
      enableMultiRowSelection: true,
      enableSubRowSelection: true,
      // enableGroupingRowSelection: false,
      // isAdditiveSelectEvent: (e: unknown) => !!e.metaKey,
      // isInclusiveSelectEvent: (e: unknown) => !!e.shiftKey,
    }
  },

  createTable: (
    table: Table
  ): RowSelectionInstance => {
    return {
      setRowSelection: updater => table.options.onRowSelectionChange?.(updater),
      resetRowSelection: defaultState =>
        table.setRowSelection(
          defaultState ? {} : table.initialState.rowSelection ?? {}
        ),
      toggleAllRowsSelected: value => {
        table.setRowSelection(old => {
          value =
            typeof value !== 'undefined' ? value : !table.getIsAllRowsSelected()

          const rowSelection = { ...old }

          const preGroupedFlatRows = table.getPreGroupedRowModel().flatRows

          // We don't use `mutateRowIsSelected` here for performance reasons.
          // All of the rows are flat already, so it wouldn't be worth it
          if (value) {
            preGroupedFlatRows.forEach(row => {
              if (!row.getCanSelect()) {
                return
              }
              rowSelection[row.id] = true
            })
          } else {
            preGroupedFlatRows.forEach(row => {
              delete rowSelection[row.id]
            })
          }

          return rowSelection
        })
      },
      toggleAllPageRowsSelected: value =>
        table.setRowSelection(old => {
          const resolvedValue =
            typeof value !== 'undefined'
              ? value
              : !table.getIsAllPageRowsSelected()

          const rowSelection: RowSelectionState = { ...old }

          table.getRowModel().rows.forEach(row => {
            mutateRowIsSelected(rowSelection, row.id, resolvedValue, table)
          })

          return rowSelection
        }),

      // addRowSelectionRange: rowId => {
      //   const {
      //     rows,
      //     rowsById,
      //     options: { selectGroupingRows, selectSubRows },
      //   } = table

      //   const findSelectedRow = (rows: Row[]) => {
      //     let found
      //     rows.find(d => {
      //       if (d.getIsSelected()) {
      //         found = d
      //         return true
      //       }
      //       const subFound = findSelectedRow(d.subRows || [])
      //       if (subFound) {
      //         found = subFound
      //         return true
      //       }
      //       return false
      //     })
      //     return found
      //   }

      //   const firstRow = findSelectedRow(rows) || rows[0]
      //   const lastRow = rowsById[rowId]

      //   let include = false
      //   const selectedRowIds = {}

      //   const addRow = (row: Row) => {
      //     mutateRowIsSelected(selectedRowIds, row.id, true, {
      //       rowsById,
      //       selectGroupingRows: selectGroupingRows!,
      //       selectSubRows: selectSubRows!,
      //     })
      //   }

      //   table.rows.forEach(row => {
      //     const isFirstRow = row.id === firstRow.id
      //     const isLastRow = row.id === lastRow.id

      //     if (isFirstRow || isLastRow) {
      //       if (!include) {
      //         include = true
      //       } else if (include) {
      //         addRow(row)
      //         include = false
      //       }
      //     }

      //     if (include) {
      //       addRow(row)
      //     }
      //   })

      //   table.setRowSelection(selectedRowIds)
      // },
      getPreSelectedRowModel: () => table.getCoreRowModel(),
      getSelectedRowModel: memo(
        () => [table.getState().rowSelection, table.getCoreRowModel()],
        (rowSelection, rowModel) => {
          if (!Object.keys(rowSelection).length) {
            return {
              rows: [],
              flatRows: [],
              rowsById: {},
            }
          }

          return selectRowsFn(table, rowModel)
        },
        {
          key: process.env.NODE_ENV === 'development' && 'getSelectedRowModel',
          debug: () => table.options.debugAll ?? table.options.debugTable,
        }
      ),

      getFilteredSelectedRowModel: memo(
        () => [table.getState().rowSelection, table.getFilteredRowModel()],
        (rowSelection, rowModel) => {
          if (!Object.keys(rowSelection).length) {
            return {
              rows: [],
              flatRows: [],
              rowsById: {},
            }
          }

          return selectRowsFn(table, rowModel)
        },
        {
          key:
            process.env.NODE_ENV === 'production' &&
            'getFilteredSelectedRowModel',
          debug: () => table.options.debugAll ?? table.options.debugTable,
        }
      ),

      getGroupedSelectedRowModel: memo(
        () => [table.getState().rowSelection, table.getSortedRowModel()],
        (rowSelection, rowModel) => {
          if (!Object.keys(rowSelection).length) {
            return {
              rows: [],
              flatRows: [],
              rowsById: {},
            }
          }

          return selectRowsFn(table, rowModel)
        },
        {
          key:
            process.env.NODE_ENV === 'production' &&
            'getGroupedSelectedRowModel',
          debug: () => table.options.debugAll ?? table.options.debugTable,
        }
      ),

      ///

      // getGroupingRowCanSelect: rowId => {
      //   const row = table.getRow(rowId)

      //   if (!row) {
      //     throw new Error()
      //   }

      //   if (typeof table.options.enableGroupingRowSelection === 'function') {
      //     return table.options.enableGroupingRowSelection(row)
      //   }

      //   return table.options.enableGroupingRowSelection ?? false
      // },

      getIsAllRowsSelected: () => {
        const preGroupedFlatRows = table.getFilteredRowModel().flatRows
        const { rowSelection } = table.getState()

        let isAllRowsSelected = Boolean(
          preGroupedFlatRows.length && Object.keys(rowSelection).length
        )

        if (isAllRowsSelected) {
          if (
            preGroupedFlatRows.some(
              row => row.getCanSelect() && !rowSelection[row.id]
            )
          ) {
            isAllRowsSelected = false
          }
        }

        return isAllRowsSelected
      },

      getIsAllPageRowsSelected: () => {
        const paginationFlatRows = table
          .getPaginationRowModel()
          .flatRows.filter(row => row.getCanSelect())
        const { rowSelection } = table.getState()

        let isAllPageRowsSelected = !!paginationFlatRows.length

        if (
          isAllPageRowsSelected &&
          paginationFlatRows.some(row => !rowSelection[row.id])
        ) {
          isAllPageRowsSelected = false
        }

        return isAllPageRowsSelected
      },

      getIsSomeRowsSelected: () => {
        const totalSelected = Object.keys(
          table.getState().rowSelection ?? {}
        ).length
        return (
          totalSelected > 0 &&
          totalSelected < table.getFilteredRowModel().flatRows.length
        )
      },

      getIsSomePageRowsSelected: () => {
        const paginationFlatRows = table.getPaginationRowModel().flatRows
        return table.getIsAllPageRowsSelected()
          ? false
          : paginationFlatRows
              .filter(row => row.getCanSelect())
              .some(d => d.getIsSelected() || d.getIsSomeSelected())
      },

      getToggleAllRowsSelectedHandler: () => {
        return (e: unknown) => {
          table.toggleAllRowsSelected(
            ((e as MouseEvent).target as HTMLInputElement).checked
          )
        }
      },

      getToggleAllPageRowsSelectedHandler: () => {
        return (e: unknown) => {
          table.toggleAllPageRowsSelected(
            ((e as MouseEvent).target as HTMLInputElement).checked
          )
        }
      },
    }
  },

  createRow: (
    row: Row,
    table: Table
  ): RowSelectionRow => {
    return {
      toggleSelected: value => {
        const isSelected = row.getIsSelected()

        table.setRowSelection(old => {
          value = typeof value !== 'undefined' ? value : !isSelected

          if (isSelected === value) {
            return old
          }

          const selectedRowIds = { ...old }

          mutateRowIsSelected(selectedRowIds, row.id, value, table)

          return selectedRowIds
        })
      },
      getIsSelected: () => {
        const { rowSelection } = table.getState()
        return isRowSelected(row, rowSelection)
      },

      getIsSomeSelected: () => {
        const { rowSelection } = table.getState()
        return isSubRowSelected(row, rowSelection, table) === 'some'
      },

      getIsAllSubRowsSelected: () => {
        const { rowSelection } = table.getState()
        return isSubRowSelected(row, rowSelection, table) === 'all'
      },

      getCanSelect: () => {
        if (typeof table.options.enableRowSelection === 'function') {
          return table.options.enableRowSelection(row)
        }

        return table.options.enableRowSelection ?? true
      },

      getCanSelectSubRows: () => {
        if (typeof table.options.enableSubRowSelection === 'function') {
          return table.options.enableSubRowSelection(row)
        }

        return table.options.enableSubRowSelection ?? true
      },

      getCanMultiSelect: () => {
        if (typeof table.options.enableMultiRowSelection === 'function') {
          return table.options.enableMultiRowSelection(row)
        }

        return table.options.enableMultiRowSelection ?? true
      },
      getToggleSelectedHandler: () => {
        const canSelect = row.getCanSelect()

        return (e: unknown) => {
          if (!canSelect) return
          row.toggleSelected(
            ((e as MouseEvent).target as HTMLInputElement)?.checked
          )
        }
      },
    }
  },
}

const mutateRowIsSelected = (
  selectedRowIds: Record,
  id: string,
  value: boolean,
  table: Table
) => {
  const row = table.getRow(id)

  // const isGrouped = row.getIsGrouped()

  // if ( // TODO: enforce grouping row selection rules
  //   !isGrouped ||
  //   (isGrouped && table.options.enableGroupingRowSelection)
  // ) {
  if (value) {
    if (!row.getCanMultiSelect()) {
      Object.keys(selectedRowIds).forEach(key => delete selectedRowIds[key])
    }
    if (row.getCanSelect()) {
      selectedRowIds[id] = true
    }
  } else {
    delete selectedRowIds[id]
  }
  // }

  if (row.subRows?.length && row.getCanSelectSubRows()) {
    row.subRows.forEach(row =>
      mutateRowIsSelected(selectedRowIds, row.id, value, table)
    )
  }
}

export function selectRowsFn(
  table: Table,
  rowModel: RowModel
): RowModel {
  const rowSelection = table.getState().rowSelection

  const newSelectedFlatRows: Row[] = []
  const newSelectedRowsById: Record> = {}

  // Filters top level and nested rows
  const recurseRows = (rows: Row[], depth = 0): Row[] => {
    return rows
      .map(row => {
        const isSelected = isRowSelected(row, rowSelection)

        if (isSelected) {
          newSelectedFlatRows.push(row)
          newSelectedRowsById[row.id] = row
        }

        if (row.subRows?.length) {
          row = {
            ...row,
            subRows: recurseRows(row.subRows, depth + 1),
          }
        }

        if (isSelected) {
          return row
        }
      })
      .filter(Boolean) as Row[]
  }

  return {
    rows: recurseRows(rowModel.rows),
    flatRows: newSelectedFlatRows,
    rowsById: newSelectedRowsById,
  }
}

export function isRowSelected(
  row: Row,
  selection: Record
): boolean {
  return selection[row.id] ?? false
}

export function isSubRowSelected(
  row: Row,
  selection: Record,
  table: Table
): boolean | 'some' | 'all' {
  if (row.subRows && row.subRows.length) {
    let allChildrenSelected = true
    let someSelected = false

    row.subRows.forEach(subRow => {
      // Bail out early if we know both of these
      if (someSelected && !allChildrenSelected) {
        return
      }

      if (isRowSelected(subRow, selection)) {
        someSelected = true
      } else {
        allChildrenSelected = false
      }
    })

    return allChildrenSelected ? 'all' : someSelected ? 'some' : false
  }

  return false
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy