mirror of
https://github.com/gosticks/react-table.git
synced 2026-08-19 16:30:21 +00:00
Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8776d78edb | ||
|
|
f0293f5511 | ||
|
|
16c5512c4f | ||
|
|
3b42c72e7c | ||
|
|
f40b8f5ca2 | ||
|
|
b61a8ed040 | ||
|
|
4665006109 | ||
|
|
c0aa9533e9 | ||
|
|
944b75c709 | ||
|
|
4537f28a8d |
+42
-14
@@ -97,6 +97,19 @@ The following options are supported via the main options object passed to `useTa
|
||||
- Defaults to `initialState`
|
||||
- This key is used to look for the initial state of a row when initializing the `rowState` for a`data` array.
|
||||
- If the value located at `row[initialRowStateKey]` is falsey, `{}` will be used instead.
|
||||
- `getSubRows: Function(row, relativeIndex) => Rows[]`
|
||||
- Optional
|
||||
- Must be **memoized**
|
||||
- Defaults to `(row) => row.subRows || []`
|
||||
- Use this function to change how React Table detects subrows. You could even use this function to generate sub rows if you want.
|
||||
- By default, it will attempt to return the `subRows` property on the row, or an empty array if that is not found.
|
||||
- `getRowID: Function(row, relativeIndex) => string`
|
||||
- Use this function to change how React Table detects unique rows and also how it constructs each row's underlying `path` property.
|
||||
- Optional
|
||||
- Must be **memoized**
|
||||
- Defaults to `(row, relativeIndex) => relativeIndex`
|
||||
- You may want to change this function if
|
||||
- By default, it will use the `index` of the row within it's original array.
|
||||
- `debug: Bool`
|
||||
- Optional
|
||||
- A flag to turn on debug mode.
|
||||
@@ -143,14 +156,24 @@ The following options are supported on any column object you can pass to `column
|
||||
|
||||
The following properties are available on the table instance returned from `useTable`
|
||||
|
||||
- `columns: Array<Column>`
|
||||
- A **nested** array of final column objects, **similar in structure to the original columns configuration option**.
|
||||
- See [Column Properties](#column-properties) for more information
|
||||
- `flatColumns: Array<Column>`
|
||||
- A **flat** array of all final column objects.
|
||||
- See [Column Properties](#column-properties) for more information
|
||||
- `headerGroups: Array<HeaderGroup>`
|
||||
- An array of normalized header groups, each containing a flattened array of final column objects for that row.
|
||||
- **Some of these headers may be materialized as placeholders**
|
||||
- See [Header Group Properties](#headergroup-properties) for more information
|
||||
- `columns: Array<Column>`
|
||||
- A **flat** array of all final column objects computed from the original columns configuration option.
|
||||
- `headers: Array<Column>`
|
||||
- An **nested** array of final header objects, **similar in structure to the original columns configuration option, but rebuilt for ordering**
|
||||
- Each contains the headers that are displayed underneath it.
|
||||
- **Some of these headers may be materialized as placeholders**
|
||||
- See [Column Properties](#column-properties) for more information
|
||||
- `headers[] Array<Column>`
|
||||
- A **nested** array of final column objects, similar in structure to the original columns configuration option.
|
||||
- `flatHeaders[] Array<Column>`
|
||||
- A **flat** array of final header objects found in each header group.
|
||||
- **Some of these headers may be materialized as placeholders**
|
||||
- See [Column Properties](#column-properties) for more information
|
||||
- `rows: Array<Row>`
|
||||
- An array of **materialized row objects** from the original `data` array and `columns` passed into the table options
|
||||
@@ -179,7 +202,7 @@ The following properties are available on the table instance returned from `useT
|
||||
- Pass it a valid `rowPath` array, `columnID` and `updater`. The `updater` may be a value or function, similar to `React.useState`'s usage.
|
||||
- If `updater` is a function, it will be passed the previous value
|
||||
|
||||
### `HeaderGroup` Properties
|
||||
### HeaderGroup Properties
|
||||
|
||||
The following additional properties are available on every `headerGroup` object returned by the table instance.
|
||||
|
||||
@@ -402,12 +425,13 @@ The following options are supported on any `Column` object passed to the `column
|
||||
- If set to `true`, the underlying sorting direction will be inverted, but the UI will not.
|
||||
- This may be useful in situations where positive and negative connotation is inverted, eg. a Golfing score where a lower score is considered more positive than a higher one.
|
||||
- `sortType: String | Function`
|
||||
- Used to compare 2 rows of data and order them correctly.
|
||||
- If a **function** is passed, it must be **memoized**
|
||||
- Defaults to [`alphanumeric`](TODO)
|
||||
- The resolved function from the this string/function will be used to sort the this column's data.
|
||||
- If a `string` is passed, the function with that name located on either the custom `sortTypes` option or the built-in sorting types object will be used. If
|
||||
- If a `function` is passed, it will be used.
|
||||
- For mor information on sort types, see [Sorting](TODO)
|
||||
- For more information on sort types, see [Sorting](TODO)
|
||||
|
||||
### Instance Properties
|
||||
|
||||
@@ -434,12 +458,12 @@ The following properties are available on every `Column` object returned by the
|
||||
- This function is used to resolve any props needed for this column's UI that is responsible for toggling the sort direction when the user clicks it.
|
||||
- You can use the `getSortByToggleProps` hook to extend its functionality.
|
||||
- Custom props may be passed. **NOTE: Custom props may override built-in sortBy props, so be careful!**
|
||||
- `sorted: Boolean`
|
||||
- `isSorted: Boolean`
|
||||
- Denotes whether this column is currently being sorted
|
||||
- `sortedIndex: Int`
|
||||
- If the column is currently sorted, this integer will be the index in the `sortBy` array from state that corresponds to this column.
|
||||
- If this column is not sorted, the index will always be `-1`
|
||||
- `sortedDesc: Bool`
|
||||
- `isSortedDesc: Bool`
|
||||
- If the column is currently sorted, this denotes whether the column's sort direction is descending or not.
|
||||
- If `true`, the column is sorted `descending`
|
||||
- If `false`, the column is sorted `ascending`
|
||||
@@ -473,10 +497,14 @@ function Table({ columns, data }) {
|
||||
<span>
|
||||
{/* Add a sort direction indicator */}
|
||||
<span>
|
||||
{column.sorted ? (column.sortedDesc ? ' 🔽' : ' 🔼') : ''}
|
||||
{column.isSorted
|
||||
? column.isSortedDesc
|
||||
? ' 🔽'
|
||||
: ' 🔼'
|
||||
: ''}
|
||||
</span>
|
||||
{/* Add a sort index indicator */}
|
||||
<span>({column.sorted ? column.sortedIndex + 1 : ''})</span>
|
||||
<span>({column.isSorted ? column.sortedIndex + 1 : ''})</span>
|
||||
</span>
|
||||
</th>
|
||||
))}
|
||||
@@ -520,7 +548,7 @@ The following options are supported via the main options object passed to `useTa
|
||||
- The function (or resolved function from the string) will be used as the default/fallback filter method for every column that has filtering enabled.
|
||||
- If a `string` is passed, the function with that name located on the `filterTypes` option object will be used.
|
||||
- If a `function` is passed, it will be used.
|
||||
- For mor information on filter types, see [Filtering](TODO)
|
||||
- For more information on filter types, see [Filtering](TODO)
|
||||
- `manualFilters: Bool`
|
||||
- Enables filter detection functionality, but does not automatically perform row filtering.
|
||||
- Turn this on if you wish to implement your own row filter outside of the table (eg. server-side or manual row grouping/nesting)
|
||||
@@ -529,7 +557,7 @@ The following options are supported via the main options object passed to `useTa
|
||||
- `filterTypes: Object<filterKey: filterType>`
|
||||
- Must be **memoized**
|
||||
- Allows overriding or adding additional filter types for columns to use. If a column's filter type isn't found on this object, it will default to using the [built-in filter types](TODO).
|
||||
- For mor information on filter types, see [Filtering](TODO)
|
||||
- For more information on filter types, see [Filtering](TODO)
|
||||
|
||||
### Column Options
|
||||
|
||||
@@ -549,7 +577,7 @@ The following options are supported on any `Column` object passed to the `column
|
||||
- The resolved function from the this string/function will be used to filter the this column's data.
|
||||
- If a `string` is passed, the function with that name located on either the custom `filterTypes` option or the built-in filtering types object will be used. If
|
||||
- If a `function` is passed, it will be used directly.
|
||||
- For mor information on filter types, see [Filtering](TODO)
|
||||
- For more information on filter types, see [Filtering](TODO)
|
||||
- If a **function** is passed, it must be **memoized**
|
||||
|
||||
### Instance Properties
|
||||
@@ -917,7 +945,7 @@ The following options are supported via the main options object passed to `useTa
|
||||
- A `pathIndex` can be set as the key and its value set to `true` to expand that row's subRows into view. For example, if `{ '3': true }` was passed as the `expanded` state, the **4th row in the original data array** would be expanded.
|
||||
- For nested expansion, you may **use another object** instead of a Boolean to expand sub rows. For example, if `{ '3': { '5' : true }}` was passed as the `expanded` state, then the **6th subRow of the 4th row and the 4th row of the original data array** would be expanded.
|
||||
- This information is stored in state since the table is allowed to manipulate the filter through user interaction.
|
||||
- `subRowsKey: String`
|
||||
- `getSubRows: Function(row, relativeIndex) => Rows[]`
|
||||
- Optional
|
||||
- See the [useTable hook](#table-options) for more details
|
||||
- `manualExpandedKey: String`
|
||||
|
||||
@@ -33,17 +33,11 @@ const Styles = styled.div`
|
||||
}
|
||||
`
|
||||
|
||||
const defaultColumn = {
|
||||
sort: 'numeric',
|
||||
}
|
||||
|
||||
function Table({ columns, data }) {
|
||||
const { getTableProps, headerGroups, rows, prepareRow } = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
defaultColumn,
|
||||
debug: true,
|
||||
},
|
||||
useSortBy
|
||||
)
|
||||
|
||||
@@ -42,7 +42,7 @@ function Table({ columns: userColumns, data, renderRowSubComponent }) {
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
columns,
|
||||
flatColumns,
|
||||
state: [{ expanded }],
|
||||
} = useTable(
|
||||
{
|
||||
@@ -87,7 +87,7 @@ function Table({ columns: userColumns, data, renderRowSubComponent }) {
|
||||
*/}
|
||||
{row.isExpanded ? (
|
||||
<tr>
|
||||
<td colSpan={columns.length}>
|
||||
<td colSpan={flatColumns.length}>
|
||||
{/*
|
||||
Inside it, call our renderRowSubComponent function. In reality,
|
||||
you coul pass whatever you want as props to
|
||||
|
||||
Binary file not shown.
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-table",
|
||||
"version": "7.0.0-alpha.25",
|
||||
"version": "7.0.0-alpha.29",
|
||||
"description": "A fast, lightweight, opinionated table and datagrid built on React",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/tannerlinsley/react-table#readme",
|
||||
|
||||
+126
-133
@@ -8,8 +8,8 @@ import {
|
||||
flexRender,
|
||||
decorateColumnTree,
|
||||
makeHeaderGroups,
|
||||
findMaxDepth,
|
||||
flattenBy,
|
||||
determineHeaderVisibility,
|
||||
} from '../utils'
|
||||
|
||||
import { useTableState } from './useTableState'
|
||||
@@ -17,14 +17,10 @@ import { useTableState } from './useTableState'
|
||||
const propTypes = {
|
||||
// General
|
||||
data: PropTypes.array.isRequired,
|
||||
columns: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
Cell: PropTypes.any,
|
||||
Header: PropTypes.any,
|
||||
})
|
||||
).isRequired,
|
||||
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
defaultColumn: PropTypes.object,
|
||||
subRowsKey: PropTypes.string,
|
||||
getSubRows: PropTypes.func,
|
||||
getRowID: PropTypes.func,
|
||||
debug: PropTypes.bool,
|
||||
}
|
||||
|
||||
@@ -33,6 +29,9 @@ const renderErr =
|
||||
|
||||
const defaultColumnInstance = {}
|
||||
|
||||
const defaultGetSubRows = (row, index) => row.subRows || []
|
||||
const defaultGetRowID = (row, index) => index
|
||||
|
||||
export const useTable = (props, ...plugins) => {
|
||||
// Validate props
|
||||
PropTypes.checkPropTypes(propTypes, props, 'property', 'useTable')
|
||||
@@ -43,33 +42,31 @@ export const useTable = (props, ...plugins) => {
|
||||
state: userState,
|
||||
columns: userColumns,
|
||||
defaultColumn = defaultColumnInstance,
|
||||
subRowsKey = 'subRows',
|
||||
getSubRows = defaultGetSubRows,
|
||||
getRowID = defaultGetRowID,
|
||||
debug,
|
||||
} = props
|
||||
|
||||
debug = process.env.NODE_ENV === 'production' ? false : debug
|
||||
|
||||
// Always provide a default state
|
||||
// Always provide a default table state
|
||||
const defaultState = useTableState()
|
||||
|
||||
// But use the users state if provided
|
||||
// But use the users table state if provided
|
||||
const state = userState || defaultState
|
||||
|
||||
// The initial api
|
||||
// The table instance ref
|
||||
let instanceRef = React.useRef({})
|
||||
|
||||
Object.assign(instanceRef.current, {
|
||||
...props,
|
||||
data,
|
||||
state,
|
||||
plugins,
|
||||
data, // The raw data
|
||||
state, // The resolved table state
|
||||
plugins, // All resolved plugins
|
||||
hooks: {
|
||||
columnsBeforeHeaderGroups: [],
|
||||
columnsBeforeHeaderGroupsDeps: [],
|
||||
useMain: [],
|
||||
useColumns: [],
|
||||
useHeaders: [],
|
||||
useHeaderGroups: [],
|
||||
useRows: [],
|
||||
prepareRow: [],
|
||||
getTableProps: [],
|
||||
@@ -82,34 +79,32 @@ export const useTable = (props, ...plugins) => {
|
||||
|
||||
// Allow plugins to register hooks
|
||||
if (process.env.NODE_ENV === 'development' && debug) console.time('plugins')
|
||||
|
||||
plugins.filter(Boolean).forEach(plugin => {
|
||||
plugin(instanceRef.current.hooks)
|
||||
})
|
||||
|
||||
if (process.env.NODE_ENV === 'development' && debug)
|
||||
console.timeEnd('plugins')
|
||||
|
||||
if (process.env.NODE_ENV === 'development' && debug)
|
||||
console.info('buildColumns/headerGroup/headers')
|
||||
// Decorate All the columns
|
||||
let columnTree = React.useMemo(
|
||||
let columns = React.useMemo(
|
||||
() => decorateColumnTree(userColumns, defaultColumn),
|
||||
[defaultColumn, userColumns]
|
||||
)
|
||||
|
||||
// Get the flat list of all columns
|
||||
let columns = React.useMemo(() => flattenBy(columnTree, 'columns'), [
|
||||
columnTree,
|
||||
])
|
||||
|
||||
// Allow hooks to decorate columns (and trigger this memoization via deps)
|
||||
columns = React.useMemo(() => {
|
||||
// Get the flat list of all columns andllow hooks to decorate
|
||||
// those columns (and trigger this memoization via deps)
|
||||
let flatColumns = React.useMemo(() => {
|
||||
if (process.env.NODE_ENV === 'development' && debug)
|
||||
console.time('hooks.columnsBeforeHeaderGroups')
|
||||
const newColumns = applyHooks(
|
||||
|
||||
let newColumns = applyHooks(
|
||||
instanceRef.current.hooks.columnsBeforeHeaderGroups,
|
||||
columns,
|
||||
flattenBy(columns, 'columns'),
|
||||
instanceRef.current
|
||||
)
|
||||
|
||||
if (process.env.NODE_ENV === 'development' && debug)
|
||||
console.timeEnd('hooks.columnsBeforeHeaderGroups')
|
||||
return newColumns
|
||||
@@ -126,16 +121,15 @@ export const useTable = (props, ...plugins) => {
|
||||
|
||||
// Make the headerGroups
|
||||
const headerGroups = React.useMemo(
|
||||
() => makeHeaderGroups(columns, findMaxDepth(columnTree), defaultColumn),
|
||||
[columnTree, columns, defaultColumn]
|
||||
() => makeHeaderGroups(flatColumns, columns, defaultColumn),
|
||||
[columns, defaultColumn, flatColumns]
|
||||
)
|
||||
|
||||
const headers = React.useMemo(() => flattenBy(headerGroups, 'headers'), [
|
||||
headerGroups,
|
||||
])
|
||||
const headers = React.useMemo(() => headerGroups[0].headers, [headerGroups])
|
||||
|
||||
Object.assign(instanceRef.current, {
|
||||
columns,
|
||||
flatColumns,
|
||||
headerGroups,
|
||||
headers,
|
||||
})
|
||||
@@ -153,18 +147,20 @@ export const useTable = (props, ...plugins) => {
|
||||
// Keep the original reference around
|
||||
const original = originalRow
|
||||
|
||||
const rowID = getRowID(originalRow, i)
|
||||
|
||||
// Make the new path for the row
|
||||
const path = [...parentPath, i]
|
||||
const path = [...parentPath, rowID]
|
||||
|
||||
flatRows++
|
||||
rowPaths.push(path.join('.'))
|
||||
|
||||
// Process any subRows
|
||||
const subRows = originalRow[subRowsKey]
|
||||
? originalRow[subRowsKey].map((d, i) =>
|
||||
accessRow(d, i, depth + 1, path)
|
||||
)
|
||||
: []
|
||||
let subRows = getSubRows(originalRow, i)
|
||||
|
||||
if (subRows) {
|
||||
subRows = subRows.map((d, i) => accessRow(d, i, depth + 1, path))
|
||||
}
|
||||
|
||||
const row = {
|
||||
original,
|
||||
@@ -189,7 +185,7 @@ export const useTable = (props, ...plugins) => {
|
||||
|
||||
// Create the cells and values
|
||||
row.values = {}
|
||||
columns.forEach(column => {
|
||||
flatColumns.forEach(column => {
|
||||
row.values[column.id] = column.accessor
|
||||
? column.accessor(originalRow, i, { subRows, depth, data })
|
||||
: undefined
|
||||
@@ -203,19 +199,20 @@ export const useTable = (props, ...plugins) => {
|
||||
if (process.env.NODE_ENV === 'development' && debug)
|
||||
console.timeEnd('getAccessedRows')
|
||||
return [accessedData, rowPaths, flatRows]
|
||||
}, [debug, data, columns, subRowsKey])
|
||||
}, [debug, data, getRowID, getSubRows, flatColumns])
|
||||
|
||||
instanceRef.current.rows = rows
|
||||
instanceRef.current.rowPaths = rowPaths
|
||||
instanceRef.current.flatRows = flatRows
|
||||
|
||||
// Determine column visibility
|
||||
instanceRef.current.columns.forEach(column => {
|
||||
column.isVisible =
|
||||
typeof column.show === 'function'
|
||||
? column.show(instanceRef.current)
|
||||
: !!column.show
|
||||
})
|
||||
determineHeaderVisibility(instanceRef.current)
|
||||
|
||||
// Provide a flat header list for utilities
|
||||
instanceRef.current.flatHeaders = headerGroups.reduce(
|
||||
(all, headerGroup) => [...all, ...headerGroup.headers],
|
||||
[]
|
||||
)
|
||||
|
||||
if (process.env.NODE_ENV === 'development' && debug)
|
||||
console.time('hooks.useMain')
|
||||
@@ -225,54 +222,53 @@ export const useTable = (props, ...plugins) => {
|
||||
)
|
||||
if (process.env.NODE_ENV === 'development' && debug)
|
||||
console.timeEnd('hooks.useMain')
|
||||
;[...instanceRef.current.columns, ...instanceRef.current.headers].forEach(
|
||||
column => {
|
||||
// Give columns/headers rendering power
|
||||
column.render = (type, userProps = {}) => {
|
||||
const Comp = typeof type === 'string' ? column[type] : type
|
||||
|
||||
if (typeof Comp === 'undefined') {
|
||||
throw new Error(renderErr)
|
||||
}
|
||||
// Each materialized header needs to be assigned a render function and other
|
||||
// prop getter properties here.
|
||||
instanceRef.current.flatHeaders.forEach(column => {
|
||||
// Give columns/headers rendering power
|
||||
column.render = (type, userProps = {}) => {
|
||||
const Comp = typeof type === 'string' ? column[type] : type
|
||||
|
||||
return flexRender(Comp, {
|
||||
...instanceRef.current,
|
||||
column,
|
||||
...userProps,
|
||||
})
|
||||
if (typeof Comp === 'undefined') {
|
||||
throw new Error(renderErr)
|
||||
}
|
||||
|
||||
// Give columns/headers a default getHeaderProps
|
||||
column.getHeaderProps = props =>
|
||||
mergeProps(
|
||||
{
|
||||
key: ['header', column.id].join('_'),
|
||||
colSpan: column.columns
|
||||
? column.columns.filter(column => column.isVisible).length
|
||||
: 1,
|
||||
},
|
||||
applyPropHooks(
|
||||
instanceRef.current.hooks.getHeaderProps,
|
||||
column,
|
||||
instanceRef.current
|
||||
),
|
||||
props
|
||||
)
|
||||
return flexRender(Comp, {
|
||||
...instanceRef.current,
|
||||
column,
|
||||
...userProps,
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
instanceRef.current.headerGroups.filter((headerGroup, i) => {
|
||||
// Give columns/headers a default getHeaderProps
|
||||
column.getHeaderProps = props =>
|
||||
mergeProps(
|
||||
{
|
||||
key: ['header', column.id].join('_'),
|
||||
colSpan: column.totalHeaderCount,
|
||||
},
|
||||
applyPropHooks(
|
||||
instanceRef.current.hooks.getHeaderProps,
|
||||
column,
|
||||
instanceRef.current
|
||||
),
|
||||
props
|
||||
)
|
||||
})
|
||||
|
||||
instanceRef.current.headerGroups.forEach((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)
|
||||
const recurse = headers =>
|
||||
headers.filter(header => {
|
||||
if (header.headers) {
|
||||
return recurse(header.headers)
|
||||
}
|
||||
return column.isVisible
|
||||
return header.isVisible
|
||||
}).length
|
||||
if (header.columns) {
|
||||
return recurse(header.columns)
|
||||
if (header.headers) {
|
||||
return recurse(header.headers)
|
||||
}
|
||||
return header.isVisible
|
||||
})
|
||||
@@ -291,10 +287,9 @@ export const useTable = (props, ...plugins) => {
|
||||
),
|
||||
props
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
// Run the rows (this could be a dangerous hook with a ton of data)
|
||||
@@ -323,53 +318,51 @@ export const useTable = (props, ...plugins) => {
|
||||
props
|
||||
)
|
||||
|
||||
const visibleColumns = instanceRef.current.columns.filter(
|
||||
column => column.isVisible
|
||||
)
|
||||
|
||||
// Build the cells for each row
|
||||
row.cells = visibleColumns.map(column => {
|
||||
const cell = {
|
||||
column,
|
||||
row,
|
||||
value: row.values[column.id],
|
||||
}
|
||||
|
||||
// Give each cell a getCellProps base
|
||||
cell.getCellProps = props => {
|
||||
const columnPathStr = [...row.path, column.id].join('_')
|
||||
return mergeProps(
|
||||
{
|
||||
key: ['cell', columnPathStr].join('_'),
|
||||
},
|
||||
applyPropHooks(
|
||||
instanceRef.current.hooks.getCellProps,
|
||||
cell,
|
||||
instanceRef.current
|
||||
),
|
||||
props
|
||||
)
|
||||
}
|
||||
|
||||
// Give each cell a renderer function (supports multiple renderers)
|
||||
cell.render = (type, userProps = {}) => {
|
||||
const Comp = typeof type === 'string' ? column[type] : type
|
||||
|
||||
if (typeof Comp === 'undefined') {
|
||||
throw new Error(renderErr)
|
||||
}
|
||||
|
||||
return flexRender(Comp, {
|
||||
...instanceRef.current,
|
||||
// Build the visible cells for each row
|
||||
row.cells = instanceRef.current.flatColumns
|
||||
.filter(d => d.isVisible)
|
||||
.map(column => {
|
||||
const cell = {
|
||||
column,
|
||||
row,
|
||||
cell,
|
||||
...userProps,
|
||||
})
|
||||
}
|
||||
value: row.values[column.id],
|
||||
}
|
||||
|
||||
return cell
|
||||
})
|
||||
// Give each cell a getCellProps base
|
||||
cell.getCellProps = props => {
|
||||
const columnPathStr = [...row.path, column.id].join('_')
|
||||
return mergeProps(
|
||||
{
|
||||
key: ['cell', columnPathStr].join('_'),
|
||||
},
|
||||
applyPropHooks(
|
||||
instanceRef.current.hooks.getCellProps,
|
||||
cell,
|
||||
instanceRef.current
|
||||
),
|
||||
props
|
||||
)
|
||||
}
|
||||
|
||||
// Give each cell a renderer function (supports multiple renderers)
|
||||
cell.render = (type, userProps = {}) => {
|
||||
const Comp = typeof type === 'string' ? column[type] : type
|
||||
|
||||
if (typeof Comp === 'undefined') {
|
||||
throw new Error(renderErr)
|
||||
}
|
||||
|
||||
return flexRender(Comp, {
|
||||
...instanceRef.current,
|
||||
column,
|
||||
row,
|
||||
cell,
|
||||
...userProps,
|
||||
})
|
||||
}
|
||||
|
||||
return cell
|
||||
})
|
||||
|
||||
// need to apply any row specific hooks (useExpanded requires this)
|
||||
applyHooks(instanceRef.current.hooks.prepareRow, row, instanceRef.current)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -50,7 +50,7 @@ function Table({ columns: userColumns, data, SubComponent }) {
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
columns,
|
||||
flatColumns,
|
||||
state: [{ expanded }],
|
||||
} = useTable(
|
||||
{
|
||||
@@ -90,7 +90,9 @@ function Table({ columns: userColumns, data, SubComponent }) {
|
||||
</tr>
|
||||
{!row.subRows.length && row.isExpanded ? (
|
||||
<tr>
|
||||
<td colSpan={columns.length}>{SubComponent({ row })}</td>
|
||||
<td colSpan={flatColumns.length}>
|
||||
{SubComponent({ row })}
|
||||
</td>
|
||||
</tr>
|
||||
) : null}
|
||||
</React.Fragment>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTable } from '../../hooks/useTable'
|
||||
import { useRowSelect } from '../useRowSelect'
|
||||
import { useExpanded } from '../useExpanded'
|
||||
|
||||
const data = [
|
||||
const dataPiece = [
|
||||
{
|
||||
firstName: 'tanner',
|
||||
lastName: 'linsley',
|
||||
@@ -58,6 +58,15 @@ const data = [
|
||||
},
|
||||
]
|
||||
|
||||
const data = [
|
||||
...dataPiece,
|
||||
...dataPiece,
|
||||
...dataPiece,
|
||||
...dataPiece,
|
||||
...dataPiece,
|
||||
...dataPiece,
|
||||
]
|
||||
|
||||
function Table({ columns, data }) {
|
||||
// Use the state and functions returned from useTable to build your UI
|
||||
const {
|
||||
@@ -141,7 +150,10 @@ function App() {
|
||||
{
|
||||
id: 'selectedStatus',
|
||||
Cell: ({ row }) => (
|
||||
<div>{row.isSelected ? 'Selected' : 'Not Selected'}</div>
|
||||
<div>
|
||||
Row {row.path.join('.')}{' '}
|
||||
{row.isSelected ? 'Selected' : 'Not Selected'}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
@@ -218,7 +230,6 @@ test('renders a table with selectable rows', () => {
|
||||
|
||||
const fragment8 = asFragment()
|
||||
|
||||
|
||||
expect(fragment1).toMatchDiffSnapshot(fragment2)
|
||||
expect(fragment2).toMatchDiffSnapshot(fragment3)
|
||||
expect(fragment3).toMatchDiffSnapshot(fragment4)
|
||||
|
||||
@@ -33,7 +33,7 @@ function useMain(instance) {
|
||||
const {
|
||||
debug,
|
||||
rows,
|
||||
columns,
|
||||
flatColumns,
|
||||
filterTypes: userFilterTypes,
|
||||
manualFilters,
|
||||
disableFilters,
|
||||
@@ -43,7 +43,7 @@ function useMain(instance) {
|
||||
const preFilteredRows = rows
|
||||
|
||||
const setFilter = (id, updater) => {
|
||||
const column = columns.find(d => d.id === id)
|
||||
const column = flatColumns.find(d => d.id === id)
|
||||
|
||||
if (!column) {
|
||||
throw new Error(`React-Table: Could not find a column with id: ${id}`)
|
||||
@@ -85,7 +85,7 @@ function useMain(instance) {
|
||||
// Filter out undefined values
|
||||
Object.keys(newFilters).forEach(id => {
|
||||
const newFilter = newFilters[id]
|
||||
const column = columns.find(d => d.id === id)
|
||||
const column = flatColumns.find(d => d.id === id)
|
||||
const filterMethod = getFilterMethod(
|
||||
column.filter,
|
||||
userFilterTypes || {},
|
||||
@@ -104,7 +104,7 @@ function useMain(instance) {
|
||||
}, actions.setAllFilters)
|
||||
}
|
||||
|
||||
columns.forEach(column => {
|
||||
flatColumns.forEach(column => {
|
||||
const { id, accessor, disableFilters: columnDisableFilters } = column
|
||||
|
||||
// Determine if a column is filterable
|
||||
@@ -144,7 +144,7 @@ function useMain(instance) {
|
||||
filteredRows = Object.entries(filters).reduce(
|
||||
(filteredSoFar, [columnID, filterValue]) => {
|
||||
// Find the filters column
|
||||
const column = columns.find(d => d.id === columnID)
|
||||
const column = flatColumns.find(d => d.id === columnID)
|
||||
|
||||
if (!column) {
|
||||
return filteredSoFar
|
||||
@@ -195,12 +195,12 @@ function useMain(instance) {
|
||||
}
|
||||
|
||||
return filterRows(rows)
|
||||
}, [manualFilters, filters, debug, rows, columns, userFilterTypes])
|
||||
}, [manualFilters, filters, debug, rows, flatColumns, userFilterTypes])
|
||||
|
||||
React.useMemo(() => {
|
||||
// Now that each filtered column has it's partially filtered rows,
|
||||
// lets assign the final filtered rows to all of the other columns
|
||||
const nonFilteredColumns = columns.filter(
|
||||
const nonFilteredColumns = flatColumns.filter(
|
||||
column => !Object.keys(filters).includes(column.id)
|
||||
)
|
||||
|
||||
@@ -209,7 +209,7 @@ function useMain(instance) {
|
||||
nonFilteredColumns.forEach(column => {
|
||||
column.preFilteredRows = filteredRows
|
||||
})
|
||||
}, [columns, filteredRows, filters])
|
||||
}, [filteredRows, filters, flatColumns])
|
||||
|
||||
return {
|
||||
...instance,
|
||||
|
||||
@@ -48,16 +48,16 @@ export const useGroupBy = hooks => {
|
||||
|
||||
useGroupBy.pluginName = 'useGroupBy'
|
||||
|
||||
function columnsBeforeHeaderGroups(columns, { state: [{ groupBy }] }) {
|
||||
function columnsBeforeHeaderGroups(flatColumns, { state: [{ groupBy }] }) {
|
||||
// Sort grouped columns to the start of the column list
|
||||
// before the headers are built
|
||||
|
||||
const groupByColumns = groupBy.map(g => columns.find(col => col.id === g))
|
||||
const nonGroupByColumns = columns.filter(col => !groupBy.includes(col.id))
|
||||
const groupByColumns = groupBy.map(g => flatColumns.find(col => col.id === g))
|
||||
const nonGroupByColumns = flatColumns.filter(col => !groupBy.includes(col.id))
|
||||
|
||||
// If a groupByBoundary column is found, place the groupBy's after it
|
||||
const groupByBoundaryColumnIndex =
|
||||
columns.findIndex(column => column.groupByBoundary) + 1
|
||||
flatColumns.findIndex(column => column.groupByBoundary) + 1
|
||||
|
||||
return [
|
||||
...nonGroupByColumns.slice(0, groupByBoundaryColumnIndex),
|
||||
@@ -72,8 +72,8 @@ function useMain(instance) {
|
||||
const {
|
||||
debug,
|
||||
rows,
|
||||
columns,
|
||||
headers,
|
||||
flatColumns,
|
||||
flatHeaders,
|
||||
groupByFn = defaultGroupByFn,
|
||||
manualGroupBy,
|
||||
disableGrouping,
|
||||
@@ -85,7 +85,7 @@ function useMain(instance) {
|
||||
|
||||
ensurePluginOrder(plugins, [], 'useGroupBy', ['useExpanded'])
|
||||
|
||||
columns.forEach(column => {
|
||||
flatColumns.forEach(column => {
|
||||
const { id, accessor, disableGrouping: columnDisableGrouping } = column
|
||||
column.isGrouped = groupBy.includes(id)
|
||||
column.groupedIndex = groupBy.indexOf(id)
|
||||
@@ -124,16 +124,15 @@ function useMain(instance) {
|
||||
|
||||
hooks.getGroupByToggleProps = []
|
||||
|
||||
//
|
||||
;[...columns, ...headers].forEach(column => {
|
||||
const { canGroupBy } = column
|
||||
column.getGroupByToggleProps = props => {
|
||||
flatHeaders.forEach(header => {
|
||||
const { canGroupBy } = header
|
||||
header.getGroupByToggleProps = props => {
|
||||
return mergeProps(
|
||||
{
|
||||
onClick: canGroupBy
|
||||
? e => {
|
||||
e.persist()
|
||||
column.toggleGroupBy()
|
||||
header.toggleGroupBy()
|
||||
}
|
||||
: undefined,
|
||||
style: {
|
||||
@@ -141,7 +140,7 @@ function useMain(instance) {
|
||||
},
|
||||
title: 'Toggle GroupBy',
|
||||
},
|
||||
applyPropHooks(instance.hooks.getGroupByToggleProps, column, instance),
|
||||
applyPropHooks(instance.hooks.getGroupByToggleProps, header, instance),
|
||||
props
|
||||
)
|
||||
}
|
||||
@@ -173,7 +172,7 @@ function useMain(instance) {
|
||||
const aggregateRowsToValues = (rows, isSourceRows) => {
|
||||
const values = {}
|
||||
|
||||
columns.forEach(column => {
|
||||
flatColumns.forEach(column => {
|
||||
// Don't aggregate columns that are in the groupBy
|
||||
if (groupBy.includes(column.id)) {
|
||||
values[column.id] = rows[0] ? rows[0].values[column.id] : null
|
||||
@@ -266,7 +265,7 @@ function useMain(instance) {
|
||||
groupBy,
|
||||
debug,
|
||||
rows,
|
||||
columns,
|
||||
flatColumns,
|
||||
userAggregations,
|
||||
groupByFn,
|
||||
])
|
||||
|
||||
@@ -70,6 +70,7 @@ function useMain(instance) {
|
||||
|
||||
const toggleRowSelected = (path, set) => {
|
||||
const key = path.join('.')
|
||||
const childRowPrefixKey = [key, '.'].join('')
|
||||
|
||||
return setState(old => {
|
||||
// Join the paths of deep rows
|
||||
@@ -81,11 +82,15 @@ function useMain(instance) {
|
||||
|
||||
if (!exists && shouldExist) {
|
||||
rowPaths.forEach(rowPath => {
|
||||
if (rowPath.startsWith(key)) newSelectedRows.add(rowPath)
|
||||
if (rowPath === key || rowPath.startsWith(childRowPrefixKey)) {
|
||||
newSelectedRows.add(rowPath)
|
||||
}
|
||||
})
|
||||
} else if (exists && !shouldExist) {
|
||||
rowPaths.forEach(rowPath => {
|
||||
if (rowPath.startsWith(key)) newSelectedRows.delete(rowPath)
|
||||
if (rowPath === key || rowPath.startsWith(childRowPrefixKey)) {
|
||||
newSelectedRows.delete(rowPath)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
return old
|
||||
@@ -128,7 +133,6 @@ function useMain(instance) {
|
||||
)
|
||||
row.toggleRowSelected = set => {
|
||||
set = typeof set !== 'undefined' ? set : !row.isSelected
|
||||
console.log(subRowPaths)
|
||||
subRowPaths.forEach(path => {
|
||||
toggleRowSelected(path, set)
|
||||
})
|
||||
|
||||
@@ -49,7 +49,7 @@ function useMain(instance) {
|
||||
const {
|
||||
debug,
|
||||
rows,
|
||||
columns,
|
||||
flatColumns,
|
||||
orderByFn = defaultOrderByFn,
|
||||
sortTypes: userSortTypes,
|
||||
manualSorting,
|
||||
@@ -59,6 +59,7 @@ function useMain(instance) {
|
||||
disableMultiSort,
|
||||
isMultiSortEvent = e => e.shiftKey,
|
||||
maxMultiSortColCount = Number.MAX_SAFE_INTEGER,
|
||||
flatHeaders,
|
||||
hooks,
|
||||
state: [{ sortBy }, setState],
|
||||
plugins,
|
||||
@@ -74,7 +75,7 @@ function useMain(instance) {
|
||||
const { sortBy } = old
|
||||
|
||||
// Find the column for this columnID
|
||||
const column = columns.find(d => d.id === columnID)
|
||||
const column = flatColumns.find(d => d.id === columnID)
|
||||
const { sortDescFirst } = column
|
||||
|
||||
// Find any existing sortBy for this column
|
||||
@@ -157,7 +158,7 @@ function useMain(instance) {
|
||||
}
|
||||
|
||||
// Add the getSortByToggleProps method to columns and headers
|
||||
;[...instance.columns, ...instance.headers].forEach(column => {
|
||||
flatHeaders.forEach(column => {
|
||||
const { accessor, disableSorting: columnDisableSorting, id } = column
|
||||
|
||||
const canSort = accessor
|
||||
@@ -212,7 +213,7 @@ function useMain(instance) {
|
||||
|
||||
// Filter out sortBys that correspond to non existing columns
|
||||
const availableSortBy = sortBy.filter(sort =>
|
||||
columns.find(col => col.id === sort.id)
|
||||
flatColumns.find(col => col.id === sort.id)
|
||||
)
|
||||
|
||||
const sortData = rows => {
|
||||
@@ -223,7 +224,7 @@ function useMain(instance) {
|
||||
rows,
|
||||
availableSortBy.map(sort => {
|
||||
// Support custom sorting methods for each column
|
||||
const column = columns.find(d => d.id === sort.id)
|
||||
const column = flatColumns.find(d => d.id === sort.id)
|
||||
|
||||
if (!column) {
|
||||
throw new Error(
|
||||
@@ -246,14 +247,14 @@ function useMain(instance) {
|
||||
sortTypes[sortType] ||
|
||||
sortTypes.alphanumeric
|
||||
|
||||
// Return the correct sortFn
|
||||
return (a, b) =>
|
||||
sortMethod(a.values[sort.id], b.values[sort.id], sort.desc)
|
||||
// Return the correct sortFn.
|
||||
// This function should always return in ascending order
|
||||
return (a, b) => sortMethod(a, b, sort.id)
|
||||
}),
|
||||
// Map the directions
|
||||
availableSortBy.map(sort => {
|
||||
// Detect and use the sortInverted option
|
||||
const column = columns.find(d => d.id === sort.id)
|
||||
const column = flatColumns.find(d => d.id === sort.id)
|
||||
|
||||
if (column && column.sortInverted) {
|
||||
return sort.desc
|
||||
@@ -278,7 +279,15 @@ function useMain(instance) {
|
||||
console.timeEnd('getSortedRows')
|
||||
|
||||
return sortData(rows)
|
||||
}, [manualSorting, sortBy, debug, columns, rows, orderByFn, userSortTypes])
|
||||
}, [
|
||||
manualSorting,
|
||||
sortBy,
|
||||
debug,
|
||||
rows,
|
||||
flatColumns,
|
||||
orderByFn,
|
||||
userSortTypes,
|
||||
])
|
||||
|
||||
return {
|
||||
...instance,
|
||||
|
||||
+22
-5
@@ -3,7 +3,9 @@ const reSplitAlphaNumeric = /([0-9]+)/gm
|
||||
// Mixed sorting is slow, but very inclusive of many edge cases.
|
||||
// It handles numbers, mixed alphanumeric combinations, and even
|
||||
// null, undefined, and Infinity
|
||||
export const alphanumeric = (a, b) => {
|
||||
export const alphanumeric = (rowA, rowB, columnID) => {
|
||||
let a = getRowValueByColumnID(rowA, columnID)
|
||||
let b = getRowValueByColumnID(rowB, columnID)
|
||||
// Force to strings (or "" for unsupported types)
|
||||
a = toString(a)
|
||||
b = toString(b)
|
||||
@@ -51,18 +53,33 @@ export const alphanumeric = (a, b) => {
|
||||
return a.length - b.length
|
||||
}
|
||||
|
||||
export function datetime(a, b) {
|
||||
export function datetime(rowA, rowB, columnID) {
|
||||
let a = getRowValueByColumnID(rowA, columnID)
|
||||
let b = getRowValueByColumnID(rowB, columnID)
|
||||
|
||||
a = a.getTime()
|
||||
b = b.getTime()
|
||||
return numeric(a, b)
|
||||
|
||||
return compareBasic(a, b)
|
||||
}
|
||||
|
||||
export function numeric(a, b) {
|
||||
return a === b ? 0 : a > b ? 1 : -1
|
||||
export function basic(rowA, rowB, columnID) {
|
||||
let a = getRowValueByColumnID(rowA, columnID)
|
||||
let b = getRowValueByColumnID(rowB, columnID)
|
||||
|
||||
return compareBasic(a, b)
|
||||
}
|
||||
|
||||
// Utils
|
||||
|
||||
function compareBasic(a, b) {
|
||||
return a === b ? 0 : a > b ? 1 : -1
|
||||
}
|
||||
|
||||
function getRowValueByColumnID(row, columnID) {
|
||||
return row.values[columnID]
|
||||
}
|
||||
|
||||
function toString(a) {
|
||||
if (typeof a === 'number') {
|
||||
if (isNaN(a) || a === Infinity || a === -Infinity) {
|
||||
|
||||
+48
-19
@@ -1,5 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
const columnFallbacks = {
|
||||
Header: () => null,
|
||||
Cell: ({ cell: { value = '' } }) => value,
|
||||
show: true,
|
||||
}
|
||||
|
||||
// Find the depth of the columns
|
||||
export function findMaxDepth(columns, depth = 0) {
|
||||
return columns.reduce((prev, curr) => {
|
||||
@@ -38,9 +44,7 @@ export function decorateColumn(column, defaultColumn, parent, depth, index) {
|
||||
}
|
||||
|
||||
column = {
|
||||
Header: () => null,
|
||||
Cell: ({ cell: { value = '' } }) => value,
|
||||
show: true,
|
||||
...columnFallbacks,
|
||||
...column,
|
||||
id,
|
||||
accessor,
|
||||
@@ -69,28 +73,27 @@ export function decorateColumnTree(columns, defaultColumn, parent, depth = 0) {
|
||||
}
|
||||
|
||||
// Build the header groups from the bottom up
|
||||
export function makeHeaderGroups(columns, maxDepth, defaultColumn) {
|
||||
export function makeHeaderGroups(flatColumns, columns, defaultColumn) {
|
||||
const headerGroups = []
|
||||
|
||||
const removeChildColumns = column => {
|
||||
delete column.columns
|
||||
if (column.parent) {
|
||||
removeChildColumns(column.parent)
|
||||
}
|
||||
}
|
||||
columns.forEach(removeChildColumns)
|
||||
const maxDepth = findMaxDepth(columns)
|
||||
|
||||
const buildGroup = (columns, depth = 0) => {
|
||||
// Build each header group from the bottom up
|
||||
const buildGroup = (columns, depth) => {
|
||||
const headerGroup = {
|
||||
headers: [],
|
||||
}
|
||||
|
||||
const parentColumns = []
|
||||
|
||||
// Do any of these columns have parents?
|
||||
const hasParents = columns.some(col => col.parent)
|
||||
|
||||
columns.forEach(column => {
|
||||
// Are we the first column in this group?
|
||||
const isFirst = !parentColumns.length
|
||||
|
||||
// What is the latest (last) parent column?
|
||||
let latestParentColumn = [...parentColumns].reverse()[0]
|
||||
|
||||
// If the column has a parent, add it if necessary
|
||||
@@ -103,7 +106,7 @@ export function makeHeaderGroups(columns, maxDepth, defaultColumn) {
|
||||
})
|
||||
}
|
||||
} else if (hasParents) {
|
||||
// If other columns have parents, add a place holder if necessary
|
||||
// If other columns have parents, we'll need to add a place holder if necessary
|
||||
const placeholderColumn = decorateColumn(
|
||||
{
|
||||
originalID: [column.id, 'placeholder', maxDepth - depth].join('_'),
|
||||
@@ -124,30 +127,56 @@ export function makeHeaderGroups(columns, maxDepth, defaultColumn) {
|
||||
}
|
||||
}
|
||||
|
||||
// Establish the new columns[] relationship on the parent
|
||||
// Establish the new headers[] relationship on the parent
|
||||
if (column.parent || hasParents) {
|
||||
latestParentColumn = [...parentColumns].reverse()[0]
|
||||
latestParentColumn.columns = latestParentColumn.columns || []
|
||||
if (!latestParentColumn.columns.includes(column)) {
|
||||
latestParentColumn.columns.push(column)
|
||||
latestParentColumn.headers = latestParentColumn.headers || []
|
||||
if (!latestParentColumn.headers.includes(column)) {
|
||||
latestParentColumn.headers.push(column)
|
||||
}
|
||||
}
|
||||
|
||||
column.totalHeaderCount = column.headers
|
||||
? column.headers.reduce(
|
||||
(sum, header) => sum + header.totalHeaderCount,
|
||||
0
|
||||
)
|
||||
: 1 // Leaf node columns take up at least one count
|
||||
headerGroup.headers.push(column)
|
||||
})
|
||||
|
||||
headerGroups.push(headerGroup)
|
||||
|
||||
if (parentColumns.length) {
|
||||
buildGroup(parentColumns)
|
||||
buildGroup(parentColumns, depth + 1)
|
||||
}
|
||||
}
|
||||
|
||||
buildGroup(columns)
|
||||
buildGroup(flatColumns, 0)
|
||||
|
||||
return headerGroups.reverse()
|
||||
}
|
||||
|
||||
export function determineHeaderVisibility(instance) {
|
||||
const { headers } = instance
|
||||
|
||||
const handleColumn = (column, parentVisible) => {
|
||||
column.isVisible = parentVisible
|
||||
? typeof column.show === 'function'
|
||||
? column.show(instance)
|
||||
: !!column.show
|
||||
: false
|
||||
|
||||
if (column.headers && column.headers.length) {
|
||||
column.headers.forEach(subColumn =>
|
||||
handleColumn(subColumn, column.isVisible)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
headers.forEach(subHeader => handleColumn(subHeader, true))
|
||||
}
|
||||
|
||||
export function getBy(obj, path, def) {
|
||||
if (!path) {
|
||||
return obj
|
||||
|
||||
Reference in New Issue
Block a user