mirror of
https://github.com/gosticks/react-table.git
synced 2026-07-01 01:50:02 +00:00
Rename hook files
This commit is contained in:
11
.babelrc
Normal file
11
.babelrc
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/env",
|
||||
{
|
||||
"modules": false
|
||||
}
|
||||
],
|
||||
"@babel/react"
|
||||
]
|
||||
}
|
||||
15
.eslintrc
Normal file
15
.eslintrc
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"parser": "babel-eslint",
|
||||
"extends": ["standard", "standard-react"],
|
||||
"env": {
|
||||
"es6": true
|
||||
},
|
||||
"plugins": ["react"],
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"space-before-function-paren": 0,
|
||||
"react/jsx-boolean-value": 0
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
extends: 'react-tools',
|
||||
}
|
||||
2
dist/index.js
vendored
Normal file
2
dist/index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
dist/index.js.map
vendored
Normal file
1
dist/index.js.map
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
dist/index.js.zip
vendored
Normal file
BIN
dist/index.js.zip
vendored
Normal file
Binary file not shown.
@@ -1,42 +1,32 @@
|
||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
||||
import babel from 'rollup-plugin-babel'
|
||||
import replace from 'rollup-plugin-replace'
|
||||
import commonjs from 'rollup-plugin-commonjs'
|
||||
import uglify from 'rollup-plugin-uglify'
|
||||
import external from 'rollup-plugin-peer-deps-external'
|
||||
import resolve from 'rollup-plugin-node-resolve'
|
||||
import { uglify } from 'rollup-plugin-uglify'
|
||||
|
||||
const env = process.env.NODE_ENV
|
||||
import pkg from './package.json'
|
||||
|
||||
const config = {
|
||||
export default {
|
||||
input: 'src/index.js',
|
||||
output: {
|
||||
file: env === 'production' ? 'react-table.min.js' : 'react-table.js',
|
||||
format: 'umd',
|
||||
globals: {
|
||||
react: 'React',
|
||||
},
|
||||
name: 'ReactTable',
|
||||
exports: 'named',
|
||||
},
|
||||
external: ['react'],
|
||||
plugins: [
|
||||
nodeResolve(),
|
||||
babel({
|
||||
exclude: '**/node_modules/**',
|
||||
}),
|
||||
replace({
|
||||
'process.env.NODE_ENV': JSON.stringify(env),
|
||||
}),
|
||||
commonjs(),
|
||||
output: [
|
||||
{
|
||||
file: pkg.main,
|
||||
format: 'cjs',
|
||||
sourcemap: true
|
||||
}
|
||||
// {
|
||||
// file: pkg.module,
|
||||
// format: "es",
|
||||
// sourcemap: true
|
||||
// }
|
||||
],
|
||||
plugins: [
|
||||
external(),
|
||||
babel({
|
||||
exclude: 'node_modules/**'
|
||||
}),
|
||||
resolve(),
|
||||
commonjs(),
|
||||
uglify()
|
||||
]
|
||||
}
|
||||
|
||||
if (env === 'production') {
|
||||
config.plugins.push(uglify({
|
||||
compress: {
|
||||
dead_code: true,
|
||||
warnings: false,
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
export default config
|
||||
|
||||
9
src/actions.js
Executable file
9
src/actions.js
Executable file
@@ -0,0 +1,9 @@
|
||||
const actions = {}
|
||||
|
||||
export { actions }
|
||||
|
||||
export const addActions = acts => {
|
||||
Object.keys(acts).forEach(key => {
|
||||
actions[key] = acts[key]
|
||||
})
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
export function sum (values, rows) {
|
||||
export function sum(values, rows) {
|
||||
return values.reduce((sum, next) => sum + next, 0)
|
||||
}
|
||||
|
||||
export function average (values, rows) {
|
||||
export function average(values, rows) {
|
||||
return Math.round((sum(values, rows) / values.length) * 100) / 100
|
||||
}
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export default function useAccessedRows ({
|
||||
debug, data, columns, subRowsKey,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (debug) console.info('getAccessedRows')
|
||||
|
||||
// Access the row's data
|
||||
const accessRow = (data, i, depth = 0) => {
|
||||
// Keep the original reference around
|
||||
const original = data
|
||||
|
||||
// Process any subRows
|
||||
const subRows = data[subRowsKey]
|
||||
? data[subRowsKey].map((d, i) => accessRow(d, i, depth + 1))
|
||||
: undefined
|
||||
|
||||
const row = {
|
||||
original,
|
||||
index: i,
|
||||
subRows,
|
||||
depth,
|
||||
}
|
||||
|
||||
// Create the cells and values
|
||||
row.values = {}
|
||||
columns.forEach(column => {
|
||||
row.values[column.id] = column.accessor ? column.accessor(data) : undefined
|
||||
})
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
// Use the resolved data
|
||||
return data.map((d, i) => accessRow(d, i))
|
||||
},
|
||||
[data, columns]
|
||||
)
|
||||
}
|
||||
@@ -1,45 +1,61 @@
|
||||
import { useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { getFirstDefined, getBy } from '../utils'
|
||||
import { getBy } from '../utils'
|
||||
|
||||
export default function useColumns ({
|
||||
debug,
|
||||
groupBy,
|
||||
userColumns,
|
||||
disableSorting,
|
||||
disableGrouping,
|
||||
disableFilters,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (debug) console.info('getColumns')
|
||||
|
||||
// Decorate All the columns
|
||||
const columnTree = decorateColumnTree(userColumns)
|
||||
|
||||
// Get the flat list of all columns
|
||||
let columns = flattenBy(columnTree, 'columns')
|
||||
|
||||
columns = [
|
||||
...groupBy.map(g => columns.find(col => col.id === g)),
|
||||
...columns.filter(col => !groupBy.includes(col.id)),
|
||||
]
|
||||
|
||||
// Get headerGroups
|
||||
const headerGroups = makeHeaderGroups(columns, findMaxDepth(columnTree))
|
||||
const headers = flattenBy(headerGroups, 'headers')
|
||||
|
||||
return {
|
||||
columns,
|
||||
headerGroups,
|
||||
headers,
|
||||
}
|
||||
},
|
||||
[groupBy, userColumns]
|
||||
const propTypes = {
|
||||
// General
|
||||
columns: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
Cell: PropTypes.any,
|
||||
Header: PropTypes.any
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
export const useColumns = props => {
|
||||
const {
|
||||
debug,
|
||||
columns: userColumns,
|
||||
state: [{ groupBy }]
|
||||
} = props
|
||||
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useColumns')
|
||||
|
||||
const { columns, headerGroups, headers } = useMemo(() => {
|
||||
if (debug) console.info('getColumns')
|
||||
|
||||
// Decorate All the columns
|
||||
let columnTree = decorateColumnTree(userColumns)
|
||||
|
||||
// Get the flat list of all columns
|
||||
let columns = flattenBy(columnTree, 'columns')
|
||||
|
||||
columns = [
|
||||
...groupBy.map(g => columns.find(col => col.id === g)),
|
||||
...columns.filter(col => !groupBy.includes(col.id))
|
||||
]
|
||||
|
||||
// Get headerGroups
|
||||
const headerGroups = makeHeaderGroups(columns, findMaxDepth(columnTree))
|
||||
const headers = flattenBy(headerGroups, 'headers')
|
||||
|
||||
return {
|
||||
columns,
|
||||
headerGroups,
|
||||
headers
|
||||
}
|
||||
}, [groupBy, userColumns])
|
||||
|
||||
return {
|
||||
...props,
|
||||
columns,
|
||||
headerGroups,
|
||||
headers
|
||||
}
|
||||
|
||||
// Find the depth of the columns
|
||||
function findMaxDepth (columns, depth = 0) {
|
||||
function findMaxDepth(columns, depth = 0) {
|
||||
return columns.reduce((prev, curr) => {
|
||||
if (curr.columns) {
|
||||
return Math.max(prev, findMaxDepth(curr.columns, depth + 1))
|
||||
@@ -48,11 +64,9 @@ export default function useColumns ({
|
||||
}, 0)
|
||||
}
|
||||
|
||||
function decorateColumn (column, parent) {
|
||||
function decorateColumn(column, parent) {
|
||||
// First check for string accessor
|
||||
let {
|
||||
id, accessor, Header, canSortBy, canGroupBy, canFilter,
|
||||
} = column
|
||||
let { id, accessor, Header } = column
|
||||
|
||||
if (typeof accessor === 'string') {
|
||||
id = id || accessor
|
||||
@@ -60,18 +74,6 @@ export default function useColumns ({
|
||||
accessor = row => getBy(row, accessorString)
|
||||
}
|
||||
|
||||
const grouped = groupBy.includes(id)
|
||||
|
||||
canSortBy = accessor
|
||||
? getFirstDefined(canSortBy, disableSorting === true ? false : undefined, true)
|
||||
: false
|
||||
canGroupBy = accessor
|
||||
? getFirstDefined(canGroupBy, disableGrouping === true ? false : undefined, true)
|
||||
: false
|
||||
canFilter = accessor
|
||||
? getFirstDefined(canFilter, disableFilters === true ? false : undefined, true)
|
||||
: false
|
||||
|
||||
if (!id && typeof Header === 'string') {
|
||||
id = Header
|
||||
}
|
||||
@@ -87,22 +89,16 @@ export default function useColumns ({
|
||||
Cell: cell => cell.value,
|
||||
show: true,
|
||||
...column,
|
||||
grouped,
|
||||
canSortBy,
|
||||
canGroupBy,
|
||||
canFilter,
|
||||
id,
|
||||
accessor,
|
||||
parent,
|
||||
parent
|
||||
}
|
||||
|
||||
column.Aggregated = column.Aggregated || column.Cell
|
||||
|
||||
return column
|
||||
}
|
||||
|
||||
// Build the visible columns, headers and flat column list
|
||||
function decorateColumnTree (columns, parent, depth = 0) {
|
||||
function decorateColumnTree(columns, parent, depth = 0) {
|
||||
return columns.map(column => {
|
||||
column = decorateColumn(column, parent)
|
||||
if (column.columns) {
|
||||
@@ -112,7 +108,7 @@ export default function useColumns ({
|
||||
})
|
||||
}
|
||||
|
||||
function flattenBy (columns, childKey) {
|
||||
function flattenBy(columns, childKey) {
|
||||
const flatColumns = []
|
||||
|
||||
const recurse = columns => {
|
||||
@@ -131,7 +127,7 @@ export default function useColumns ({
|
||||
}
|
||||
|
||||
// Build the header groups from the bottom up
|
||||
function makeHeaderGroups (columns, maxDepth) {
|
||||
function makeHeaderGroups(columns, maxDepth) {
|
||||
const headerGroups = []
|
||||
|
||||
const removeChildColumns = column => {
|
||||
@@ -144,7 +140,7 @@ export default function useColumns ({
|
||||
|
||||
const buildGroup = (columns, depth = 0) => {
|
||||
const headerGroup = {
|
||||
headers: [],
|
||||
headers: []
|
||||
}
|
||||
|
||||
const parentColumns = []
|
||||
@@ -161,16 +157,24 @@ export default function useColumns ({
|
||||
parentColumns.push({
|
||||
...column.parent,
|
||||
originalID: column.parent.id,
|
||||
id: [column.parent.id, parentColumns.length].join('_'),
|
||||
id: [column.parent.id, parentColumns.length].join('_')
|
||||
})
|
||||
}
|
||||
} else if (hasParents) {
|
||||
// If other columns have parents, add a place holder if necessary
|
||||
const placeholderColumn = decorateColumn({
|
||||
originalID: [column.id, 'placeholder', maxDepth - depth].join('_'),
|
||||
id: [column.id, 'placeholder', maxDepth - depth, parentColumns.length].join('_'),
|
||||
id: [
|
||||
column.id,
|
||||
'placeholder',
|
||||
maxDepth - depth,
|
||||
parentColumns.length
|
||||
].join('_')
|
||||
})
|
||||
if (isFirst || latestParentColumn.originalID !== placeholderColumn.originalID) {
|
||||
if (
|
||||
isFirst ||
|
||||
latestParentColumn.originalID !== placeholderColumn.originalID
|
||||
) {
|
||||
parentColumns.push(placeholderColumn)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
const mergeStateAndProps = (state, defaults, props) => {
|
||||
Object.keys(props).forEach(key => {
|
||||
if (typeof props[key] !== 'undefined') {
|
||||
state[key] = props[key]
|
||||
}
|
||||
})
|
||||
|
||||
Object.keys(defaults).forEach(key => {
|
||||
if (typeof state[key] === 'undefined') {
|
||||
state[key] = defaults[key]
|
||||
}
|
||||
})
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
export default function useControlledState ([userState, userSetState], defaults, props) {
|
||||
const state = mergeStateAndProps(userState, defaults, props)
|
||||
|
||||
const setState = (updater, type) =>
|
||||
userSetState(old => updater(mergeStateAndProps(old, defaults, props)), type)
|
||||
|
||||
return [state, setState]
|
||||
}
|
||||
105
src/hooks/useExpanded.js
Executable file
105
src/hooks/useExpanded.js
Executable file
@@ -0,0 +1,105 @@
|
||||
import { useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { getBy, getFirstDefined, setBy } from '../utils'
|
||||
import { addActions, actions } from '../actions'
|
||||
import { defaultState } from './useTableState'
|
||||
|
||||
defaultState.expanded = {}
|
||||
|
||||
addActions({
|
||||
toggleExpanded: '__toggleExpanded__',
|
||||
useExpanded: '__useExpanded__'
|
||||
})
|
||||
|
||||
const propTypes = {
|
||||
expandedKey: PropTypes.string
|
||||
}
|
||||
|
||||
export const useExpanded = props => {
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useExpanded')
|
||||
|
||||
const {
|
||||
debug,
|
||||
columns,
|
||||
rows,
|
||||
expandedKey = 'expanded',
|
||||
hooks,
|
||||
state: [{ expanded }, setState]
|
||||
} = props
|
||||
|
||||
const toggleExpandedByPath = (path, set) => {
|
||||
return setState(old => {
|
||||
const { expanded } = old
|
||||
const existing = getBy(expanded, path)
|
||||
set = getFirstDefined(set, !existing)
|
||||
return {
|
||||
...old,
|
||||
expanded: setBy(expanded, path, set)
|
||||
}
|
||||
}, actions.toggleExpanded)
|
||||
}
|
||||
|
||||
hooks.row.push(row => {
|
||||
const { path } = row
|
||||
row.toggleExpanded = set => toggleExpandedByPath(path, set)
|
||||
})
|
||||
|
||||
const expandedRows = useMemo(() => {
|
||||
if (debug) console.info('getExpandedRows')
|
||||
|
||||
const expandedRows = []
|
||||
|
||||
// Here we do some mutation, but it's the last stage in the
|
||||
// immutable process so this is safe
|
||||
const handleRow = (row, index, depth = 0, parentPath = []) => {
|
||||
// Compute some final state for the row
|
||||
const path = [...parentPath, index]
|
||||
|
||||
row.path = path
|
||||
row.depth = depth
|
||||
|
||||
row.isExpanded =
|
||||
(row.original && row.original[expandedKey]) || getBy(expanded, path)
|
||||
|
||||
row.cells = columns.map(column => {
|
||||
const cell = {
|
||||
column,
|
||||
row,
|
||||
state: null,
|
||||
value: row.values[column.id]
|
||||
}
|
||||
|
||||
return cell
|
||||
})
|
||||
|
||||
expandedRows.push(row)
|
||||
|
||||
if (row.isExpanded && row.subRows && row.subRows.length) {
|
||||
row.subRows.forEach((row, i) => handleRow(row, i, depth + 1, path))
|
||||
}
|
||||
}
|
||||
|
||||
rows.forEach((row, i) => handleRow(row, i))
|
||||
|
||||
return expandedRows
|
||||
}, [rows, expanded, columns])
|
||||
|
||||
const expandedDepth = findExpandedDepth(expanded)
|
||||
|
||||
return {
|
||||
...props,
|
||||
toggleExpandedByPath,
|
||||
expandedDepth,
|
||||
rows: expandedRows
|
||||
}
|
||||
}
|
||||
|
||||
function findExpandedDepth(obj, depth = 1) {
|
||||
return Object.values(obj).reduce((prev, curr) => {
|
||||
if (typeof curr === 'object') {
|
||||
return Math.max(prev, findExpandedDepth(curr, depth + 1))
|
||||
}
|
||||
return depth
|
||||
}, 0)
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import { useMemo, useEffect } from 'react'
|
||||
|
||||
import { getBy } from '../utils'
|
||||
|
||||
export default function useExpandedRows ({
|
||||
debug,
|
||||
expanded,
|
||||
expandedKey,
|
||||
columns,
|
||||
rows,
|
||||
setState,
|
||||
actions,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (debug) console.info('getExpandedRows')
|
||||
|
||||
const expandedRows = []
|
||||
|
||||
// Here we do some mutation, but it's the last stage in the
|
||||
// immutable process so this is safe
|
||||
const handleRow = (row, index, depth = 0, parentPath = []) => {
|
||||
// Compute some final state for the row
|
||||
const path = [...parentPath, index]
|
||||
|
||||
row.path = path
|
||||
row.depth = depth
|
||||
|
||||
row.isExpanded = (row.original && row.original[expandedKey]) || getBy(expanded, path)
|
||||
|
||||
row.cells = columns.map(column => {
|
||||
const cell = {
|
||||
column,
|
||||
row,
|
||||
state: null,
|
||||
value: row.values[column.id],
|
||||
}
|
||||
|
||||
return cell
|
||||
})
|
||||
|
||||
expandedRows.push(row)
|
||||
|
||||
if (row.isExpanded && row.subRows && row.subRows.length) {
|
||||
row.subRows.forEach((row, i) => handleRow(row, i, depth + 1, path))
|
||||
}
|
||||
}
|
||||
|
||||
rows.forEach((row, i) => handleRow(row, i))
|
||||
|
||||
return expandedRows
|
||||
},
|
||||
[rows, expanded, columns]
|
||||
)
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export default function useFilteredRows ({
|
||||
debug,
|
||||
filters,
|
||||
rows,
|
||||
columns,
|
||||
filterFn,
|
||||
manualFilters,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (manualFilters || !Object.keys(filters).length) {
|
||||
return rows
|
||||
}
|
||||
|
||||
if (debug) console.info('getFilteredRows')
|
||||
|
||||
// Filters top level and nested rows
|
||||
const filterRows = rows => {
|
||||
let filteredRows = rows
|
||||
|
||||
filteredRows = Object.entries(filters).reduce((filteredSoFar, [columnID, filterValue]) => {
|
||||
// Find the filters column
|
||||
const column = columns.find(d => d.id === columnID)
|
||||
|
||||
// Don't filter hidden columns or columns that have had their filters disabled
|
||||
if (!column || column.filterable === false) {
|
||||
return filteredSoFar
|
||||
}
|
||||
|
||||
const filterMethod = column.filterMethod || filterFn
|
||||
|
||||
// If 'filterAll' is set to true, pass the entire dataset to the filter method
|
||||
if (column.filterAll) {
|
||||
return filterMethod(filteredSoFar, columnID, filterValue, column)
|
||||
}
|
||||
return filteredSoFar.filter(row => filterMethod(row, columnID, filterValue, column))
|
||||
}, rows)
|
||||
|
||||
// Apply the filter to any subRows
|
||||
filteredRows = filteredRows.map(row => {
|
||||
if (!row.subRows) {
|
||||
return row
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
subRows: filterRows(row.subRows),
|
||||
}
|
||||
})
|
||||
|
||||
// then filter any rows without subcolumns because it would be strange to show
|
||||
filteredRows = filteredRows.filter(row => {
|
||||
if (!row.subRows) {
|
||||
return true
|
||||
}
|
||||
return row.subRows.length > 0
|
||||
})
|
||||
|
||||
return filteredRows
|
||||
}
|
||||
|
||||
return filterRows(rows)
|
||||
},
|
||||
[rows, filters, manualFilters]
|
||||
)
|
||||
}
|
||||
161
src/hooks/useFilters.js
Executable file
161
src/hooks/useFilters.js
Executable file
@@ -0,0 +1,161 @@
|
||||
import { useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { defaultFilterFn, getFirstDefined } from '../utils'
|
||||
import { addActions, actions } from '../actions'
|
||||
import { defaultState } from './useTableState'
|
||||
|
||||
defaultState.filters = {}
|
||||
addActions({
|
||||
setFilter: '__setFilter__',
|
||||
setAllFilters: '__setAllFilters__'
|
||||
})
|
||||
|
||||
const propTypes = {
|
||||
// General
|
||||
columns: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
filterFn: PropTypes.func,
|
||||
filterAll: PropTypes.bool,
|
||||
canFilter: PropTypes.bool,
|
||||
Filter: PropTypes.any
|
||||
})
|
||||
),
|
||||
|
||||
filterFn: PropTypes.func,
|
||||
manualFilters: PropTypes.bool
|
||||
}
|
||||
|
||||
export const useFilters = props => {
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useFilters')
|
||||
|
||||
const {
|
||||
debug,
|
||||
rows,
|
||||
columns,
|
||||
filterFn = defaultFilterFn,
|
||||
manualFilters,
|
||||
disableFilters,
|
||||
hooks,
|
||||
state: [{ filters }, setState]
|
||||
} = props
|
||||
|
||||
columns.forEach(column => {
|
||||
const { id, accessor, canFilter } = column
|
||||
column.canFilter = accessor
|
||||
? getFirstDefined(
|
||||
canFilter,
|
||||
disableFilters === true ? false : undefined,
|
||||
true
|
||||
)
|
||||
: false
|
||||
// Was going to add this to the filter hook
|
||||
column.filterValue = filters[id]
|
||||
})
|
||||
|
||||
const setFilter = (id, val) => {
|
||||
return setState(old => {
|
||||
if (typeof val === 'undefined') {
|
||||
const { [id]: prev, ...rest } = filters
|
||||
return {
|
||||
...old,
|
||||
filters: {
|
||||
...rest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...old,
|
||||
filters: {
|
||||
...filters,
|
||||
[id]: val
|
||||
}
|
||||
}
|
||||
}, actions.setFilter)
|
||||
}
|
||||
|
||||
const setAllFilters = filters => {
|
||||
return setState(old => {
|
||||
return {
|
||||
...old,
|
||||
filters
|
||||
}
|
||||
}, actions.setAllFilters)
|
||||
}
|
||||
|
||||
hooks.columns.push(columns => {
|
||||
columns.forEach(column => {
|
||||
if (column.canFilter) {
|
||||
column.setFilter = val => setFilter(column.id, val)
|
||||
}
|
||||
})
|
||||
return columns
|
||||
})
|
||||
|
||||
const filteredRows = useMemo(() => {
|
||||
if (manualFilters || !Object.keys(filters).length) {
|
||||
return rows
|
||||
}
|
||||
|
||||
if (debug) console.info('getFilteredRows')
|
||||
|
||||
// Filters top level and nested rows
|
||||
const filterRows = rows => {
|
||||
let filteredRows = rows
|
||||
|
||||
filteredRows = Object.entries(filters).reduce(
|
||||
(filteredSoFar, [columnID, filterValue]) => {
|
||||
// Find the filters column
|
||||
const column = columns.find(d => d.id === columnID)
|
||||
|
||||
// Don't filter hidden columns or columns that have had their filters disabled
|
||||
if (!column || column.filterable === false) {
|
||||
return filteredSoFar
|
||||
}
|
||||
|
||||
const filterMethod = column.filterMethod || filterFn
|
||||
|
||||
// If 'filterAll' is set to true, pass the entire dataset to the filter method
|
||||
if (column.filterAll) {
|
||||
return filterMethod(filteredSoFar, columnID, filterValue, column)
|
||||
}
|
||||
return filteredSoFar.filter(row =>
|
||||
filterMethod(row, columnID, filterValue, column)
|
||||
)
|
||||
},
|
||||
rows
|
||||
)
|
||||
|
||||
// Apply the filter to any subRows
|
||||
filteredRows = filteredRows.map(row => {
|
||||
if (!row.subRows) {
|
||||
return row
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
subRows: filterRows(row.subRows)
|
||||
}
|
||||
})
|
||||
|
||||
// then filter any rows without subcolumns because it would be strange to show
|
||||
filteredRows = filteredRows.filter(row => {
|
||||
if (!row.subRows) {
|
||||
return true
|
||||
}
|
||||
return row.subRows.length > 0
|
||||
})
|
||||
|
||||
return filteredRows
|
||||
}
|
||||
|
||||
return filterRows(rows)
|
||||
}, [rows, filters, manualFilters])
|
||||
|
||||
return {
|
||||
...props,
|
||||
setFilter,
|
||||
setAllFilters,
|
||||
rows: filteredRows
|
||||
}
|
||||
}
|
||||
@@ -1,115 +1,135 @@
|
||||
import { useMemo, useState } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { getFirstDefined, sum } from './utils'
|
||||
|
||||
const propTypes = {
|
||||
defaultFlex: PropTypes.number,
|
||||
}
|
||||
import { getFirstDefined, sum } from '../utils'
|
||||
|
||||
export const actions = {}
|
||||
|
||||
export default function useFlexLayout (api, props = {}) {
|
||||
// Validate props
|
||||
const propTypes = {
|
||||
defaultFlex: PropTypes.number
|
||||
}
|
||||
|
||||
export const useFlexLayout = props => {
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useFlexLayout')
|
||||
|
||||
const {
|
||||
defaultFlex = 1,
|
||||
hooks: {
|
||||
getRowProps, getHeaderRowProps, getHeaderProps, getCellProps,
|
||||
},
|
||||
visibleColumns,
|
||||
} = api
|
||||
columns: columnsHooks,
|
||||
getRowProps,
|
||||
getHeaderRowProps,
|
||||
getHeaderProps,
|
||||
getCellProps
|
||||
}
|
||||
} = props
|
||||
|
||||
const { defaultFlex = 1 } = props
|
||||
columnsHooks.push((columns, api) => {
|
||||
const visibleColumns = columns.filter(column => {
|
||||
column.visible =
|
||||
typeof column.show === 'function' ? column.show(api) : !!column.show
|
||||
return column.visible
|
||||
})
|
||||
|
||||
const [columnMeasurements, setColumnMeasurements] = useState({})
|
||||
const columnMeasurements = {}
|
||||
|
||||
const rowStyles = useMemo(
|
||||
() => {
|
||||
let sumWidth = 0
|
||||
visibleColumns.forEach(column => {
|
||||
const { width, minWidth } = getSizesForColumn(
|
||||
column,
|
||||
defaultFlex,
|
||||
undefined,
|
||||
undefined,
|
||||
api
|
||||
)
|
||||
if (width) {
|
||||
sumWidth += width
|
||||
} else if (minWidth) {
|
||||
sumWidth += minWidth
|
||||
} else {
|
||||
sumWidth += defaultFlex
|
||||
}
|
||||
})
|
||||
let sumWidth = 0
|
||||
visibleColumns.forEach(column => {
|
||||
const { width, minWidth } = getSizesForColumn(
|
||||
column,
|
||||
defaultFlex,
|
||||
undefined,
|
||||
undefined,
|
||||
api
|
||||
)
|
||||
if (width) {
|
||||
sumWidth += width
|
||||
} else if (minWidth) {
|
||||
sumWidth += minWidth
|
||||
} else {
|
||||
sumWidth += defaultFlex
|
||||
}
|
||||
})
|
||||
|
||||
const rowStyles = {
|
||||
style: {
|
||||
display: 'flex',
|
||||
minWidth: `${sumWidth}px`
|
||||
}
|
||||
}
|
||||
|
||||
api.rowStyles = rowStyles
|
||||
|
||||
getRowProps.push(() => rowStyles)
|
||||
getHeaderRowProps.push(() => rowStyles)
|
||||
|
||||
getHeaderProps.push(column => ({
|
||||
style: {
|
||||
boxSizing: 'border-box',
|
||||
...getStylesForColumn(column, columnMeasurements, defaultFlex, api)
|
||||
}
|
||||
// [refKey]: el => {
|
||||
// renderedCellInfoRef.current[key] = {
|
||||
// column,
|
||||
// el
|
||||
// };
|
||||
// },
|
||||
}))
|
||||
|
||||
getCellProps.push(cell => {
|
||||
return {
|
||||
style: {
|
||||
display: 'flex',
|
||||
minWidth: `${sumWidth}px`,
|
||||
},
|
||||
display: 'block',
|
||||
boxSizing: 'border-box',
|
||||
...getStylesForColumn(
|
||||
cell.column,
|
||||
columnMeasurements,
|
||||
defaultFlex,
|
||||
undefined,
|
||||
api
|
||||
)
|
||||
}
|
||||
// [refKey]: el => {
|
||||
// renderedCellInfoRef.current[columnPathStr] = {
|
||||
// column,
|
||||
// el
|
||||
// };
|
||||
// }
|
||||
}
|
||||
},
|
||||
[visibleColumns]
|
||||
)
|
||||
})
|
||||
|
||||
getRowProps.push(row => rowStyles)
|
||||
getHeaderRowProps.push(row => rowStyles)
|
||||
return columns
|
||||
})
|
||||
|
||||
getHeaderProps.push(column => ({
|
||||
style: {
|
||||
boxSizing: 'border-box',
|
||||
...getStylesForColumn(column, columnMeasurements, defaultFlex, api),
|
||||
},
|
||||
// [refKey]: el => {
|
||||
// renderedCellInfoRef.current[key] = {
|
||||
// column,
|
||||
// el
|
||||
// };
|
||||
// },
|
||||
}))
|
||||
|
||||
getCellProps.push(cell => ({
|
||||
style: {
|
||||
display: 'block',
|
||||
boxSizing: 'border-box',
|
||||
...getStylesForColumn(cell.column, columnMeasurements, defaultFlex, undefined, api),
|
||||
},
|
||||
// [refKey]: el => {
|
||||
// renderedCellInfoRef.current[columnPathStr] = {
|
||||
// column,
|
||||
// el
|
||||
// };
|
||||
// }
|
||||
}))
|
||||
|
||||
return {
|
||||
rowStyles,
|
||||
}
|
||||
return props
|
||||
}
|
||||
|
||||
function getStylesForColumn (column, columnMeasurements, defaultFlex, api) {
|
||||
const { flex, width, maxWidth } = getSizesForColumn(column, columnMeasurements, defaultFlex, api)
|
||||
// Utils
|
||||
|
||||
function getStylesForColumn(column, columnMeasurements, defaultFlex, api) {
|
||||
const { flex, width, maxWidth } = getSizesForColumn(
|
||||
column,
|
||||
columnMeasurements,
|
||||
defaultFlex,
|
||||
api
|
||||
)
|
||||
|
||||
return {
|
||||
flex: `${flex} 0 auto`,
|
||||
width: `${width}px`,
|
||||
maxWidth: `${maxWidth}px`,
|
||||
maxWidth: `${maxWidth}px`
|
||||
}
|
||||
}
|
||||
|
||||
function getSizesForColumn (
|
||||
{
|
||||
columns, id, width, minWidth, maxWidth,
|
||||
},
|
||||
function getSizesForColumn(
|
||||
{ columns, id, width, minWidth, maxWidth },
|
||||
columnMeasurements,
|
||||
defaultFlex,
|
||||
api
|
||||
) {
|
||||
if (columns) {
|
||||
columns = columns
|
||||
.map(column => getSizesForColumn(column, columnMeasurements, defaultFlex, api))
|
||||
.map(column =>
|
||||
getSizesForColumn(column, columnMeasurements, defaultFlex, api)
|
||||
)
|
||||
.filter(Boolean)
|
||||
|
||||
if (!columns.length) {
|
||||
@@ -123,7 +143,7 @@ function getSizesForColumn (
|
||||
return {
|
||||
flex,
|
||||
width,
|
||||
maxWidth,
|
||||
maxWidth
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,7 +153,7 @@ function getSizesForColumn (
|
||||
width === 'auto'
|
||||
? columnMeasurements[id] || defaultFlex
|
||||
: getFirstDefined(width, minWidth, defaultFlex),
|
||||
maxWidth,
|
||||
maxWidth
|
||||
}
|
||||
}
|
||||
|
||||
189
src/hooks/useGroupBy.js
Executable file
189
src/hooks/useGroupBy.js
Executable file
@@ -0,0 +1,189 @@
|
||||
import { useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import * as aggregations from '../aggregations'
|
||||
import { addActions, actions } from '../actions'
|
||||
import { defaultState } from './useTableState'
|
||||
import {
|
||||
mergeProps,
|
||||
applyPropHooks,
|
||||
defaultGroupByFn,
|
||||
getFirstDefined
|
||||
} from '../utils'
|
||||
|
||||
defaultState.groupBy = []
|
||||
|
||||
addActions({
|
||||
toggleGroupBy: '__toggleGroupBy__'
|
||||
})
|
||||
|
||||
const propTypes = {
|
||||
// General
|
||||
columns: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
aggregate: PropTypes.func,
|
||||
canGroupBy: PropTypes.bool,
|
||||
Aggregated: PropTypes.any
|
||||
})
|
||||
),
|
||||
groupByFn: PropTypes.func,
|
||||
manualGrouping: PropTypes.bool,
|
||||
aggregations: PropTypes.object
|
||||
}
|
||||
|
||||
export const useGroupBy = props => {
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useGroupBy')
|
||||
|
||||
const {
|
||||
debug,
|
||||
rows,
|
||||
columns,
|
||||
groupByFn = defaultGroupByFn,
|
||||
manualGroupBy,
|
||||
disableGrouping,
|
||||
aggregations: userAggregations = {},
|
||||
hooks,
|
||||
state: [{ groupBy }, setState]
|
||||
} = props
|
||||
|
||||
columns.forEach(column => {
|
||||
const { id, accessor, canGroupBy } = column
|
||||
column.grouped = groupBy.includes(id)
|
||||
|
||||
column.canGroupBy = accessor
|
||||
? getFirstDefined(
|
||||
canGroupBy,
|
||||
disableGrouping === true ? false : undefined,
|
||||
true
|
||||
)
|
||||
: false
|
||||
|
||||
column.Aggregated = column.Aggregated || column.Cell
|
||||
})
|
||||
|
||||
const toggleGroupBy = (id, toggle) => {
|
||||
return setState(old => {
|
||||
const resolvedToggle =
|
||||
typeof set !== 'undefined' ? toggle : !groupBy.includes(id)
|
||||
if (resolvedToggle) {
|
||||
return {
|
||||
...old,
|
||||
groupBy: [...groupBy, id]
|
||||
}
|
||||
}
|
||||
return {
|
||||
...old,
|
||||
groupBy: groupBy.filter(d => d !== id)
|
||||
}
|
||||
}, actions.toggleGroupBy)
|
||||
}
|
||||
|
||||
hooks.columns.push(columns => {
|
||||
columns.forEach(column => {
|
||||
if (column.canGroupBy) {
|
||||
column.toggleGroupBy = () => toggleGroupBy(column.id)
|
||||
}
|
||||
})
|
||||
return columns
|
||||
})
|
||||
|
||||
hooks.getGroupByToggleProps = []
|
||||
|
||||
const addGroupByToggleProps = (columns, api) => {
|
||||
columns.forEach(column => {
|
||||
const { canGroupBy } = column
|
||||
column.getGroupByToggleProps = props => {
|
||||
return mergeProps(
|
||||
{
|
||||
onClick: canGroupBy
|
||||
? e => {
|
||||
e.persist()
|
||||
column.toggleGroupBy()
|
||||
}
|
||||
: undefined,
|
||||
style: {
|
||||
cursor: canGroupBy ? 'pointer' : undefined
|
||||
},
|
||||
title: 'Toggle GroupBy'
|
||||
},
|
||||
applyPropHooks(api.hooks.getGroupByToggleProps, column, api),
|
||||
props
|
||||
)
|
||||
}
|
||||
})
|
||||
return columns
|
||||
}
|
||||
|
||||
hooks.columns.push(addGroupByToggleProps)
|
||||
hooks.headers.push(addGroupByToggleProps)
|
||||
|
||||
const groupedRows = useMemo(() => {
|
||||
if (manualGroupBy || !groupBy.length) {
|
||||
return rows
|
||||
}
|
||||
if (debug) console.info('getGroupedRows')
|
||||
// Find the columns that can or are aggregating
|
||||
|
||||
// Uses each column to aggregate rows into a single value
|
||||
const aggregateRowsToValues = rows => {
|
||||
const values = {}
|
||||
columns.forEach(column => {
|
||||
const columnValues = rows.map(d => d.values[column.id])
|
||||
let aggregate =
|
||||
userAggregations[column.aggregate] ||
|
||||
aggregations[column.aggregate] ||
|
||||
column.aggregate
|
||||
if (typeof aggregate === 'function') {
|
||||
values[column.id] = aggregate(columnValues, rows)
|
||||
} else if (aggregate) {
|
||||
throw new Error(
|
||||
`Invalid aggregate "${aggregate}" passed to column with ID: "${
|
||||
column.id
|
||||
}"`
|
||||
)
|
||||
} else {
|
||||
values[column.id] = columnValues[0]
|
||||
}
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
// Recursively group the data
|
||||
const groupRecursively = (rows, groupBy, depth = 0) => {
|
||||
// This is the last level, just return the rows
|
||||
if (depth >= groupBy.length) {
|
||||
return rows
|
||||
}
|
||||
|
||||
// Group the rows together for this level
|
||||
let groupedRows = Object.entries(groupByFn(rows, groupBy[depth])).map(
|
||||
([groupByVal, subRows], index) => {
|
||||
// Recurse to sub rows before aggregation
|
||||
subRows = groupRecursively(subRows, groupBy, depth + 1)
|
||||
|
||||
const values = aggregateRowsToValues(subRows)
|
||||
|
||||
const row = {
|
||||
groupByID: groupBy[depth],
|
||||
groupByVal,
|
||||
values,
|
||||
subRows,
|
||||
depth,
|
||||
index
|
||||
}
|
||||
return row
|
||||
}
|
||||
)
|
||||
|
||||
return groupedRows
|
||||
}
|
||||
|
||||
// Assign the new data
|
||||
return groupRecursively(rows, groupBy)
|
||||
}, [rows, groupBy, columns, manualGroupBy])
|
||||
|
||||
return {
|
||||
...props,
|
||||
rows: groupedRows
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
import * as aggregations from '../aggregations'
|
||||
|
||||
export default function useGroupedRows ({
|
||||
debug,
|
||||
groupBy,
|
||||
rows,
|
||||
columns,
|
||||
groupByFn,
|
||||
manualGroupBy,
|
||||
userAggregations,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (manualGroupBy || !groupBy.length) {
|
||||
return rows
|
||||
}
|
||||
if (debug) console.info('getGroupedRows')
|
||||
// Find the columns that can or are aggregating
|
||||
|
||||
// Uses each column to aggregate rows into a single value
|
||||
const aggregateRowsToValues = rows => {
|
||||
const values = {}
|
||||
columns.forEach(column => {
|
||||
const columnValues = rows.map(d => d.values[column.id])
|
||||
const aggregate =
|
||||
userAggregations[column.aggregate] || aggregations[column.aggregate] || column.aggregate
|
||||
if (typeof aggregate === 'function') {
|
||||
values[column.id] = aggregate(columnValues, rows)
|
||||
} else if (aggregate) {
|
||||
throw new Error(
|
||||
`Invalid aggregate "${aggregate}" passed to column with ID: "${column.id}"`
|
||||
)
|
||||
} else {
|
||||
values[column.id] = columnValues[0]
|
||||
}
|
||||
})
|
||||
return values
|
||||
}
|
||||
|
||||
// Recursively group the data
|
||||
const groupRecursively = (rows, groupBy, depth = 0) => {
|
||||
// This is the last level, just return the rows
|
||||
if (depth >= groupBy.length) {
|
||||
return rows
|
||||
}
|
||||
|
||||
// Group the rows together for this level
|
||||
const groupedRows = Object.entries(groupByFn(rows, groupBy[depth])).map(
|
||||
([groupByVal, subRows], index) => {
|
||||
// Recurse to sub rows before aggregation
|
||||
subRows = groupRecursively(subRows, groupBy, depth + 1)
|
||||
|
||||
const values = aggregateRowsToValues(subRows)
|
||||
|
||||
const row = {
|
||||
groupByID: groupBy[depth],
|
||||
groupByVal,
|
||||
values,
|
||||
subRows,
|
||||
depth,
|
||||
index,
|
||||
}
|
||||
return row
|
||||
}
|
||||
)
|
||||
|
||||
return groupedRows
|
||||
}
|
||||
|
||||
// Assign the new data
|
||||
return groupRecursively(rows, groupBy)
|
||||
},
|
||||
[rows, groupBy, columns, manualGroupBy]
|
||||
)
|
||||
}
|
||||
129
src/hooks/usePagination.js
Executable file
129
src/hooks/usePagination.js
Executable file
@@ -0,0 +1,129 @@
|
||||
import { useMemo, useLayoutEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
//
|
||||
import { addActions, actions } from '../actions'
|
||||
import { defaultState } from './useTableState'
|
||||
|
||||
defaultState.pageSize = 10
|
||||
defaultState.pageIndex = 0
|
||||
|
||||
addActions({
|
||||
pageChange: '__pageChange__'
|
||||
})
|
||||
|
||||
const propTypes = {
|
||||
// General
|
||||
manualPagination: PropTypes.bool
|
||||
}
|
||||
|
||||
export const usePagination = props => {
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'usePagination')
|
||||
|
||||
const {
|
||||
rows,
|
||||
manualPagination,
|
||||
debug,
|
||||
state: [
|
||||
{
|
||||
pageSize,
|
||||
pageIndex,
|
||||
pageCount: userPageCount,
|
||||
filters,
|
||||
groupBy,
|
||||
sortBy
|
||||
},
|
||||
setState
|
||||
]
|
||||
} = props
|
||||
|
||||
useLayoutEffect(() => {
|
||||
setState(
|
||||
old => ({
|
||||
...old,
|
||||
pageIndex: 0
|
||||
}),
|
||||
actions.pageChange
|
||||
)
|
||||
}, [filters, groupBy, sortBy])
|
||||
|
||||
const { pages, pageCount } = useMemo(() => {
|
||||
if (manualPagination) {
|
||||
return {
|
||||
pages: [rows],
|
||||
pageCount: userPageCount
|
||||
}
|
||||
}
|
||||
if (debug) console.info('getPages')
|
||||
|
||||
// Create a new pages with the first page ready to go.
|
||||
const pages = rows.length ? [] : [[]]
|
||||
|
||||
// Start the pageIndex and currentPage cursors
|
||||
let cursor = 0
|
||||
while (cursor < rows.length) {
|
||||
const end = cursor + pageSize
|
||||
pages.push(rows.slice(cursor, end))
|
||||
cursor = end
|
||||
}
|
||||
|
||||
const pageCount = pages.length
|
||||
|
||||
return {
|
||||
pages,
|
||||
pageCount,
|
||||
pageOptions
|
||||
}
|
||||
}, [rows, pageSize, userPageCount])
|
||||
|
||||
const pageOptions = [...new Array(pageCount)].map((d, i) => i)
|
||||
const page = manualPagination ? rows : pages[pageIndex] || []
|
||||
const canPreviousPage = pageIndex > 0
|
||||
const canNextPage = pageIndex < pageCount - 1
|
||||
|
||||
const gotoPage = pageIndex => {
|
||||
if (debug) console.info('gotoPage')
|
||||
return setState(old => {
|
||||
if (pageIndex < 0 || pageIndex > pageCount - 1) {
|
||||
return old
|
||||
}
|
||||
return {
|
||||
...old,
|
||||
pageIndex
|
||||
}
|
||||
}, actions.pageChange)
|
||||
}
|
||||
|
||||
const previousPage = () => {
|
||||
return gotoPage(pageIndex - 1)
|
||||
}
|
||||
|
||||
const nextPage = () => {
|
||||
return gotoPage(pageIndex + 1)
|
||||
}
|
||||
|
||||
const setPageSize = pageSize => {
|
||||
setState(old => {
|
||||
const topRowIndex = old.pageSize * old.pageIndex
|
||||
const pageIndex = Math.floor(topRowIndex / pageSize)
|
||||
return {
|
||||
...old,
|
||||
pageIndex,
|
||||
pageSize
|
||||
}
|
||||
}, actions.setPageSize)
|
||||
}
|
||||
|
||||
return {
|
||||
...props,
|
||||
pages,
|
||||
pageOptions,
|
||||
page,
|
||||
canPreviousPage,
|
||||
canNextPage,
|
||||
gotoPage,
|
||||
previousPage,
|
||||
nextPage,
|
||||
setPageSize
|
||||
}
|
||||
}
|
||||
52
src/hooks/useRows.js
Executable file
52
src/hooks/useRows.js
Executable file
@@ -0,0 +1,52 @@
|
||||
import { useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
const propTypes = {
|
||||
subRowsKey: PropTypes.string
|
||||
}
|
||||
|
||||
export const useRows = props => {
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useRows')
|
||||
|
||||
const { debug, columns, subRowsKey = 'subRows', data } = props
|
||||
|
||||
const accessedRows = useMemo(() => {
|
||||
if (debug) console.info('getAccessedRows')
|
||||
|
||||
// Access the row's data
|
||||
const accessRow = (originalRow, i, depth = 0) => {
|
||||
// Keep the original reference around
|
||||
const original = originalRow
|
||||
|
||||
// Process any subRows
|
||||
const subRows = originalRow[subRowsKey]
|
||||
? originalRow[subRowsKey].map((d, i) => accessRow(d, i, depth + 1))
|
||||
: undefined
|
||||
|
||||
const row = {
|
||||
original,
|
||||
index: i,
|
||||
subRows,
|
||||
depth
|
||||
}
|
||||
|
||||
// Create the cells and values
|
||||
row.values = {}
|
||||
columns.forEach(column => {
|
||||
row.values[column.id] = column.accessor
|
||||
? column.accessor(originalRow, i, { subRows, depth, data })
|
||||
: undefined
|
||||
})
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
// Use the resolved data
|
||||
return data.map((d, i) => accessRow(d, i))
|
||||
}, [data, columns])
|
||||
|
||||
return {
|
||||
...props,
|
||||
rows: accessedRows
|
||||
}
|
||||
}
|
||||
256
src/hooks/useSortBy.js
Executable file
256
src/hooks/useSortBy.js
Executable file
@@ -0,0 +1,256 @@
|
||||
import { useMemo } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { addActions, actions } from '../actions'
|
||||
import { defaultState } from './useTableState'
|
||||
import {
|
||||
mergeProps,
|
||||
applyPropHooks,
|
||||
getFirstDefined,
|
||||
defaultOrderByFn,
|
||||
defaultSortByFn
|
||||
} from '../utils'
|
||||
|
||||
defaultState.sortBy = []
|
||||
|
||||
addActions({
|
||||
sortByChange: '__sortByChange__'
|
||||
})
|
||||
|
||||
const propTypes = {
|
||||
// General
|
||||
columns: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
sortByFn: PropTypes.func,
|
||||
efaultSortDesc: PropTypes.bool
|
||||
})
|
||||
),
|
||||
sortByFn: PropTypes.func,
|
||||
manualSorting: PropTypes.bool,
|
||||
disableSorting: PropTypes.bool,
|
||||
defaultSortDesc: PropTypes.bool,
|
||||
disableMultiSort: PropTypes.bool
|
||||
}
|
||||
|
||||
export const useSortBy = props => {
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useSortBy')
|
||||
|
||||
const {
|
||||
debug,
|
||||
rows,
|
||||
columns,
|
||||
orderByFn = defaultOrderByFn,
|
||||
sortByFn = defaultSortByFn,
|
||||
manualSorting,
|
||||
disableSorting,
|
||||
defaultSortDesc,
|
||||
hooks,
|
||||
state: [{ sortBy }, setState]
|
||||
} = props
|
||||
|
||||
columns.forEach(column => {
|
||||
const { accessor, canSortBy } = column
|
||||
column.canSortBy = accessor
|
||||
? getFirstDefined(
|
||||
canSortBy,
|
||||
disableSorting === true ? false : undefined,
|
||||
true
|
||||
)
|
||||
: false
|
||||
})
|
||||
|
||||
// Updates sorting based on a columnID, desc flag and multi flag
|
||||
const toggleSortByID = (columnID, desc, multi) => {
|
||||
return setState(old => {
|
||||
const { sortBy } = old
|
||||
|
||||
// Find the column for this columnID
|
||||
const column = columns.find(d => d.id === columnID)
|
||||
const resolvedDefaultSortDesc = getFirstDefined(
|
||||
column.defaultSortDesc,
|
||||
defaultSortDesc
|
||||
)
|
||||
|
||||
// Find any existing sortBy for this column
|
||||
const existingSortBy = sortBy.find(d => d.id === columnID)
|
||||
const hasDescDefined = typeof desc !== 'undefined' && desc !== null
|
||||
|
||||
let newSortBy = []
|
||||
|
||||
// What should we do with this filter?
|
||||
let action
|
||||
|
||||
if (!multi) {
|
||||
if (sortBy.length <= 1 && existingSortBy) {
|
||||
if (existingSortBy.desc) {
|
||||
action = 'remove'
|
||||
} else {
|
||||
action = 'toggle'
|
||||
}
|
||||
} else {
|
||||
action = 'replace'
|
||||
}
|
||||
} else {
|
||||
if (!existingSortBy) {
|
||||
action = 'add'
|
||||
} else {
|
||||
if (hasDescDefined) {
|
||||
action = 'set'
|
||||
} else {
|
||||
action = 'toggle'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (action === 'replace') {
|
||||
newSortBy = [
|
||||
{
|
||||
id: columnID,
|
||||
desc: hasDescDefined ? desc : resolvedDefaultSortDesc
|
||||
}
|
||||
]
|
||||
} else if (action === 'add') {
|
||||
newSortBy = [
|
||||
...sortBy,
|
||||
{
|
||||
id: columnID,
|
||||
desc: hasDescDefined ? desc : resolvedDefaultSortDesc
|
||||
}
|
||||
]
|
||||
} else if (action === 'set') {
|
||||
newSortBy = sortBy.map(d => {
|
||||
if (d.id === columnID) {
|
||||
return {
|
||||
...d,
|
||||
desc
|
||||
}
|
||||
}
|
||||
return d
|
||||
})
|
||||
} else if (action === 'toggle') {
|
||||
newSortBy = sortBy.map(d => {
|
||||
if (d.id === columnID) {
|
||||
return {
|
||||
...d,
|
||||
desc: !existingSortBy.desc
|
||||
}
|
||||
}
|
||||
return d
|
||||
})
|
||||
} else if (action === 'remove') {
|
||||
newSortBy = []
|
||||
}
|
||||
|
||||
return {
|
||||
...old,
|
||||
sortBy: newSortBy
|
||||
}
|
||||
}, actions.sortByChange)
|
||||
}
|
||||
|
||||
hooks.columns.push(columns => {
|
||||
columns.forEach(column => {
|
||||
if (column.canSortBy) {
|
||||
column.toggleSortBy = (desc, multi) =>
|
||||
toggleSortByID(column.id, desc, multi)
|
||||
}
|
||||
})
|
||||
return columns
|
||||
})
|
||||
|
||||
hooks.getSortByToggleProps = []
|
||||
|
||||
const addSortByToggleProps = (columns, api) => {
|
||||
columns.forEach(column => {
|
||||
const { canSortBy } = column
|
||||
column.getSortByToggleProps = props => {
|
||||
return mergeProps(
|
||||
{
|
||||
onClick: canSortBy
|
||||
? e => {
|
||||
e.persist()
|
||||
column.toggleSortBy(
|
||||
undefined,
|
||||
!api.disableMultiSort && e.shiftKey
|
||||
)
|
||||
}
|
||||
: undefined,
|
||||
style: {
|
||||
cursor: canSortBy ? 'pointer' : undefined
|
||||
},
|
||||
title: 'Toggle SortBy'
|
||||
},
|
||||
applyPropHooks(api.hooks.getSortByToggleProps, column, api),
|
||||
props
|
||||
)
|
||||
}
|
||||
})
|
||||
return columns
|
||||
}
|
||||
|
||||
hooks.columns.push(addSortByToggleProps)
|
||||
hooks.headers.push(addSortByToggleProps)
|
||||
|
||||
// Mutate columns to reflect sorting state
|
||||
columns.forEach(column => {
|
||||
const { id } = column
|
||||
column.sorted = sortBy.find(d => d.id === id)
|
||||
column.sortedIndex = sortBy.findIndex(d => d.id === id)
|
||||
column.sortedDesc = column.sorted ? column.sorted.desc : undefined
|
||||
})
|
||||
|
||||
const sortedRows = useMemo(() => {
|
||||
if (manualSorting || !sortBy.length) {
|
||||
return rows
|
||||
}
|
||||
if (debug) console.info('getSortedRows')
|
||||
|
||||
const sortMethodsByColumnID = {}
|
||||
|
||||
columns
|
||||
.filter(col => col.sortMethod)
|
||||
.forEach(col => {
|
||||
sortMethodsByColumnID[col.id] = col.sortMethod
|
||||
})
|
||||
|
||||
const sortData = rows => {
|
||||
// Use the orderByFn to compose multiple sortBy's together.
|
||||
// This will also perform a stable sorting using the row index
|
||||
// if needed.
|
||||
const sortedData = orderByFn(
|
||||
rows,
|
||||
sortBy.map(sort => {
|
||||
// Support custom sorting methods for each column
|
||||
const columnSortBy = sortMethodsByColumnID[sort.id]
|
||||
|
||||
// Return the correct sortFn
|
||||
return (a, b) =>
|
||||
(columnSortBy || sortByFn)(
|
||||
a.values[sort.id],
|
||||
b.values[sort.id],
|
||||
sort.desc
|
||||
)
|
||||
}),
|
||||
// Map the directions
|
||||
sortBy.map(d => !d.desc)
|
||||
)
|
||||
|
||||
// TODO: this should be optimized. Not good to loop again
|
||||
sortedData.forEach(row => {
|
||||
if (!row.subRows) {
|
||||
return
|
||||
}
|
||||
row.subRows = sortData(row.subRows)
|
||||
})
|
||||
|
||||
return sortedData
|
||||
}
|
||||
|
||||
return sortData(rows)
|
||||
}, [rows, columns, sortBy, manualSorting])
|
||||
|
||||
return {
|
||||
...props,
|
||||
rows: sortedRows
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
import { useMemo } from 'react'
|
||||
|
||||
export default function useSortedRows ({
|
||||
debug,
|
||||
sortBy,
|
||||
rows,
|
||||
columns,
|
||||
orderByFn,
|
||||
sortByFn,
|
||||
manualSorting,
|
||||
}) {
|
||||
return useMemo(
|
||||
() => {
|
||||
if (manualSorting || !sortBy.length) {
|
||||
return rows
|
||||
}
|
||||
if (debug) console.info('getSortedRows')
|
||||
|
||||
const sortMethodsByColumnID = {}
|
||||
|
||||
columns
|
||||
.filter(col => col.sortMethod)
|
||||
.forEach(col => {
|
||||
sortMethodsByColumnID[col.id] = col.sortMethod
|
||||
})
|
||||
|
||||
const sortData = rows => {
|
||||
// Use the orderByFn to compose multiple sortBy's together.
|
||||
// This will also perform a stable sorting using the row index
|
||||
// if needed.
|
||||
const sortedData = orderByFn(
|
||||
rows,
|
||||
sortBy.map(sort => {
|
||||
// Support custom sorting methods for each column
|
||||
const columnSortBy = sortMethodsByColumnID[sort.id]
|
||||
|
||||
// Return the correct sortFn
|
||||
return (a, b) =>
|
||||
(columnSortBy || sortByFn)(a.values[sort.id], b.values[sort.id], sort.desc)
|
||||
}),
|
||||
// Map the directions
|
||||
sortBy.map(d => !d.desc)
|
||||
)
|
||||
|
||||
// TODO: this should be optimized. Not good to loop again
|
||||
sortedData.forEach(row => {
|
||||
if (!row.subRows) {
|
||||
return
|
||||
}
|
||||
row.subRows = sortData(row.subRows)
|
||||
})
|
||||
|
||||
return sortedData
|
||||
}
|
||||
|
||||
return sortData(rows)
|
||||
},
|
||||
[rows, columns, sortBy, manualSorting]
|
||||
)
|
||||
}
|
||||
192
src/hooks/useTable.js
Executable file
192
src/hooks/useTable.js
Executable file
@@ -0,0 +1,192 @@
|
||||
import PropTypes from 'prop-types'
|
||||
//
|
||||
import { flexRender, applyHooks, applyPropHooks, mergeProps } from '../utils'
|
||||
|
||||
import { useTableState } from './useTableState'
|
||||
|
||||
const renderErr =
|
||||
'You must specify a render "type". This could be "Header", "Filter", or any other custom renderers you have set on your column.'
|
||||
|
||||
const propTypes = {
|
||||
// General
|
||||
data: PropTypes.array.isRequired,
|
||||
debug: PropTypes.bool
|
||||
}
|
||||
|
||||
export const useTable = (props, ...plugins) => {
|
||||
// Validate props
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useTable')
|
||||
|
||||
// Destructure props
|
||||
let { data = [], state: userState, debug } = props
|
||||
|
||||
debug = process.env.NODE_ENV === 'production' ? false : debug
|
||||
|
||||
// Always provide a default state
|
||||
const defaultState = useTableState()
|
||||
|
||||
// But use the users state if provided
|
||||
const state = userState || defaultState
|
||||
|
||||
// These are hooks that plugins can use right before render
|
||||
const hooks = {
|
||||
beforeRender: [],
|
||||
columns: [],
|
||||
headers: [],
|
||||
headerGroups: [],
|
||||
rows: [],
|
||||
row: [],
|
||||
renderableRows: [],
|
||||
getTableProps: [],
|
||||
getRowProps: [],
|
||||
getHeaderRowProps: [],
|
||||
getHeaderProps: [],
|
||||
getCellProps: []
|
||||
}
|
||||
|
||||
// The initial api
|
||||
let api = {
|
||||
...props,
|
||||
data,
|
||||
state,
|
||||
hooks
|
||||
}
|
||||
|
||||
if (debug) console.time('hooks')
|
||||
// Loop through plugins to build the api out
|
||||
api = plugins.filter(Boolean).reduce((prev, next) => next(prev), api)
|
||||
if (debug) console.timeEnd('hooks')
|
||||
|
||||
// Run the beforeRender hook
|
||||
if (debug) console.time('hooks.beforeRender')
|
||||
applyHooks(api.hooks.beforeRender, undefined, api)
|
||||
if (debug) console.timeEnd('hooks.beforeRender')
|
||||
|
||||
if (debug) console.time('hooks.columns')
|
||||
api.columns = applyHooks(api.hooks.columns, api.columns, api)
|
||||
if (debug) console.timeEnd('hooks.columns')
|
||||
|
||||
if (debug) console.time('hooks.headers')
|
||||
api.headers = applyHooks(api.hooks.headers, api.headers, api)
|
||||
if (debug) console.timeEnd('hooks.headers')
|
||||
;[...api.columns, ...api.headers].forEach(column => {
|
||||
// Give columns/headers rendering power
|
||||
column.render = (type, userProps = {}) => {
|
||||
if (!type) {
|
||||
throw new Error(renderErr)
|
||||
}
|
||||
return flexRender(column[type], {
|
||||
...api,
|
||||
...column,
|
||||
...userProps
|
||||
})
|
||||
}
|
||||
|
||||
// Give columns/headers getHeaderProps
|
||||
column.getHeaderProps = props =>
|
||||
mergeProps(
|
||||
{
|
||||
key: ['header', column.id].join('_')
|
||||
},
|
||||
applyPropHooks(api.hooks.getHeaderProps, column, api),
|
||||
props
|
||||
)
|
||||
})
|
||||
|
||||
if (debug) console.time('hooks.headerGroups')
|
||||
api.headerGroups = applyHooks(
|
||||
api.hooks.headerGroups,
|
||||
api.headerGroups,
|
||||
api
|
||||
).filter((headerGroup, i) => {
|
||||
// Filter out any headers and headerGroups that don't have visible columns
|
||||
headerGroup.headers = headerGroup.headers.filter(header => {
|
||||
const recurse = columns =>
|
||||
columns.filter(column => {
|
||||
if (column.columns) {
|
||||
return recurse(column.columns)
|
||||
}
|
||||
return column.visible
|
||||
}).length
|
||||
if (header.columns) {
|
||||
return recurse(header.columns)
|
||||
}
|
||||
return header.visible
|
||||
})
|
||||
|
||||
// Give headerGroups getRowProps
|
||||
if (headerGroup.headers.length) {
|
||||
headerGroup.getRowProps = (props = {}) =>
|
||||
mergeProps(
|
||||
{
|
||||
key: [`header${i}`].join('_')
|
||||
},
|
||||
applyPropHooks(api.hooks.getHeaderRowProps, headerGroup, api),
|
||||
props
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
if (debug) console.timeEnd('hooks.headerGroups')
|
||||
|
||||
// Run the rows (this could be a dangerous hook with a ton of data)
|
||||
if (debug) console.time('hooks.rows')
|
||||
api.rows = applyHooks(api.hooks.rows, api.rows, api)
|
||||
if (debug) console.timeEnd('hooks.rows')
|
||||
|
||||
// This function is absolutely necessary and MUST be called on
|
||||
// any rows the user wishes to be displayed.
|
||||
api.prepareRow = row => {
|
||||
const { index } = row
|
||||
row.getRowProps = props =>
|
||||
mergeProps(
|
||||
{ key: ['row', index].join('_') },
|
||||
applyHooks(api.hooks.getRowProps, row, api),
|
||||
props
|
||||
)
|
||||
|
||||
row.cells = row.cells.filter(cell => cell.column.visible)
|
||||
|
||||
row.cells.forEach(cell => {
|
||||
if (!cell) {
|
||||
return
|
||||
}
|
||||
|
||||
const { column } = cell
|
||||
|
||||
cell.getCellProps = props => {
|
||||
const columnPathStr = [index, column.id].join('_')
|
||||
return mergeProps(
|
||||
{
|
||||
key: ['cell', columnPathStr].join('_')
|
||||
},
|
||||
applyPropHooks(api.hooks.getCellProps, cell, api),
|
||||
props
|
||||
)
|
||||
}
|
||||
|
||||
cell.render = (type, userProps = {}) => {
|
||||
if (!type) {
|
||||
throw new Error(
|
||||
'You must specify a render "type". This could be "Cell", "Header", "Filter", "Aggregated" or any other custom renderers you have set on your column.'
|
||||
)
|
||||
}
|
||||
return flexRender(column[type], {
|
||||
...api,
|
||||
...cell,
|
||||
...userProps
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
api.getTableProps = userProps =>
|
||||
mergeProps(applyPropHooks(api.hooks.getTableProps, api), userProps)
|
||||
|
||||
api.getRowProps = userProps =>
|
||||
mergeProps(applyPropHooks(api.hooks.getRowProps, api), userProps)
|
||||
|
||||
return api
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
import { getBy, setBy, getFirstDefined } from '../utils'
|
||||
|
||||
export default function useTableMethods ({
|
||||
debug,
|
||||
setState,
|
||||
actions,
|
||||
groupBy,
|
||||
columns,
|
||||
defaultSortDesc,
|
||||
filters,
|
||||
}) {
|
||||
const toggleExpandedByPath = (path, set) =>
|
||||
setState(old => {
|
||||
const { expanded } = old
|
||||
const existing = getBy(expanded, path)
|
||||
set = getFirstDefined(set, !existing)
|
||||
return {
|
||||
...old,
|
||||
expanded: setBy(expanded, path, set),
|
||||
}
|
||||
}, actions.toggleExpanded)
|
||||
|
||||
// Updates sorting based on a columnID, desc flag and multi flag
|
||||
const toggleSortByID = (columnID, desc, multi) =>
|
||||
setState(old => {
|
||||
const { sortBy } = old
|
||||
|
||||
// Find the column for this columnID
|
||||
const column = columns.find(d => d.id === columnID)
|
||||
const resolvedDefaultSortDesc = getFirstDefined(column.defaultSortDesc, defaultSortDesc)
|
||||
|
||||
// Find any existing sortBy for this column
|
||||
const existingSortBy = sortBy.find(d => d.id === columnID)
|
||||
const hasDescDefined = typeof desc !== 'undefined' && desc !== null
|
||||
|
||||
let newSortBy = []
|
||||
|
||||
// What should we do with this filter?
|
||||
let action
|
||||
|
||||
if (!multi) {
|
||||
if (sortBy.length <= 1 && existingSortBy) {
|
||||
if (existingSortBy.desc) {
|
||||
action = 'remove'
|
||||
} else {
|
||||
action = 'toggle'
|
||||
}
|
||||
} else {
|
||||
action = 'replace'
|
||||
}
|
||||
} else if (!existingSortBy) {
|
||||
action = 'add'
|
||||
} else if (hasDescDefined) {
|
||||
action = 'set'
|
||||
} else {
|
||||
action = 'toggle'
|
||||
}
|
||||
|
||||
if (action === 'replace') {
|
||||
newSortBy = [
|
||||
{
|
||||
id: columnID,
|
||||
desc: hasDescDefined ? desc : resolvedDefaultSortDesc,
|
||||
},
|
||||
]
|
||||
} else if (action === 'add') {
|
||||
newSortBy = [
|
||||
...sortBy,
|
||||
{
|
||||
id: columnID,
|
||||
desc: hasDescDefined ? desc : resolvedDefaultSortDesc,
|
||||
},
|
||||
]
|
||||
} else if (action === 'set') {
|
||||
newSortBy = sortBy.map(d => {
|
||||
if (d.id === columnID) {
|
||||
return {
|
||||
...d,
|
||||
desc,
|
||||
}
|
||||
}
|
||||
return d
|
||||
})
|
||||
} else if (action === 'toggle') {
|
||||
newSortBy = sortBy.map(d => {
|
||||
if (d.id === columnID) {
|
||||
return {
|
||||
...d,
|
||||
desc: !existingSortBy.desc,
|
||||
}
|
||||
}
|
||||
return d
|
||||
})
|
||||
} else if (action === 'remove') {
|
||||
newSortBy = []
|
||||
}
|
||||
|
||||
return {
|
||||
...old,
|
||||
sortBy: newSortBy,
|
||||
}
|
||||
}, actions.sortByChange)
|
||||
|
||||
const toggleGroupBy = (id, toggle) =>
|
||||
setState(old => {
|
||||
const resolvedToggle = typeof set !== 'undefined' ? toggle : !groupBy.includes(id)
|
||||
if (resolvedToggle) {
|
||||
return {
|
||||
...old,
|
||||
groupBy: [...groupBy, id],
|
||||
}
|
||||
}
|
||||
return {
|
||||
...old,
|
||||
groupBy: groupBy.filter(d => d !== id),
|
||||
}
|
||||
}, actions.toggleGroupBy)
|
||||
|
||||
const setFilter = (id, val) =>
|
||||
setState(old => {
|
||||
if (typeof val === 'undefined') {
|
||||
const { [id]: prev, ...rest } = filters
|
||||
return {
|
||||
...old,
|
||||
filters: {
|
||||
...rest,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...old,
|
||||
filters: {
|
||||
...filters,
|
||||
[id]: val,
|
||||
},
|
||||
}
|
||||
}, actions.setFilter)
|
||||
|
||||
const setAllFilters = filters =>
|
||||
setState(
|
||||
old => ({
|
||||
...old,
|
||||
filters,
|
||||
}),
|
||||
actions.setAllFilters
|
||||
)
|
||||
|
||||
return {
|
||||
toggleExpandedByPath,
|
||||
toggleSortByID,
|
||||
toggleGroupBy,
|
||||
setFilter,
|
||||
setAllFilters,
|
||||
}
|
||||
}
|
||||
34
src/hooks/useTableState.js
Executable file
34
src/hooks/useTableState.js
Executable file
@@ -0,0 +1,34 @@
|
||||
import { useState, useMemo } from 'react'
|
||||
|
||||
export const defaultState = {}
|
||||
|
||||
const defaultReducer = (old, newState) => newState
|
||||
|
||||
export const useTableState = (
|
||||
initialState = {},
|
||||
overrides = {},
|
||||
{ reducer = defaultReducer, useState: userUseState = useState } = {}
|
||||
) => {
|
||||
let [state, setState] = userUseState({
|
||||
...defaultState,
|
||||
...initialState
|
||||
})
|
||||
|
||||
const overriddenState = useMemo(() => {
|
||||
const newState = {
|
||||
...state
|
||||
}
|
||||
Object.keys(overrides).forEach(key => {
|
||||
newState[key] = overrides[key]
|
||||
})
|
||||
return newState
|
||||
}, [state, ...Object.values(overrides)])
|
||||
|
||||
const reducedSetState = (updater, type) =>
|
||||
setState(old => {
|
||||
const newState = updater(old)
|
||||
return reducer(old, newState, type)
|
||||
})
|
||||
|
||||
return [overriddenState, reducedSetState]
|
||||
}
|
||||
51
src/hooks/useTokenPagination.js
Normal file
51
src/hooks/useTokenPagination.js
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
// Token pagination behaves a bit differently from
|
||||
// index based pagination. This hook aids in that process.
|
||||
|
||||
export const useTokenPagination = () => {
|
||||
const [pageToken, setPageToken] = useState()
|
||||
const [nextPageToken, setNextPageToken] = useState()
|
||||
const [previousPageTokens, setPreviousPageTokens] = useState([])
|
||||
const [pageIndex, setPageIndex] = useState(0)
|
||||
|
||||
// Since we're using pagination tokens intead of index, we need
|
||||
// to be a bit clever with page-like navigation here.
|
||||
const nextPage = () => {
|
||||
setPageIndex(old => old + 1)
|
||||
setPreviousPageTokens(old => [...old, pageToken])
|
||||
setPageToken(nextPageToken)
|
||||
}
|
||||
|
||||
const previousPage = () => {
|
||||
setPageIndex(old => old - 1)
|
||||
setPreviousPageTokens(old =>
|
||||
[...old]
|
||||
.reverse()
|
||||
.slice(1)
|
||||
.reverse()
|
||||
)
|
||||
setPageToken(previousPageTokens[previousPageTokens.length - 1])
|
||||
}
|
||||
|
||||
const resetPagination = () => {
|
||||
setPageToken(undefined)
|
||||
setPageIndex(0)
|
||||
setNextPageToken(undefined)
|
||||
setPreviousPageTokens([])
|
||||
}
|
||||
|
||||
const canPreviousPage = previousPageTokens.length
|
||||
const canNextPage = nextPageToken
|
||||
|
||||
return {
|
||||
setNextPageToken,
|
||||
pageToken,
|
||||
pageIndex,
|
||||
previousPage,
|
||||
nextPage,
|
||||
canPreviousPage,
|
||||
canNextPage,
|
||||
resetPagination
|
||||
}
|
||||
}
|
||||
23
src/index.js
23
src/index.js
@@ -1,11 +1,12 @@
|
||||
import useReactTable, { actions as mainActions } from './useReactTable'
|
||||
import usePagination, { actions as paginationActions } from './usePagination'
|
||||
import useFlexLayout, { actions as flexLayoutActions } from './useFlexLayout'
|
||||
|
||||
const actions = {
|
||||
...mainActions,
|
||||
...paginationActions,
|
||||
...flexLayoutActions,
|
||||
}
|
||||
|
||||
export { useReactTable, usePagination, useFlexLayout, actions }
|
||||
export { useTable } from './hooks/useTable'
|
||||
export { useColumns } from './hooks/useColumns'
|
||||
export { useRows } from './hooks/useRows'
|
||||
export { useExpanded } from './hooks/useExpanded'
|
||||
export { useFilters } from './hooks/useFilters'
|
||||
export { useGroupBy } from './hooks/useGroupBy'
|
||||
export { useSortBy } from './hooks/useSortBy'
|
||||
export { usePagination } from './hooks/usePagination'
|
||||
export { useTableState } from './hooks/useTableState'
|
||||
export { useFlexLayout } from './hooks/useFlexLayout'
|
||||
export { useTokenPagination } from './hooks/useTokenPagination'
|
||||
export { actions } from './actions'
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
import { useMemo, useState, useLayoutEffect } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
|
||||
import { warnUnknownProps } from './utils'
|
||||
import useControlledState from './hooks/useControlledState'
|
||||
|
||||
const propTypes = {
|
||||
defaultPageSize: PropTypes.number,
|
||||
defaultPageIndex: PropTypes.number,
|
||||
pageSize: PropTypes.number,
|
||||
pages: PropTypes.number,
|
||||
pageIndex: PropTypes.number,
|
||||
onStateChange: PropTypes.func,
|
||||
stateReducer: PropTypes.func,
|
||||
subRowsKey: PropTypes.string,
|
||||
debug: PropTypes.bool,
|
||||
}
|
||||
|
||||
export const actions = {
|
||||
pageChange: '__pageChange__',
|
||||
}
|
||||
|
||||
export default function usePagination (
|
||||
{
|
||||
debug: parentDebug, rows, prepareRows, groupedRows, sortedRows, filteredRows,
|
||||
},
|
||||
props = {}
|
||||
) {
|
||||
// Validate props
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'usePagination')
|
||||
|
||||
// Destructure props
|
||||
const {
|
||||
defaultPageSize = 10,
|
||||
defaultPageIndex = 0,
|
||||
pageSize: userPageSize,
|
||||
pageIndex: userPageIndex,
|
||||
state: userState,
|
||||
manualPagination,
|
||||
pageCount: userPageCount,
|
||||
pageOptions: userPageOptions,
|
||||
debug = parentDebug,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
warnUnknownProps(rest)
|
||||
|
||||
const defaultState = useState({})
|
||||
const localState = userState || defaultState
|
||||
|
||||
// Build the controllable state
|
||||
const [{ pageSize, pageIndex }, setState] = useControlledState(
|
||||
localState,
|
||||
{
|
||||
pageSize: defaultPageSize,
|
||||
pageIndex: defaultPageIndex,
|
||||
},
|
||||
{
|
||||
pageSize: userPageSize,
|
||||
pageIndex: userPageIndex,
|
||||
}
|
||||
)
|
||||
|
||||
useLayoutEffect(
|
||||
() => {
|
||||
if (manualPagination) {
|
||||
return
|
||||
}
|
||||
setState(
|
||||
old => ({
|
||||
...old,
|
||||
pageIndex: 0,
|
||||
}),
|
||||
actions.pageChange
|
||||
)
|
||||
},
|
||||
[groupedRows, sortedRows, filteredRows]
|
||||
)
|
||||
|
||||
const { pages, pageCount } = useMemo(
|
||||
() => {
|
||||
if (manualPagination) {
|
||||
return {
|
||||
pages: [rows],
|
||||
pageCount: userPageCount,
|
||||
}
|
||||
}
|
||||
if (debug) console.info('getPages')
|
||||
|
||||
// Create a new pages with the first page ready to go.
|
||||
const pages = rows.length ? [] : [[]]
|
||||
|
||||
// Start the pageIndex and currentPage cursors
|
||||
let cursor = 0
|
||||
while (cursor < rows.length) {
|
||||
const end = cursor + pageSize
|
||||
pages.push(rows.slice(cursor, end))
|
||||
cursor = end
|
||||
}
|
||||
|
||||
const pageCount = pages.length
|
||||
|
||||
return {
|
||||
pages,
|
||||
pageCount,
|
||||
pageOptions,
|
||||
}
|
||||
},
|
||||
[rows, pageSize, userPageCount]
|
||||
)
|
||||
|
||||
const pageOptions = [...new Array(pageCount)].map((d, i) => i)
|
||||
const page = manualPagination ? rows : pages[pageIndex] || []
|
||||
const canPreviousPage = pageIndex > 0
|
||||
const canNextPage = pageIndex < pageCount - 1
|
||||
|
||||
const gotoPage = pageIndex => {
|
||||
if (debug) console.info('gotoPage')
|
||||
return setState(old => {
|
||||
if (pageIndex < 0 || pageIndex > pageCount - 1) {
|
||||
return old
|
||||
}
|
||||
return {
|
||||
...old,
|
||||
pageIndex,
|
||||
}
|
||||
}, actions.pageChange)
|
||||
}
|
||||
|
||||
const previousPage = () => gotoPage(pageIndex - 1)
|
||||
|
||||
const nextPage = () => gotoPage(pageIndex + 1)
|
||||
|
||||
const setPageSize = pageSize => {
|
||||
setState(old => ({
|
||||
...old,
|
||||
pageSize,
|
||||
}))
|
||||
}
|
||||
|
||||
prepareRows(page)
|
||||
|
||||
return {
|
||||
pages,
|
||||
pageIndex,
|
||||
pageOptions,
|
||||
page,
|
||||
canPreviousPage,
|
||||
canNextPage,
|
||||
gotoPage,
|
||||
previousPage,
|
||||
nextPage,
|
||||
setPageSize,
|
||||
}
|
||||
}
|
||||
@@ -1,470 +0,0 @@
|
||||
import { useState, useMemo, useRef } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
//
|
||||
import {
|
||||
defaultOrderByFn,
|
||||
defaultSortByFn,
|
||||
defaultGroupByFn,
|
||||
defaultFilterFn,
|
||||
flexRender,
|
||||
findExpandedDepth,
|
||||
applyHooks,
|
||||
mergeProps,
|
||||
warnUnknownProps,
|
||||
} from './utils'
|
||||
|
||||
// Internal Hooks
|
||||
import useControlledState from './hooks/useControlledState'
|
||||
import useColumns from './hooks/useColumns'
|
||||
import useTableMethods from './hooks/useTableMethods'
|
||||
import useAccessedRows from './hooks/useAccessedRows'
|
||||
import useGroupedRows from './hooks/useGroupedRows'
|
||||
import useFilteredRows from './hooks/useFilteredRows'
|
||||
import useSortedRows from './hooks/useSortedRows'
|
||||
import useExpandedRows from './hooks/useExpandedRows'
|
||||
|
||||
const defaultFlex = 1
|
||||
const renderErr =
|
||||
'You must specify a render "type". This could be "Header", "Filter", or any other custom renderers you have set on your column.'
|
||||
|
||||
export const actions = {
|
||||
sortByChange: '__sortByChange',
|
||||
updateAutoWidth: '__updateAutoWidth__',
|
||||
toggleGroupBy: '__toggleGroupBy__',
|
||||
toggleExpanded: '__toggleExpanded__',
|
||||
setExpanded: '__setExpanded__',
|
||||
setFilter: '__setFilter__',
|
||||
setAllFilters: '__setAllFilters__',
|
||||
}
|
||||
|
||||
const propTypes = {
|
||||
// General
|
||||
data: PropTypes.any,
|
||||
columns: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
aggregate: PropTypes.func,
|
||||
filterFn: PropTypes.func,
|
||||
filterAll: PropTypes.bool,
|
||||
sortByFn: PropTypes.func,
|
||||
resolvedDefaultSortDesc: PropTypes.bool,
|
||||
canSortBy: PropTypes.bool,
|
||||
canGroupBy: PropTypes.bool,
|
||||
Cell: PropTypes.any,
|
||||
Header: PropTypes.any,
|
||||
Filter: PropTypes.any,
|
||||
})
|
||||
),
|
||||
defaultFilters: PropTypes.array,
|
||||
defaultSortBy: PropTypes.array,
|
||||
defaultGroupBy: PropTypes.array,
|
||||
|
||||
groupBy: PropTypes.array,
|
||||
filters: PropTypes.array,
|
||||
sortBy: PropTypes.array,
|
||||
|
||||
filterFn: PropTypes.func,
|
||||
sortByFn: PropTypes.func,
|
||||
orderByFn: PropTypes.func,
|
||||
groupByFn: PropTypes.func,
|
||||
|
||||
manualGrouping: PropTypes.bool,
|
||||
manualFilters: PropTypes.bool,
|
||||
manualSorting: PropTypes.bool,
|
||||
|
||||
disableGrouping: PropTypes.bool,
|
||||
disableFilters: PropTypes.bool,
|
||||
disableSorting: PropTypes.bool,
|
||||
|
||||
defaultSortDesc: PropTypes.bool,
|
||||
disableMultiSort: PropTypes.bool,
|
||||
subRowsKey: PropTypes.string,
|
||||
expandedKey: PropTypes.string,
|
||||
userAggregations: PropTypes.object,
|
||||
|
||||
debug: PropTypes.bool,
|
||||
}
|
||||
|
||||
export default function useReactTable (props) {
|
||||
// Validate props
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useReactTable')
|
||||
|
||||
// Destructure props
|
||||
const {
|
||||
data = [],
|
||||
state: userState,
|
||||
columns: userColumns,
|
||||
sortBy: userSortBy,
|
||||
groupBy: userGroupBy,
|
||||
filters: userFilters,
|
||||
expanded: userExpanded,
|
||||
defaultSortBy = [],
|
||||
defaultGroupBy = [],
|
||||
defaultFilters = {},
|
||||
defaultExpanded = {},
|
||||
manualGrouping,
|
||||
manualFilters,
|
||||
manualSorting,
|
||||
disableSorting,
|
||||
disableGrouping,
|
||||
disableFilters,
|
||||
defaultSortDesc,
|
||||
disableMultiSort,
|
||||
subRowsKey = 'subRows',
|
||||
expandedKey = 'expanded',
|
||||
filterFn = defaultFilterFn,
|
||||
sortByFn = defaultSortByFn,
|
||||
orderByFn = defaultOrderByFn,
|
||||
groupByFn = defaultGroupByFn,
|
||||
userAggregations = {},
|
||||
debug,
|
||||
...rest
|
||||
} = props
|
||||
|
||||
warnUnknownProps(rest)
|
||||
|
||||
const defaultState = useState({})
|
||||
|
||||
const localState = userState || defaultState
|
||||
|
||||
// Build the controllable state
|
||||
const [{
|
||||
sortBy, groupBy, filters, expanded,
|
||||
}, setState] = useControlledState(
|
||||
localState,
|
||||
{
|
||||
sortBy: defaultSortBy,
|
||||
groupBy: defaultGroupBy,
|
||||
filters: defaultFilters,
|
||||
expanded: defaultExpanded,
|
||||
},
|
||||
{
|
||||
sortBy: userSortBy,
|
||||
groupBy: userGroupBy,
|
||||
filters: userFilters,
|
||||
expanded: userExpanded,
|
||||
}
|
||||
)
|
||||
|
||||
const expandedDepth = findExpandedDepth(expanded)
|
||||
const renderedCellInfoRef = useRef({})
|
||||
|
||||
// Build the base column
|
||||
let { columns, headerGroups, headers } = useColumns({
|
||||
debug,
|
||||
groupBy,
|
||||
userColumns,
|
||||
defaultFlex,
|
||||
renderedCellInfoRef,
|
||||
disableSorting,
|
||||
disableGrouping,
|
||||
disableFilters,
|
||||
})
|
||||
|
||||
// Use data and columns as memoization
|
||||
const accessedRows = useAccessedRows({
|
||||
debug,
|
||||
data,
|
||||
columns,
|
||||
subRowsKey,
|
||||
})
|
||||
|
||||
// Use rows and groupBy as memoization for row grouping
|
||||
const groupedRows = useGroupedRows({
|
||||
debug,
|
||||
rows: accessedRows,
|
||||
groupBy,
|
||||
columns,
|
||||
groupByFn,
|
||||
manualGrouping,
|
||||
userAggregations,
|
||||
})
|
||||
|
||||
const filteredRows = useFilteredRows({
|
||||
debug,
|
||||
rows: groupedRows,
|
||||
filters,
|
||||
columns,
|
||||
filterFn,
|
||||
manualFilters,
|
||||
})
|
||||
|
||||
const sortedRows = useSortedRows({
|
||||
debug,
|
||||
rows: filteredRows,
|
||||
sortBy,
|
||||
columns,
|
||||
orderByFn,
|
||||
sortByFn,
|
||||
manualSorting,
|
||||
})
|
||||
|
||||
const rows = useExpandedRows({
|
||||
debug,
|
||||
rows: sortedRows,
|
||||
expanded,
|
||||
expandedKey,
|
||||
columns,
|
||||
groupBy,
|
||||
setState,
|
||||
actions,
|
||||
})
|
||||
|
||||
// Mutate columns to reflect sorting state
|
||||
columns.forEach(column => {
|
||||
const { id } = column
|
||||
column.sorted = sortBy.find(d => d.id === id)
|
||||
column.sortedIndex = sortBy.findIndex(d => d.id === id)
|
||||
column.sortedDesc = column.sorted ? column.sorted.desc : undefined
|
||||
column.filterVal = filters[id]
|
||||
})
|
||||
|
||||
// Public API
|
||||
|
||||
const {
|
||||
toggleExpandedByPath,
|
||||
toggleSortByID,
|
||||
toggleGroupBy,
|
||||
setFilter,
|
||||
setAllFilters,
|
||||
} = useTableMethods({
|
||||
debug,
|
||||
setState,
|
||||
actions,
|
||||
groupBy,
|
||||
columns,
|
||||
defaultSortDesc,
|
||||
filters,
|
||||
})
|
||||
|
||||
const state = {
|
||||
// State
|
||||
columns,
|
||||
expandedDepth,
|
||||
accessedRows,
|
||||
groupedRows,
|
||||
filteredRows,
|
||||
sortedRows,
|
||||
rows,
|
||||
headerGroups,
|
||||
renderedCellInfoRef,
|
||||
disableMultiSort,
|
||||
|
||||
// Controllable state
|
||||
groupBy,
|
||||
filters,
|
||||
sortBy,
|
||||
expanded,
|
||||
}
|
||||
|
||||
const api = {
|
||||
// State manager,
|
||||
...state,
|
||||
|
||||
// Methods
|
||||
toggleExpandedByPath,
|
||||
toggleSortByID,
|
||||
toggleGroupBy,
|
||||
setFilter,
|
||||
setAllFilters,
|
||||
}
|
||||
|
||||
columns.forEach(column => attachApi(column, api))
|
||||
headers.forEach(header => attachApi(header, api))
|
||||
|
||||
const visibleColumns = columns.filter(column => {
|
||||
column.visible = typeof column.show === 'function' ? column.show(state) : !!column.show
|
||||
return column.visible
|
||||
})
|
||||
|
||||
state.visibleColumns = visibleColumns
|
||||
api.visibleColumns = visibleColumns
|
||||
|
||||
api.hooks = {
|
||||
getTableProps: [],
|
||||
getRowProps: [],
|
||||
getHeaderRowProps: [],
|
||||
getCellProps: [],
|
||||
getHeaderProps: [],
|
||||
getSortByToggleProps: [],
|
||||
getGroupByToggleProps: [],
|
||||
}
|
||||
|
||||
api.getTableProps = props => mergeProps(
|
||||
{
|
||||
style: {
|
||||
overflowX: 'auto',
|
||||
},
|
||||
},
|
||||
applyHooks(api.hooks.getTableProps),
|
||||
props
|
||||
)
|
||||
|
||||
api.getRowProps = props => mergeProps(applyHooks(api.hooks.getRowProps), props)
|
||||
|
||||
// Filter out hidden header groups or header groups that
|
||||
// have no visible columns, then add the getRowProps method
|
||||
headerGroups = useMemo(
|
||||
() =>
|
||||
headerGroups.filter((headerGroup, i) => {
|
||||
headerGroup.headers = headerGroup.headers.filter(header => {
|
||||
const recurse = columns => columns.filter(column => {
|
||||
if (column.columns) {
|
||||
return recurse(column.columns)
|
||||
}
|
||||
return column.visible
|
||||
}).length
|
||||
if (header.columns) {
|
||||
return recurse(header.columns)
|
||||
}
|
||||
return header.visible
|
||||
})
|
||||
|
||||
if (headerGroup.headers.length) {
|
||||
headerGroup.getRowProps = (props = {}) => mergeProps(
|
||||
{
|
||||
key: [`header${i}`].join('_'),
|
||||
},
|
||||
applyHooks(api.hooks.getHeaderRowProps, headerGroup),
|
||||
props
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}),
|
||||
[headerGroups]
|
||||
)
|
||||
|
||||
// A function that mutates and prepares page rows with methods
|
||||
api.prepareRows = rows => {
|
||||
rows.forEach((row, i) => {
|
||||
const { path } = row
|
||||
row.getRowProps = props => mergeProps(
|
||||
{ key: ['row', i].join('_') },
|
||||
applyHooks(api.hooks.getRowProps, row),
|
||||
props
|
||||
)
|
||||
|
||||
row.toggleExpanded = set => toggleExpandedByPath(path, set)
|
||||
|
||||
row.cells = row.cells.filter(cell => cell.column.visible);
|
||||
[row.groupByCell, ...row.cells].forEach(cell => {
|
||||
if (!cell) {
|
||||
return
|
||||
}
|
||||
|
||||
const { column } = cell
|
||||
|
||||
cell.getCellProps = props => {
|
||||
const columnPathStr = [i, column.id].join('_')
|
||||
return mergeProps(
|
||||
{
|
||||
key: ['cell', columnPathStr].join('_'),
|
||||
},
|
||||
applyHooks(api.hooks.getCellProps, cell),
|
||||
props
|
||||
)
|
||||
}
|
||||
|
||||
cell.render = (type, userProps = {}) => {
|
||||
if (!type) {
|
||||
throw new Error(
|
||||
'You must specify a render "type". This could be "Cell", "Header", "Filter", "Aggregated" or any other custom renderers you have set on your column.'
|
||||
)
|
||||
}
|
||||
return flexRender(column[type], {
|
||||
...api,
|
||||
...cell,
|
||||
...userProps,
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const makeHook = api => {
|
||||
api.hook = (hook, ...args) => {
|
||||
const result = hook(api, ...args) || {}
|
||||
const newApi = {
|
||||
...api,
|
||||
...result,
|
||||
}
|
||||
makeHook(newApi)
|
||||
return newApi
|
||||
}
|
||||
}
|
||||
|
||||
makeHook(api)
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
function attachApi (column, api) {
|
||||
const {
|
||||
id, canSortBy, canGroupBy, canFilter,
|
||||
} = column
|
||||
const { toggleSortByID, toggleGroupBy, setFilter } = api
|
||||
|
||||
column.render = (type, userProps = {}) => {
|
||||
if (!type) {
|
||||
throw new Error(renderErr)
|
||||
}
|
||||
return flexRender(column[type], {
|
||||
...api,
|
||||
...column,
|
||||
...userProps,
|
||||
})
|
||||
}
|
||||
|
||||
if (canSortBy) {
|
||||
column.toggleSortBy = (desc, multi) => toggleSortByID(id, desc, multi)
|
||||
}
|
||||
if (canGroupBy) {
|
||||
column.toggleGroupBy = () => toggleGroupBy(id)
|
||||
}
|
||||
if (canFilter) {
|
||||
column.setFilter = val => setFilter(id, val)
|
||||
}
|
||||
|
||||
column.getSortByToggleProps = props => mergeProps(
|
||||
{
|
||||
onClick: canSortBy
|
||||
? e => {
|
||||
e.persist()
|
||||
column.toggleSortBy(undefined, !api.disableMultiSort && e.shiftKey)
|
||||
}
|
||||
: undefined,
|
||||
style: {
|
||||
cursor: canSortBy ? 'pointer' : undefined,
|
||||
},
|
||||
title: 'Toggle SortBy',
|
||||
},
|
||||
applyHooks(api.hooks.getSortByToggleProps, column),
|
||||
props
|
||||
)
|
||||
|
||||
column.getGroupByToggleProps = props => mergeProps(
|
||||
{
|
||||
onClick: canGroupBy
|
||||
? e => {
|
||||
e.persist()
|
||||
column.toggleGroupBy()
|
||||
}
|
||||
: undefined,
|
||||
style: {
|
||||
cursor: canGroupBy ? 'pointer' : undefined,
|
||||
},
|
||||
title: 'Toggle GroupBy',
|
||||
},
|
||||
applyHooks(api.hooks.getGroupByToggleProps, column),
|
||||
props
|
||||
)
|
||||
|
||||
column.getHeaderProps = props => mergeProps(
|
||||
{
|
||||
key: ['header', column.id].join('_'),
|
||||
},
|
||||
applyHooks(api.hooks.getHeaderProps, column),
|
||||
props
|
||||
)
|
||||
}
|
||||
68
src/utils.js
68
src/utils.js
@@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
export function getBy (obj, path, def) {
|
||||
export function getBy(obj, path, def) {
|
||||
if (!path) {
|
||||
return obj
|
||||
}
|
||||
@@ -14,7 +14,7 @@ export function getBy (obj, path, def) {
|
||||
return typeof val !== 'undefined' ? val : def
|
||||
}
|
||||
|
||||
export function defaultOrderByFn (arr, funcs, dirs) {
|
||||
export function defaultOrderByFn(arr, funcs, dirs) {
|
||||
return [...arr].sort((rowA, rowB) => {
|
||||
for (let i = 0; i < funcs.length; i += 1) {
|
||||
const sortFn = funcs[i]
|
||||
@@ -28,7 +28,7 @@ export function defaultOrderByFn (arr, funcs, dirs) {
|
||||
})
|
||||
}
|
||||
|
||||
export function defaultSortByFn (a, b, desc) {
|
||||
export function defaultSortByFn(a, b, desc) {
|
||||
// force null and undefined to the bottom
|
||||
a = a === null || a === undefined ? '' : a
|
||||
b = b === null || b === undefined ? '' : b
|
||||
@@ -47,7 +47,7 @@ export function defaultSortByFn (a, b, desc) {
|
||||
return 0
|
||||
}
|
||||
|
||||
export function getFirstDefined (...args) {
|
||||
export function getFirstDefined(...args) {
|
||||
for (let i = 0; i < args.length; i += 1) {
|
||||
if (typeof args[i] !== 'undefined') {
|
||||
return args[i]
|
||||
@@ -55,16 +55,19 @@ export function getFirstDefined (...args) {
|
||||
}
|
||||
}
|
||||
|
||||
export function defaultGroupByFn (rows, grouper) {
|
||||
export function defaultGroupByFn(rows, grouper) {
|
||||
return rows.reduce((prev, row, i) => {
|
||||
const resKey = typeof grouper === 'function' ? grouper(row.values, i) : row.values[grouper]
|
||||
const resKey =
|
||||
typeof grouper === 'function'
|
||||
? grouper(row.values, i)
|
||||
: row.values[grouper]
|
||||
prev[resKey] = Array.isArray(prev[resKey]) ? prev[resKey] : []
|
||||
prev[resKey].push(row)
|
||||
return prev
|
||||
}, {})
|
||||
}
|
||||
|
||||
export function defaultFilterFn (row, id, value, column) {
|
||||
export function defaultFilterFn(row, id, value, column) {
|
||||
return row.values[id] !== undefined
|
||||
? String(row.values[id])
|
||||
.toLowerCase()
|
||||
@@ -72,59 +75,57 @@ export function defaultFilterFn (row, id, value, column) {
|
||||
: true
|
||||
}
|
||||
|
||||
export function setBy (obj = {}, path, value) {
|
||||
export function setBy(obj = {}, path, value) {
|
||||
const recurse = (obj, depth = 0) => {
|
||||
const key = path[depth]
|
||||
const target = typeof obj[key] !== 'object' ? {} : obj[key]
|
||||
const subValue = depth === path.length - 1 ? value : recurse(target, depth + 1)
|
||||
const subValue =
|
||||
depth === path.length - 1 ? value : recurse(target, depth + 1)
|
||||
return {
|
||||
...obj,
|
||||
[key]: subValue,
|
||||
[key]: subValue
|
||||
}
|
||||
}
|
||||
|
||||
return recurse(obj)
|
||||
}
|
||||
|
||||
export function getElementDimensions (element) {
|
||||
export function getElementDimensions(element) {
|
||||
const rect = element.getBoundingClientRect()
|
||||
const style = window.getComputedStyle(element)
|
||||
const margins = {
|
||||
left: parseInt(style.marginLeft),
|
||||
right: parseInt(style.marginRight),
|
||||
right: parseInt(style.marginRight)
|
||||
}
|
||||
const padding = {
|
||||
left: parseInt(style.paddingLeft),
|
||||
right: parseInt(style.paddingRight),
|
||||
right: parseInt(style.paddingRight)
|
||||
}
|
||||
return {
|
||||
left: Math.ceil(rect.left),
|
||||
width: Math.ceil(rect.width),
|
||||
outerWidth: Math.ceil(rect.width + margins.left + margins.right + padding.left + padding.right),
|
||||
outerWidth: Math.ceil(
|
||||
rect.width + margins.left + margins.right + padding.left + padding.right
|
||||
),
|
||||
marginLeft: margins.left,
|
||||
marginRight: margins.right,
|
||||
paddingLeft: padding.left,
|
||||
paddingRight: padding.right,
|
||||
scrollWidth: element.scrollWidth,
|
||||
scrollWidth: element.scrollWidth
|
||||
}
|
||||
}
|
||||
|
||||
export function flexRender (Comp, props) {
|
||||
export function flexRender(Comp, props) {
|
||||
if (typeof Comp === 'function') {
|
||||
return Object.getPrototypeOf(Comp).isReactComponent ? <Comp {...props} /> : Comp(props)
|
||||
return Object.getPrototypeOf(Comp).isReactComponent ? (
|
||||
<Comp {...props} />
|
||||
) : (
|
||||
Comp(props)
|
||||
)
|
||||
}
|
||||
return Comp
|
||||
}
|
||||
|
||||
export function findExpandedDepth (obj, depth = 1) {
|
||||
return Object.values(obj).reduce((prev, curr) => {
|
||||
if (typeof curr === 'object') {
|
||||
return Math.max(prev, findExpandedDepth(curr, depth + 1))
|
||||
}
|
||||
return depth
|
||||
}, 0)
|
||||
}
|
||||
|
||||
export const mergeProps = (...groups) => {
|
||||
let props = {}
|
||||
groups.forEach(({ style = {}, className, ...rest } = {}) => {
|
||||
@@ -133,15 +134,18 @@ export const mergeProps = (...groups) => {
|
||||
...rest,
|
||||
style: {
|
||||
...(props.style || {}),
|
||||
...style,
|
||||
...style
|
||||
},
|
||||
className: [props.className, className].filter(Boolean).join(' '),
|
||||
className: [props.className, className].filter(Boolean).join(' ')
|
||||
}
|
||||
})
|
||||
return props
|
||||
}
|
||||
|
||||
export const applyHooks = (hooks, ...args) =>
|
||||
export const applyHooks = (hooks, initial, ...args) =>
|
||||
hooks.reduce((prev, next) => next(prev, ...args), initial)
|
||||
|
||||
export const applyPropHooks = (hooks, ...args) =>
|
||||
hooks.reduce((prev, next) => mergeProps(prev, next(...args)), {})
|
||||
|
||||
export const warnUnknownProps = props => {
|
||||
@@ -154,11 +158,11 @@ ${JSON.stringify(props, null, 2)}`
|
||||
}
|
||||
}
|
||||
|
||||
export function sum (arr) {
|
||||
export function sum(arr) {
|
||||
return arr.reduce((prev, curr) => prev + curr, 0)
|
||||
}
|
||||
|
||||
function makePathArray (obj) {
|
||||
function makePathArray(obj) {
|
||||
return flattenDeep(obj)
|
||||
.join('.')
|
||||
.replace(/\[/g, '.')
|
||||
@@ -166,7 +170,7 @@ function makePathArray (obj) {
|
||||
.split('.')
|
||||
}
|
||||
|
||||
function flattenDeep (arr, newArr = []) {
|
||||
function flattenDeep(arr, newArr = []) {
|
||||
if (!Array.isArray(arr)) {
|
||||
newArr.push(arr)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user