mirror of
https://github.com/gosticks/react-table.git
synced 2026-08-19 16:30:21 +00:00
Compare commits
36
Commits
v7.0.0-rc.16
...
v7.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5dd628144c | ||
|
|
0d0ce01544 | ||
|
|
afa21b0ad4 | ||
|
|
f42300d9e2 | ||
|
|
920420e330 | ||
|
|
6482869e8a | ||
|
|
42b41d1260 | ||
|
|
3aa016e056 | ||
|
|
bfcc08e7eb | ||
|
|
512f615988 | ||
|
|
63754a58dd | ||
|
|
4a483214ff | ||
|
|
b2af02407b | ||
|
|
b9f7bda223 | ||
|
|
acdb28df60 | ||
|
|
34be17b093 | ||
|
|
544c30b358 | ||
|
|
343e76ce65 | ||
|
|
23c9ad23fa | ||
|
|
9e1a3bfe99 | ||
|
|
6d4a733a88 | ||
|
|
c740a0c7af | ||
|
|
48d6fcd60d | ||
|
|
10b84faa29 | ||
|
|
292bbea1bf | ||
|
|
2a72667ec1 | ||
|
|
c92f3fb855 | ||
|
|
fe1c9b4b11 | ||
|
|
e28fa775f2 | ||
|
|
d9b2a9dc54 | ||
|
|
73ed7efbe6 | ||
|
|
7498059e98 | ||
|
|
a52a2e1265 | ||
|
|
facb216f5c | ||
|
|
b1a31b8176 | ||
|
|
5949932e77 |
@@ -1,27 +0,0 @@
|
||||
name: CI
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
pull_request:
|
||||
branches: [master]
|
||||
jobs:
|
||||
build-and-test:
|
||||
name: '${{ matrix.platform }}: node.js ${{ matrix.node-version }}'
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest, windows-latest, macos-latest]
|
||||
node-version: [10, 12]
|
||||
runs-on: ${{ matrix.platform }}
|
||||
env:
|
||||
CI: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@master
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@master
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- name: Build and test
|
||||
run: |
|
||||
yarn install
|
||||
yarn test
|
||||
@@ -1,3 +0,0 @@
|
||||
size-files:
|
||||
- sizes-cjs.json
|
||||
- sizes-es.json
|
||||
@@ -0,0 +1,15 @@
|
||||
name: Compressed Size
|
||||
|
||||
on: [pull_request]
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2-beta
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- uses: preactjs/compressed-size-action@v1
|
||||
with:
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
@@ -0,0 +1,26 @@
|
||||
name: react-table tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: 'node ${{ matrix.node }} ${{ matrix.os }} '
|
||||
runs-on: '${{ matrix.os }}'
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node: [12, 10]
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v2-beta
|
||||
with:
|
||||
fetch-depth: 1
|
||||
- run: npm i -g yarn
|
||||
- run: yarn --frozen-lockfile
|
||||
- run: yarn test:ci
|
||||
@@ -10,5 +10,6 @@ react-table.css
|
||||
.idea
|
||||
.DS_Store
|
||||
.history
|
||||
.vscode
|
||||
package-lock.json
|
||||
coverage
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
hooks: {
|
||||
'pre-commit': 'lint-staged && yarn test:ci',
|
||||
// 'commit-msg': 'commitlint -E HUSKY_GIT_PARAMS',
|
||||
},
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
{
|
||||
"dist/index.js": {
|
||||
"bundled": 130976,
|
||||
"minified": 61378,
|
||||
"gzipped": 15745
|
||||
},
|
||||
"dist/index.es.js": {
|
||||
"bundled": 130034,
|
||||
"minified": 60537,
|
||||
"gzipped": 15571,
|
||||
"treeshaked": {
|
||||
"rollup": {
|
||||
"code": 80,
|
||||
"import_statements": 21
|
||||
},
|
||||
"webpack": {
|
||||
"code": 7611
|
||||
}
|
||||
}
|
||||
},
|
||||
"dist\\index.js": {
|
||||
"bundled": 113237,
|
||||
"minified": 52528,
|
||||
"gzipped": 13837
|
||||
},
|
||||
"dist\\index.es.js": {
|
||||
"bundled": 112300,
|
||||
"minified": 51692,
|
||||
"gzipped": 13671,
|
||||
"treeshaked": {
|
||||
"rollup": {
|
||||
"code": 80,
|
||||
"import_statements": 21
|
||||
},
|
||||
"webpack": {
|
||||
"code": 8461
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
language: node_js
|
||||
|
||||
node_js:
|
||||
- 10
|
||||
- 12
|
||||
|
||||
os:
|
||||
- linux
|
||||
- osx
|
||||
- windows
|
||||
|
||||
env:
|
||||
global:
|
||||
- YARN_GPG=no
|
||||
|
||||
cache: yarn
|
||||
|
||||
script: yarn test
|
||||
Vendored
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"workbench.colorCustomizations": {
|
||||
"titleBar.inactiveBackground": "#ff4154", // change this color!
|
||||
"titleBar.activeBackground": "#ff4154", // change this color!
|
||||
"titleBar.inactiveForeground": "#ffffff", // change this color!
|
||||
"titleBar.activeForeground": "#ffffff" // change this color!
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,15 @@
|
||||
## 7.0.0 🎉
|
||||
|
||||
- Fixed an issue where page options array could be empty
|
||||
- Fixed an issue where duplicate columns would be silently deduped. There is now a warning when duplicate columns are found based on their IDs
|
||||
- Moved some functions around so they will get treeshaked with their respective plugins that use them.
|
||||
- Fixed an issue where filters, sorting, or grouping changes would not reset pagination
|
||||
- Added table and column level options for disabling global filters
|
||||
- Fixed an issue where row selection was not deselecting rows
|
||||
- Fixed an issue where flex table rendering was not giving the table a minimum width with necessary
|
||||
- Fixed an issue where row selection would not work when using other row-transformative plugins like filters or grouping
|
||||
- Fixed an issue where header groups were not memoized correctly
|
||||
|
||||
## 7.0.0-rc.16
|
||||
|
||||
- Moved away from snapshot tests. No more testing implementation details.
|
||||
|
||||
@@ -4,16 +4,21 @@
|
||||
|
||||
Hooks for building **lightweight, fast and extendable datagrids** for React
|
||||
|
||||
[](https://twitter.com/intent/tweet?button_hashtag=TanStack)
|
||||
|
||||
<!-- [](https://github.com/tannerlinsley/react-table/actions) -->
|
||||
|
||||
[](https://travis-ci.org/tannerlinsley/react-table)
|
||||
[](https://bundlephobia.com/result?p=react-table@next)
|
||||
[](https://npmjs.com/package/react-table)
|
||||
[](https://spectrum.chat/react-table)
|
||||
[](https://github.com/tannerlinsley/react-table)
|
||||
[](https://twitter.com/tannerlinsley)
|
||||
<a href="https://twitter.com/intent/tweet?button_hashtag=TanStack" target="\_parent">
|
||||
<img alt="#TanStack" src="https://img.shields.io/twitter/url?color=%2308a0e9&label=%23TanStack&style=social&url=https%3A%2F%2Ftwitter.com%2Fintent%2Ftweet%3Fbutton_hashtag%3DTanStack">
|
||||
</a><a href="https://github.com/tannerlinsley/react-table/actions?table=workflow%3A%22react-table+tests%22">
|
||||
<img src="https://github.com/tannerlinsley/react-table/workflows/react-table%20tests/badge.svg" />
|
||||
</a><a href="https://npmjs.com/package/react-table" target="\_parent">
|
||||
<img alt="" src="https://img.shields.io/npm/dm/react-table.svg" />
|
||||
</a><a href="https://bundlephobia.com/result?p=react-table@latest" target="\_parent">
|
||||
<img alt="" src="https://badgen.net/bundlephobia/minzip/react-table@latest" />
|
||||
</a><a href="https://spectrum.chat/react-table">
|
||||
<img alt="Join the community on Spectrum" src="https://withspectrum.github.io/badge/badge.svg" />
|
||||
</a><a href="https://github.com/tannerlinsley/react-table" target="\_parent">
|
||||
<img alt="" src="https://img.shields.io/github/stars/tannerlinsley/react-table.svg?style=social&label=Star" />
|
||||
</a><a href="https://twitter.com/tannerlinsley" target="\_parent">
|
||||
<img alt="" src="https://img.shields.io/twitter/follow/tannerlinsley.svg?style=social&label=Follow" />
|
||||
</a>
|
||||
|
||||
Enjoy this library? Try them all! [React Query](https://github.com/tannerlinsley/react-query), [React Form](https://github.com/tannerlinsley/react-form), [React Charts](https://github.com/tannerlinsley/react-charts)
|
||||
|
||||
@@ -119,6 +124,11 @@ This library is being built and maintained by me, @tannerlinsley and I am always
|
||||
<a href="https://github.com/sponsors/tannerlinsley/" target="_blank">
|
||||
<img width='150' src="https://raw.githubusercontent.com/tannerlinsley/files/master/images/patreon/silver.png">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://tripwire.com" target="_blank">
|
||||
<img width='225' src="https://www.tripwire.com/-/media/tripwiredotcom/icons/tripwire-logo-footer.svg">
|
||||
</a>
|
||||
</td>
|
||||
<td align="center" valign="middle">
|
||||
<a href="https://github.com/sponsors/tannerlinsley" target="_blank">
|
||||
@@ -163,16 +173,18 @@ This library is being built and maintained by me, @tannerlinsley and I am always
|
||||
</td>
|
||||
<td>
|
||||
<ul>
|
||||
<li>Hugo Meissner</li>
|
||||
<li>Benoit Leger-Derville</li>
|
||||
<li>Thomas Funk</li>
|
||||
<li>David Pickut</li>
|
||||
<li>Jordan Soltman</li>
|
||||
<li>Robert Tajnšek</li>
|
||||
<li>Eric Lanehart (@pushred)</li>
|
||||
<li>Anish P Patel (@anishpatelyaadada)</li>
|
||||
<li>Alin Porumb (@alinporumb)</li>
|
||||
<li>Janus Reith (@janus-reith)</li>
|
||||
<li>Hugo Meissner</li>
|
||||
<li>Benoit Leger-Derville</li>
|
||||
<li>Thomas Funk</li>
|
||||
<li>David Pickut</li>
|
||||
<li>Jordan Soltman</li>
|
||||
<li>Robert Tajnšek</li>
|
||||
<li>Eric Lanehart (@pushred)</li>
|
||||
<li>Anish P Patel (@anishpatelyaadada)</li>
|
||||
<li>Alin Porumb (@alinporumb)</li>
|
||||
<li>Janus Reith (@janus-reith)</li>
|
||||
<li>Timo Mämecke (@timomeh)</li>
|
||||
<li>Tyler Reiff (@reiff12)</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
module.exports = { extends: ['@commitlint/config-conventional'] }
|
||||
@@ -23,6 +23,8 @@ The following options are supported via the main options object passed to `useTa
|
||||
- `manualGlobalFilter: 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)
|
||||
- `disableGlobalFilter: Bool`
|
||||
- Disables global filtering for every column in the entire table.
|
||||
- `filterTypes: Object<filterKey: filterType>`
|
||||
- Must be **memoized**
|
||||
- Allows overriding or adding additional filter types for the table to use. If the globalFilter type isn't found on this object, it will default to using the built-in filter types.
|
||||
@@ -34,6 +36,14 @@ The following options are supported via the main options object passed to `useTa
|
||||
- To disable, set to `false`
|
||||
- For more information see the FAQ ["How do I stop my table state from automatically resetting when my data changes?"](./faq#how-do-i-stop-my-table-state-from-automatically-resetting-when-my-data-changes)
|
||||
|
||||
### Column Options
|
||||
|
||||
The following options are supported on any `Column` object passed to the `columns` option in `useTable()`
|
||||
|
||||
- `disableGlobalFilter: Bool`
|
||||
- Optional
|
||||
- If set to `true`, will disable global filtering for this column
|
||||
|
||||
### Instance Properties
|
||||
|
||||
The following values are provided to the table `instance`:
|
||||
|
||||
@@ -34,7 +34,7 @@ The following options are supported via the main options object passed to `useTa
|
||||
- `manualFilters` is `false` and `state.filters` is changed
|
||||
- `manualGroupBy` is `false` and `state.groupBy` is changed
|
||||
- To disable, set to `false`
|
||||
- For more information see the FAQ ["How do I stop my table state from automatically resetting when my data changes?"](./faq#how-do-i-stop-my-table-state-from-automatically-resetting-when-my-data-changes)
|
||||
- For more information see the FAQ ["How do I stop my table state from automatically resetting when my data changes?"](../faq.md#how-do-i-stop-my-table-state-from-automatically-resetting-when-my-data-changes)
|
||||
- `paginateExpandedRows: Bool`
|
||||
- Optional
|
||||
- Only applies when using the `useExpanded` plugin hook simultaneously
|
||||
|
||||
@@ -58,6 +58,13 @@ The following additional properties are available on every **prepared** `row` ob
|
||||
- `toggleRowSelected: Function(?set)`
|
||||
- Use this function to toggle this row's selected state.
|
||||
- Optionally pass `true` or `false` to set it to that state
|
||||
- `getToggleRowSelectedProps: Function(props) => props`
|
||||
- Use this function to get the props needed for a **select row checkbox**.
|
||||
- Props:
|
||||
- `onChange: Function()`
|
||||
- `style.cursor: 'pointer'`
|
||||
- `checked: Bool`
|
||||
- `title: 'Toggle Row Selected'`
|
||||
|
||||
### Example
|
||||
|
||||
|
||||
@@ -25,6 +25,11 @@ The following options are supported via the main options object passed to `useTa
|
||||
- Optional
|
||||
- The initial state object for hidden columns
|
||||
- If a column's ID is contained in this array, it will be hidden
|
||||
- `autoResetHiddenColumns: Boolean`
|
||||
- Defaults to `true`
|
||||
- When `true`, the `hiddenColumns` state will automatically reset if any of the following conditions are met:
|
||||
- `columns` is changed
|
||||
- To disable, set to `false`
|
||||
- `stateReducer: Function(newState, action, prevState) => newState`
|
||||
- Optional
|
||||
- With every action that is dispatched to the table's internal `React.useReducer` instance, this reducer is called and is allowed to modify the final state object for updating.
|
||||
@@ -73,6 +78,7 @@ The following options are supported on any column object you can pass to `column
|
||||
- The data returned by an accessor should be **primitive** and sortable.
|
||||
- If a string is passed, the column's value will be looked up on the original row via that key, eg. If your column's accessor is `firstName` then its value would be read from `row['firstName']`. You can also specify deeply nested values with accessors like `info.hobbies` or even `address[0].street`
|
||||
- If a function is passed, the column's value will be looked up on the original row using this accessor function, eg. If your column's accessor is `row => row.firstName`, then its value would be determined by passing the row to this function and using the resulting value.
|
||||
- Technically speaking, this field isn't required if you have a unique `id` for a column. This is used for things like expander or row selection columns. **Warning**: Only omit `accessor` if you really know what you're doing.
|
||||
- `id: String`
|
||||
- **Required if `accessor` is a function**
|
||||
- This is the unique ID for the column. It is used by reference in things like sorting, grouping, filtering etc.
|
||||
|
||||
@@ -11,13 +11,12 @@ import makeData from './makeData'
|
||||
|
||||
const Styles = styled.div`
|
||||
padding: 1rem;
|
||||
${'' /* These styles are suggested for the table fill all available space in its containing element */}
|
||||
display: block;
|
||||
${'' /* These styles are required for a horizontaly scrollable table overflow */}
|
||||
overflow: auto;
|
||||
|
||||
.table {
|
||||
${'' /* These styles are suggested for the table fill all available space in its containing element */}
|
||||
display: block;
|
||||
${'' /* These styles are required for a horizontaly scrollable table overflow */}
|
||||
overflow: auto;
|
||||
|
||||
border-spacing: 0;
|
||||
border: 1px solid black;
|
||||
|
||||
@@ -119,13 +118,7 @@ function Table({ columns, data }) {
|
||||
[]
|
||||
)
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
} = useTable(
|
||||
const { getTableProps, headerGroups, rows, prepareRow } = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
@@ -195,8 +188,8 @@ function Table({ columns, data }) {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div {...getTableBodyProps()} className="tbody">
|
||||
{rows.map((row, i) => {
|
||||
<div className="tbody">
|
||||
{rows.map(row => {
|
||||
prepareRow(row)
|
||||
return (
|
||||
<div {...row.getRowProps()} className="tr">
|
||||
|
||||
@@ -301,8 +301,8 @@ function Table({ columns, data, updateMyData, skipPageReset }) {
|
||||
// when we edit the data, undefined means using the default
|
||||
autoResetPage: !skipPageReset,
|
||||
},
|
||||
useGroupBy,
|
||||
useFilters,
|
||||
useGroupBy,
|
||||
useSortBy,
|
||||
useExpanded,
|
||||
usePagination,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
"react-scripts": "3.0.1",
|
||||
"react-table": "latest",
|
||||
"styled-components": "^4.3.2",
|
||||
"react-dnd": "^9.4.0",
|
||||
"react-dnd": "^10.0.2",
|
||||
"react-dnd-html5-backend": "^9.4.0",
|
||||
"immutability-helper": "^3.0.1"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
if (process.env.NODE_ENV === 'production') {
|
||||
module.exports = require('./dist/react-table.production.min.js')
|
||||
} else {
|
||||
module.exports = require('./dist/react-table.development.js')
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
module.exports = {
|
||||
'*.{md,js,ts,jsx,tsx}': ['prettier --write --no-editorconfig', 'git add'],
|
||||
}
|
||||
+7
-20
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "react-table",
|
||||
"version": "7.0.0-rc.16",
|
||||
"version": "7.0.0",
|
||||
"description": "Hooks for building lightweight, fast and extendable datagrids for React",
|
||||
"license": "MIT",
|
||||
"homepage": "https://github.com/tannerlinsley/react-table#readme",
|
||||
@@ -14,10 +14,8 @@
|
||||
"react-table",
|
||||
"datagrid"
|
||||
],
|
||||
"main": "dist/index.js",
|
||||
"module": "dist/index.es.js",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"commit": "git add . && git-cz",
|
||||
"test": "is-ci \"test:ci\" \"test:dev\"",
|
||||
"test:dev": "jest --watch",
|
||||
"test:ci": "yarn test:jest",
|
||||
@@ -39,27 +37,25 @@
|
||||
"package.json",
|
||||
"README.md"
|
||||
],
|
||||
"browserslist": "> 0.25%, not dead",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.3",
|
||||
"react-dom": "^16.8.3"
|
||||
"react": "^16.8.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.7.2",
|
||||
"@babel/plugin-transform-runtime": "^7.8.3",
|
||||
"@babel/preset-env": "^7.7.1",
|
||||
"@babel/preset-react": "^7.7.0",
|
||||
"@babel/runtime": "^7.7.2",
|
||||
"@commitlint/cli": "^8.2.0",
|
||||
"@commitlint/config-conventional": "^8.2.0",
|
||||
"@svgr/rollup": "^4.3.3",
|
||||
"@testing-library/dom": "^6.10.1",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.3.2",
|
||||
"@testing-library/react-hooks": "^3.2.1",
|
||||
"babel-eslint": "9.x",
|
||||
"babel-jest": "^24.9.0",
|
||||
"commitizen": "^4.0.3",
|
||||
"core-js": "3.4.1",
|
||||
"cross-env": "^6.0.3",
|
||||
"dtslint": "^0.9.8",
|
||||
"eslint": "6.6.0",
|
||||
"eslint-config-prettier": "^6.7.0",
|
||||
"eslint-config-react-app": "^5.0.2",
|
||||
@@ -74,7 +70,6 @@
|
||||
"eslint-plugin-react": "7.16.0",
|
||||
"eslint-plugin-react-hooks": "2.3.0",
|
||||
"eslint-plugin-standard": "^4.0.1",
|
||||
"husky": "^3.1.0",
|
||||
"is-ci-cli": "^2.0.0",
|
||||
"jest": "^24.9.0",
|
||||
"jest-cli": "^24.9.0",
|
||||
@@ -87,6 +82,7 @@
|
||||
"prop-types": "^15.5.0",
|
||||
"react": "^16.12.0",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-test-renderer": "^16.13.0",
|
||||
"rollup": "^1.27.3",
|
||||
"rollup-plugin-babel": "^4.3.3",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
@@ -96,14 +92,5 @@
|
||||
"rollup-plugin-size-snapshot": "^0.10.0",
|
||||
"rollup-plugin-terser": "^5.1.2",
|
||||
"serve": "^11.2.0"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "node_modules/cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"browserslist": "> 0.25%, not dead",
|
||||
"dependencies": {
|
||||
"@babel/plugin-transform-runtime": "^7.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
+22
-28
@@ -1,50 +1,44 @@
|
||||
import babel from 'rollup-plugin-babel'
|
||||
import commonjs from 'rollup-plugin-commonjs'
|
||||
import external from 'rollup-plugin-peer-deps-external'
|
||||
import resolve from 'rollup-plugin-node-resolve'
|
||||
import { sizeSnapshot } from 'rollup-plugin-size-snapshot'
|
||||
import { terser } from 'rollup-plugin-terser'
|
||||
import size from 'rollup-plugin-size'
|
||||
import pkg from './package.json'
|
||||
import externalDeps from 'rollup-plugin-peer-deps-external'
|
||||
|
||||
const external = ['react']
|
||||
|
||||
const globals = {
|
||||
react: 'React',
|
||||
}
|
||||
|
||||
export default [
|
||||
{
|
||||
input: 'src/index.js',
|
||||
output: {
|
||||
file: pkg.main,
|
||||
format: 'cjs',
|
||||
name: 'ReactQuery',
|
||||
file: 'dist/react-table.development.js',
|
||||
format: 'umd',
|
||||
sourcemap: true,
|
||||
globals,
|
||||
},
|
||||
plugins: [
|
||||
external(),
|
||||
babel(),
|
||||
resolve(),
|
||||
commonjs(),
|
||||
size({
|
||||
publish: true,
|
||||
exclude: pkg.module,
|
||||
filename: 'sizes-cjs.json',
|
||||
writeFile: process.env.CI ? true : false,
|
||||
}),
|
||||
sizeSnapshot(),
|
||||
],
|
||||
external,
|
||||
plugins: [babel(), externalDeps()],
|
||||
},
|
||||
{
|
||||
input: 'src/index.js',
|
||||
output: {
|
||||
file: pkg.module,
|
||||
format: 'es',
|
||||
name: 'ReactQuery',
|
||||
file: 'dist/react-table.production.min.js',
|
||||
format: 'umd',
|
||||
sourcemap: true,
|
||||
globals,
|
||||
},
|
||||
external,
|
||||
plugins: [
|
||||
external(),
|
||||
babel(),
|
||||
externalDeps(),
|
||||
terser(),
|
||||
size({
|
||||
publish: true,
|
||||
exclude: pkg.main,
|
||||
filename: 'sizes-es.json',
|
||||
writeFile: process.env.CI ? true : false,
|
||||
writeFile: false,
|
||||
}),
|
||||
sizeSnapshot(),
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1581795972580,
|
||||
"files": [
|
||||
{
|
||||
"filename": "index.js",
|
||||
"size": 26248,
|
||||
"delta": 414
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,12 +0,0 @@
|
||||
[
|
||||
{
|
||||
"timestamp": 1581795982924,
|
||||
"files": [
|
||||
{
|
||||
"filename": "index.es.js",
|
||||
"size": 26040,
|
||||
"delta": 407
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
functionalUpdate,
|
||||
useGetLatest,
|
||||
makePropGetter,
|
||||
useMountedLayoutEffect,
|
||||
} from '../publicUtils'
|
||||
|
||||
actions.resetHiddenColumns = 'resetHiddenColumns'
|
||||
@@ -147,11 +148,13 @@ function useInstanceBeforeDimensions(instance) {
|
||||
|
||||
function useInstance(instance) {
|
||||
const {
|
||||
columns,
|
||||
flatHeaders,
|
||||
dispatch,
|
||||
allColumns,
|
||||
getHooks,
|
||||
state: { hiddenColumns },
|
||||
autoResetHiddenColumns = true,
|
||||
} = instance
|
||||
|
||||
const getInstance = useGetLatest(instance)
|
||||
@@ -197,6 +200,14 @@ function useInstance(instance) {
|
||||
)
|
||||
})
|
||||
|
||||
const getAutoResetHiddenColumns = useGetLatest(autoResetHiddenColumns)
|
||||
|
||||
useMountedLayoutEffect(() => {
|
||||
if (getAutoResetHiddenColumns()) {
|
||||
dispatch({ type: actions.resetHiddenColumns })
|
||||
}
|
||||
}, [dispatch, columns])
|
||||
|
||||
Object.assign(instance, {
|
||||
allColumnsHidden,
|
||||
toggleHideColumn,
|
||||
|
||||
+63
-91
@@ -9,7 +9,6 @@ import {
|
||||
unpreparedAccessWarning,
|
||||
makeHeaderGroups,
|
||||
decorateColumn,
|
||||
dedupeBy,
|
||||
} from '../utils'
|
||||
|
||||
import {
|
||||
@@ -184,7 +183,7 @@ export const useTable = (props, ...plugins) => {
|
||||
getInstance().allColumns = allColumns
|
||||
|
||||
// Access the row model using initial columns
|
||||
const coreDataModel = React.useMemo(() => {
|
||||
const [rows, flatRows, rowsById] = React.useMemo(() => {
|
||||
let rows = []
|
||||
let flatRows = []
|
||||
const rowsById = {}
|
||||
@@ -206,69 +205,18 @@ export const useTable = (props, ...plugins) => {
|
||||
})
|
||||
}
|
||||
|
||||
return { rows, flatRows, rowsById }
|
||||
return [rows, flatRows, rowsById]
|
||||
}, [allColumns, data, getRowId, getSubRows, getHooks, getInstance])
|
||||
|
||||
// Allow materialized columns to also access data
|
||||
const [rows, flatRows, rowsById, materializedColumns] = React.useMemo(() => {
|
||||
const { rows, flatRows, rowsById } = coreDataModel
|
||||
const materializedColumns = reduceHooks(
|
||||
getHooks().materializedColumns,
|
||||
[],
|
||||
{
|
||||
instance: getInstance(),
|
||||
}
|
||||
)
|
||||
|
||||
materializedColumns.forEach(d => assignColumnAccessor(d))
|
||||
|
||||
const materializedColumnsQueue = [...materializedColumns]
|
||||
|
||||
while (materializedColumnsQueue.length) {
|
||||
const column = materializedColumnsQueue.shift()
|
||||
accessRowsForColumn({
|
||||
data,
|
||||
rows,
|
||||
flatRows,
|
||||
rowsById,
|
||||
column,
|
||||
getRowId,
|
||||
getSubRows,
|
||||
accessValueHooks: getHooks().accessValue,
|
||||
getInstance,
|
||||
})
|
||||
}
|
||||
|
||||
return [rows, flatRows, rowsById, materializedColumns]
|
||||
}, [
|
||||
coreDataModel,
|
||||
getHooks,
|
||||
getInstance,
|
||||
data,
|
||||
getRowId,
|
||||
getSubRows,
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
...reduceHooks(getHooks().materializedColumnsDeps, [], {
|
||||
instance: getInstance(),
|
||||
}),
|
||||
])
|
||||
|
||||
Object.assign(getInstance(), {
|
||||
rows,
|
||||
flatRows,
|
||||
rowsById,
|
||||
materializedColumns,
|
||||
// materializedColumns,
|
||||
})
|
||||
|
||||
loopHooks(getHooks().useInstanceAfterData, getInstance())
|
||||
|
||||
// Combine new materialized columns with all columns (dedupe prefers later columns)
|
||||
allColumns = React.useMemo(
|
||||
() => dedupeBy([...allColumns, ...materializedColumns], d => d.id),
|
||||
[allColumns, materializedColumns]
|
||||
)
|
||||
getInstance().allColumns = allColumns
|
||||
|
||||
// Get the flat list of all columns AFTER the rows
|
||||
// have been access, and allow hooks to decorate
|
||||
// those columns (and trigger this memoization via deps)
|
||||
@@ -290,12 +238,34 @@ export const useTable = (props, ...plugins) => {
|
||||
)
|
||||
|
||||
// Combine new visible columns with all columns (dedupe prefers later columns)
|
||||
allColumns = React.useMemo(
|
||||
() => dedupeBy([...allColumns, ...visibleColumns], d => d.id),
|
||||
[allColumns, visibleColumns]
|
||||
)
|
||||
allColumns = React.useMemo(() => {
|
||||
const columns = [...allColumns]
|
||||
|
||||
visibleColumns.forEach(column => {
|
||||
if (!columns.find(d => d.id === column.id)) {
|
||||
columns.push(column)
|
||||
}
|
||||
})
|
||||
|
||||
return columns
|
||||
}, [allColumns, visibleColumns])
|
||||
getInstance().allColumns = allColumns
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
const duplicateColumns = allColumns.filter((column, i) => {
|
||||
return allColumns.findIndex(d => d.id === column.id) !== i
|
||||
})
|
||||
|
||||
if (duplicateColumns.length) {
|
||||
console.info(allColumns)
|
||||
throw new Error(
|
||||
`Duplicate columns were found with ids: "${duplicateColumns
|
||||
.map(d => d.id)
|
||||
.join(', ')}" in the columns array above`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Make the headerGroups
|
||||
const headerGroups = React.useMemo(
|
||||
() =>
|
||||
@@ -380,40 +350,42 @@ export const useTable = (props, ...plugins) => {
|
||||
}
|
||||
)
|
||||
|
||||
getInstance().headerGroups = getInstance().headerGroups.filter(
|
||||
(headerGroup, i) => {
|
||||
// Filter out any headers and headerGroups that don't have visible columns
|
||||
headerGroup.headers = headerGroup.headers.filter(column => {
|
||||
const recurse = headers =>
|
||||
headers.filter(column => {
|
||||
if (column.headers) {
|
||||
return recurse(column.headers)
|
||||
}
|
||||
return column.isVisible
|
||||
}).length
|
||||
if (column.headers) {
|
||||
return recurse(column.headers)
|
||||
getInstance().headerGroups = React.useMemo(
|
||||
() =>
|
||||
headerGroups.filter((headerGroup, i) => {
|
||||
// Filter out any headers and headerGroups that don't have visible columns
|
||||
headerGroup.headers = headerGroup.headers.filter(column => {
|
||||
const recurse = headers =>
|
||||
headers.filter(column => {
|
||||
if (column.headers) {
|
||||
return recurse(column.headers)
|
||||
}
|
||||
return column.isVisible
|
||||
}).length
|
||||
if (column.headers) {
|
||||
return recurse(column.headers)
|
||||
}
|
||||
return column.isVisible
|
||||
})
|
||||
|
||||
// Give headerGroups getRowProps
|
||||
if (headerGroup.headers.length) {
|
||||
headerGroup.getHeaderGroupProps = makePropGetter(
|
||||
getHooks().getHeaderGroupProps,
|
||||
{ instance: getInstance(), headerGroup, index: i }
|
||||
)
|
||||
|
||||
headerGroup.getFooterGroupProps = makePropGetter(
|
||||
getHooks().getFooterGroupProps,
|
||||
{ instance: getInstance(), headerGroup, index: i }
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
return column.isVisible
|
||||
})
|
||||
|
||||
// Give headerGroups getRowProps
|
||||
if (headerGroup.headers.length) {
|
||||
headerGroup.getHeaderGroupProps = makePropGetter(
|
||||
getHooks().getHeaderGroupProps,
|
||||
{ instance: getInstance(), headerGroup, index: i }
|
||||
)
|
||||
|
||||
headerGroup.getFooterGroupProps = makePropGetter(
|
||||
getHooks().getFooterGroupProps,
|
||||
{ instance: getInstance(), headerGroup, index: i }
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}),
|
||||
[headerGroups, getInstance, getHooks]
|
||||
)
|
||||
|
||||
getInstance().footerGroups = [...getInstance().headerGroups].reverse()
|
||||
|
||||
+2
-2
@@ -3,8 +3,8 @@ export { useTable } from './hooks/useTable'
|
||||
export { useExpanded } from './plugin-hooks/useExpanded'
|
||||
export { useFilters } from './plugin-hooks/useFilters'
|
||||
export { useGlobalFilter } from './plugin-hooks/useGlobalFilter'
|
||||
export { useGroupBy } from './plugin-hooks/useGroupBy'
|
||||
export { useSortBy } from './plugin-hooks/useSortBy'
|
||||
export { useGroupBy, defaultGroupByFn } from './plugin-hooks/useGroupBy'
|
||||
export { useSortBy, defaultOrderByFn } from './plugin-hooks/useSortBy'
|
||||
export { usePagination } from './plugin-hooks/usePagination'
|
||||
export { _UNSTABLE_usePivotColumns } from './plugin-hooks/_UNSTABLE_usePivotColumns'
|
||||
export { useRowSelect } from './plugin-hooks/useRowSelect'
|
||||
|
||||
@@ -52,10 +52,14 @@ const defaultColumn = {
|
||||
),
|
||||
}
|
||||
|
||||
function App() {
|
||||
function App(props) {
|
||||
const [data, setData] = React.useState(makeData)
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
|
||||
const columns = React.useMemo(() => {
|
||||
if (props.columns) {
|
||||
return props.columns
|
||||
}
|
||||
return [
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
@@ -90,9 +94,8 @@ function App() {
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
]
|
||||
}, [props.columns])
|
||||
|
||||
const {
|
||||
getTableProps,
|
||||
@@ -177,6 +180,8 @@ test('renders a filterable table', async () => {
|
||||
const globalFilterInput = rendered.getByPlaceholderText('Global search...')
|
||||
const filterInputs = rendered.getAllByPlaceholderText('Search...')
|
||||
|
||||
expect(filterInputs).toHaveLength(6)
|
||||
|
||||
fireEvent.change(filterInputs[1], { target: { value: 'l' } })
|
||||
expect(
|
||||
rendered
|
||||
@@ -243,3 +248,146 @@ test('renders a filterable table', async () => {
|
||||
'firstName: jaylen',
|
||||
])
|
||||
})
|
||||
|
||||
test('does not filter columns marked as disableFilters', () => {
|
||||
const columns = [
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
disableFilters: true,
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
disableFilters: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Header: 'Info',
|
||||
columns: [
|
||||
{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
},
|
||||
{
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
const rendered = render(<App columns={columns} />)
|
||||
|
||||
const filterInputs = rendered.getAllByPlaceholderText('Search...')
|
||||
|
||||
expect(filterInputs).toHaveLength(4)
|
||||
|
||||
// should be Age column
|
||||
fireEvent.change(filterInputs[0], { target: { value: '45' } })
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(3)
|
||||
.map(row => Array.from(row.children)[0].textContent)
|
||||
).toEqual(['firstName: joe'])
|
||||
|
||||
fireEvent.change(filterInputs[0], { target: { value: '' } })
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(3)
|
||||
.map(row => Array.from(row.children)[0].textContent)
|
||||
).toEqual([
|
||||
'firstName: tanner',
|
||||
'firstName: derek',
|
||||
'firstName: joe',
|
||||
'firstName: jaylen',
|
||||
])
|
||||
})
|
||||
|
||||
test('does not filter columns with GlobalFilter if marked disableGlobalFilter', () => {
|
||||
const columns = [
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
disableGlobalFilter: true,
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
disableGlobalFilter: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Header: 'Info',
|
||||
columns: [
|
||||
{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
},
|
||||
{
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
const rendered = render(<App columns={columns} />)
|
||||
|
||||
const globalFilterInput = rendered.getByPlaceholderText('Global search...')
|
||||
|
||||
fireEvent.change(globalFilterInput, { target: { value: '' } })
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(3)
|
||||
.map(row => Array.from(row.children)[0].textContent)
|
||||
).toEqual([
|
||||
'firstName: tanner',
|
||||
'firstName: derek',
|
||||
'firstName: joe',
|
||||
'firstName: jaylen',
|
||||
])
|
||||
|
||||
// global filter shouldn't apply to firstName or lastName
|
||||
fireEvent.change(globalFilterInput, { target: { value: 'li' } })
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(3)
|
||||
.map(row => Array.from(row.children)[0].textContent)
|
||||
).toEqual(['firstName: joe'])
|
||||
|
||||
// double check global filter ignore (should ignore joe bergevin)
|
||||
fireEvent.change(globalFilterInput, { target: { value: 'in' } })
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(3)
|
||||
.map(row => Array.from(row.children)[0].textContent)
|
||||
).toEqual(['firstName: tanner', 'firstName: derek', 'firstName: jaylen'])
|
||||
})
|
||||
|
||||
@@ -0,0 +1,258 @@
|
||||
import React from 'react'
|
||||
import { render, fireEvent } from '@testing-library/react'
|
||||
import { useTable } from '../../hooks/useTable'
|
||||
import { useRowSelect } from '../useRowSelect'
|
||||
import { useFilters } from '../useFilters'
|
||||
|
||||
const dataPiece = [
|
||||
{
|
||||
firstName: 'tanner',
|
||||
lastName: 'linsley',
|
||||
age: 29,
|
||||
visits: 100,
|
||||
status: 'In Relationship',
|
||||
progress: 50,
|
||||
},
|
||||
{
|
||||
firstName: 'derek',
|
||||
lastName: 'perkins',
|
||||
age: 40,
|
||||
visits: 40,
|
||||
status: 'Single',
|
||||
progress: 80,
|
||||
},
|
||||
{
|
||||
firstName: 'jaylen',
|
||||
lastName: 'linsley',
|
||||
age: 26,
|
||||
visits: 99,
|
||||
status: 'In Relationship',
|
||||
progress: 70,
|
||||
},
|
||||
]
|
||||
|
||||
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 {
|
||||
getTableProps,
|
||||
getTableBodyProps,
|
||||
headerGroups,
|
||||
rows,
|
||||
prepareRow,
|
||||
state,
|
||||
} = useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
defaultColumn,
|
||||
},
|
||||
useFilters,
|
||||
useRowSelect
|
||||
)
|
||||
|
||||
// Render the UI for your table
|
||||
return (
|
||||
<>
|
||||
<table {...getTableProps()}>
|
||||
<thead>
|
||||
{headerGroups.map(headerGroup => (
|
||||
<tr {...headerGroup.getHeaderGroupProps()}>
|
||||
{headerGroup.headers.map(column => (
|
||||
<th {...column.getHeaderProps()}>
|
||||
{column.render('Header')}
|
||||
{column.canFilter ? column.render('Filter') : null}
|
||||
</th>
|
||||
))}
|
||||
</tr>
|
||||
))}
|
||||
</thead>
|
||||
<tbody {...getTableBodyProps()}>
|
||||
{rows.map(
|
||||
(row, i) =>
|
||||
prepareRow(row) || (
|
||||
<tr {...row.getRowProps()}>
|
||||
{row.cells.map(cell => {
|
||||
return (
|
||||
<td {...cell.getCellProps()}>{cell.render('Cell')}</td>
|
||||
)
|
||||
})}
|
||||
</tr>
|
||||
)
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
<p>
|
||||
Selected Rows:{' '}
|
||||
<span data-testid="selected-count">
|
||||
{Object.keys(state.selectedRowIds).length}
|
||||
</span>
|
||||
</p>
|
||||
<pre>
|
||||
<code>
|
||||
{JSON.stringify({ selectedRowIds: state.selectedRowIds }, null, 2)}
|
||||
</code>
|
||||
</pre>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const defaultColumn = {
|
||||
Cell: ({ cell: { value }, column: { id } }) => `${id}: ${value}`,
|
||||
Filter: ({ column: { filterValue, setFilter } }) => (
|
||||
<input
|
||||
value={filterValue || ''}
|
||||
onChange={e => {
|
||||
setFilter(e.target.value || undefined) // Set undefined to remove the filter entirely
|
||||
}}
|
||||
placeholder="Search..."
|
||||
/>
|
||||
),
|
||||
}
|
||||
|
||||
const IndeterminateCheckbox = React.forwardRef(
|
||||
({ indeterminate, ...rest }, ref) => {
|
||||
const defaultRef = React.useRef()
|
||||
const resolvedRef = ref || defaultRef
|
||||
|
||||
React.useEffect(() => {
|
||||
resolvedRef.current.indeterminate = indeterminate
|
||||
}, [resolvedRef, indeterminate])
|
||||
|
||||
return <input type="checkbox" ref={resolvedRef} {...rest} />
|
||||
}
|
||||
)
|
||||
|
||||
function App() {
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
id: 'selection',
|
||||
// The header can use the table's getToggleAllRowsSelectedProps method
|
||||
// to render a checkbox
|
||||
Header: ({ getToggleAllRowsSelectedProps }) => (
|
||||
<div>
|
||||
<label>
|
||||
<IndeterminateCheckbox {...getToggleAllRowsSelectedProps()} />{' '}
|
||||
Select All
|
||||
</label>
|
||||
</div>
|
||||
),
|
||||
// The cell can use the individual row's getToggleRowSelectedProps method
|
||||
// to the render a checkbox
|
||||
Cell: ({ row }) => (
|
||||
<div>
|
||||
<label>
|
||||
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />{' '}
|
||||
Select Row
|
||||
</label>
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
id: 'selectedStatus',
|
||||
Cell: ({ row }) => (
|
||||
<div>
|
||||
Row {row.id} {row.isSelected ? 'Selected' : 'Not Selected'}
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Header: 'Info',
|
||||
columns: [
|
||||
{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
},
|
||||
{
|
||||
id: 'visits',
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
filter: 'equals',
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
return <Table columns={columns} data={data} />
|
||||
}
|
||||
|
||||
test('Select/Clear All while filtered only affects visible rows', () => {
|
||||
const { getAllByPlaceholderText, getByLabelText, getByTestId } = render(
|
||||
<App />
|
||||
)
|
||||
const selectedCount = getByTestId('selected-count')
|
||||
const selectAllCheckbox = getByLabelText('Select All')
|
||||
const fe = fireEvent
|
||||
const filterInputs = getAllByPlaceholderText('Search...')
|
||||
|
||||
fireEvent.change(filterInputs[3], { target: { value: '40' } })
|
||||
expect(selectedCount.textContent).toBe('0') // No selection has been made
|
||||
|
||||
expect(selectAllCheckbox.checked).toBe(false)
|
||||
fireEvent.click(selectAllCheckbox)
|
||||
expect(selectAllCheckbox.checked).toBe(true)
|
||||
expect(selectedCount.textContent).toBe('6') // "Select All" has happened
|
||||
|
||||
// This filter hides all the rows (nothing matches it)
|
||||
fireEvent.change(filterInputs[3], { target: { value: '10' } })
|
||||
expect(selectedCount.textContent).toBe('6') // Filtering does not alter the selection
|
||||
|
||||
expect(selectAllCheckbox.checked).toBe(false) // None of the selected items are visible, this should be false
|
||||
fireEvent.click(selectAllCheckbox) // The selection is is for no rows
|
||||
expect(selectAllCheckbox.checked).toBe(false) // So clicking this checkbox does nothing
|
||||
expect(selectedCount.textContent).toBe('6') // And does not affect the existing selection
|
||||
|
||||
fireEvent.change(filterInputs[3], { target: { value: '100' } })
|
||||
expect(selectAllCheckbox.checked).toBe(false) // None of the selected items are visible, this should be false
|
||||
fireEvent.click(selectAllCheckbox)
|
||||
expect(selectAllCheckbox.checked).toBe(true) // Now all of the visible rows are ALSO selected
|
||||
expect(selectedCount.textContent).toBe('12') // Clearing all should leave the original 6 selected
|
||||
|
||||
// Now deselect all the rows that match the filter
|
||||
fireEvent.click(selectAllCheckbox)
|
||||
expect(selectAllCheckbox.checked).toBe(false) // Now all of the visible rows are ALSO selected
|
||||
expect(selectedCount.textContent).toBe('6') // Clearing all should leave the original 6 selected
|
||||
|
||||
fireEvent.change(filterInputs[3], { target: { value: '' } })
|
||||
expect(selectedCount.textContent).toBe('6') // Clearing the Filter does not alter the selection
|
||||
|
||||
expect(selectAllCheckbox.checked).toBe(false) // Only a subset are selected so this button should show indeterminant
|
||||
fireEvent.click(selectAllCheckbox)
|
||||
expect(selectAllCheckbox.checked).toBe(true) // Now all of the visible rows are ALSO selected
|
||||
expect(selectedCount.textContent).toBe(data.length.toString()) // Select All should select ALL of the rows
|
||||
|
||||
fireEvent.click(selectAllCheckbox)
|
||||
expect(selectedCount.textContent).toBe('0') // Select All should now clear ALL of the rows
|
||||
})
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from 'react'
|
||||
import { renderHook, act } from '@testing-library/react-hooks'
|
||||
import { render, fireEvent } from '../../../test-utils/react-testing'
|
||||
import { useTable } from '../../hooks/useTable'
|
||||
import { usePagination } from '../usePagination'
|
||||
import { useFilters } from '../useFilters'
|
||||
|
||||
const data = [...new Array(1000)].map((d, i) => ({
|
||||
const data = [...new Array(1000)].fill(null).map((d, i) => ({
|
||||
firstName: `tanner ${i + 1}`,
|
||||
lastName: 'linsley',
|
||||
age: 29,
|
||||
@@ -12,6 +14,43 @@ const data = [...new Array(1000)].map((d, i) => ({
|
||||
progress: 50,
|
||||
}))
|
||||
|
||||
const columns = [
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Header: 'Info',
|
||||
columns: [
|
||||
{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
},
|
||||
{
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
function Table({ columns, data }) {
|
||||
const {
|
||||
getTableProps,
|
||||
@@ -114,46 +153,6 @@ function Table({ columns, data }) {
|
||||
}
|
||||
|
||||
function App() {
|
||||
const columns = React.useMemo(
|
||||
() => [
|
||||
{
|
||||
Header: 'Name',
|
||||
columns: [
|
||||
{
|
||||
Header: 'First Name',
|
||||
accessor: 'firstName',
|
||||
},
|
||||
{
|
||||
Header: 'Last Name',
|
||||
accessor: 'lastName',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
Header: 'Info',
|
||||
columns: [
|
||||
{
|
||||
Header: 'Age',
|
||||
accessor: 'age',
|
||||
},
|
||||
{
|
||||
Header: 'Visits',
|
||||
accessor: 'visits',
|
||||
},
|
||||
{
|
||||
Header: 'Status',
|
||||
accessor: 'status',
|
||||
},
|
||||
{
|
||||
Header: 'Profile Progress',
|
||||
accessor: 'progress',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
[]
|
||||
)
|
||||
|
||||
return <Table columns={columns} data={data} />
|
||||
}
|
||||
|
||||
@@ -185,3 +184,53 @@ test('renders a paginated table', () => {
|
||||
.reverse()[0].children[0].textContent
|
||||
).toEqual('tanner 30')
|
||||
})
|
||||
|
||||
describe('usePagination', () => {
|
||||
test('renders a paginated table', () => {
|
||||
const rendered = render(<App />)
|
||||
|
||||
expect(rendered.queryAllByRole('cell')[0].textContent).toEqual('tanner 21')
|
||||
|
||||
fireEvent.click(rendered.getByText('>'))
|
||||
expect(rendered.queryAllByRole('cell')[0].textContent).toEqual('tanner 31')
|
||||
|
||||
fireEvent.click(rendered.getByText('>'))
|
||||
expect(rendered.queryAllByRole('cell')[0].textContent).toEqual('tanner 41')
|
||||
|
||||
fireEvent.click(rendered.getByText('>>'))
|
||||
expect(rendered.queryAllByRole('cell')[0].textContent).toEqual('tanner 991')
|
||||
|
||||
fireEvent.click(rendered.getByText('<<'))
|
||||
expect(rendered.queryAllByRole('cell')[0].textContent).toEqual('tanner 1')
|
||||
|
||||
fireEvent.change(rendered.getByTestId('page-size-select'), {
|
||||
target: { value: 30 },
|
||||
})
|
||||
|
||||
expect(
|
||||
rendered
|
||||
.queryAllByRole('row')
|
||||
.slice(2)
|
||||
.reverse()[0].children[0].textContent
|
||||
).toEqual('tanner 30')
|
||||
})
|
||||
|
||||
test('changing filters resets pagination', async () => {
|
||||
const { result } = renderHook(() =>
|
||||
useTable(
|
||||
{
|
||||
columns,
|
||||
data,
|
||||
},
|
||||
useFilters,
|
||||
usePagination
|
||||
)
|
||||
)
|
||||
|
||||
act(() => result.current.nextPage())
|
||||
act(() => result.current.nextPage())
|
||||
expect(result.current.state.pageIndex).toEqual(2)
|
||||
act(() => result.current.visibleColumns[0].setFilter('tanner'))
|
||||
expect(result.current.state.pageIndex).toEqual(0)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -237,19 +237,17 @@ function useInstance(instance) {
|
||||
// We technically could do this recursively in the above loop,
|
||||
// but that would severely hinder the API for the user, since they
|
||||
// would be required to do that recursion in some scenarios
|
||||
filteredRows = filteredRows.map(row => {
|
||||
filteredRows.forEach(row => {
|
||||
filteredFlatRows.push(row)
|
||||
filteredRowsById[row.id] = row
|
||||
if (!row.subRows) {
|
||||
return row
|
||||
}
|
||||
return {
|
||||
...row,
|
||||
subRows:
|
||||
row.subRows && row.subRows.length > 0
|
||||
? filterRows(row.subRows, depth + 1)
|
||||
: row.subRows,
|
||||
return
|
||||
}
|
||||
|
||||
row.subRows =
|
||||
row.subRows && row.subRows.length > 0
|
||||
? filterRows(row.subRows, depth + 1)
|
||||
: row.subRows
|
||||
})
|
||||
|
||||
return filteredRows
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export function useFlexLayout(hooks) {
|
||||
hooks.getTableBodyProps.push(getTableBodyProps)
|
||||
hooks.getTableProps.push(getTableProps)
|
||||
hooks.getRowProps.push(getRowStyles)
|
||||
hooks.getHeaderGroupProps.push(getRowStyles)
|
||||
hooks.getHeaderProps.push(getHeaderProps)
|
||||
@@ -8,7 +8,7 @@ export function useFlexLayout(hooks) {
|
||||
|
||||
useFlexLayout.pluginName = 'useFlexLayout'
|
||||
|
||||
const getTableBodyProps = (props, { instance }) => [
|
||||
const getTableProps = (props, { instance }) => [
|
||||
props,
|
||||
{
|
||||
style: {
|
||||
@@ -33,7 +33,9 @@ const getHeaderProps = (props, { column }) => [
|
||||
{
|
||||
style: {
|
||||
boxSizing: 'border-box',
|
||||
flex: column.totalFlexWidth ? `${column.totalFlexWidth} 0 auto` : undefined,
|
||||
flex: column.totalFlexWidth
|
||||
? `${column.totalFlexWidth} 0 auto`
|
||||
: undefined,
|
||||
minWidth: `${column.totalMinWidth}px`,
|
||||
width: `${column.totalWidth}px`,
|
||||
},
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
import React from 'react'
|
||||
|
||||
import { getFilterMethod, shouldAutoRemoveFilter } from '../utils'
|
||||
import {
|
||||
getFilterMethod,
|
||||
shouldAutoRemoveFilter,
|
||||
getFirstDefined,
|
||||
} from '../utils'
|
||||
|
||||
import {
|
||||
actions,
|
||||
@@ -68,6 +72,7 @@ function useInstance(instance) {
|
||||
state: { globalFilter: globalFilterValue },
|
||||
dispatch,
|
||||
autoResetGlobalFilter = true,
|
||||
disableGlobalFilter,
|
||||
} = instance
|
||||
|
||||
const setGlobalFilter = React.useCallback(
|
||||
@@ -105,24 +110,37 @@ function useInstance(instance) {
|
||||
return rows
|
||||
}
|
||||
|
||||
allColumns.forEach(column => {
|
||||
const { disableGlobalFilter: columnDisableGlobalFilter } = column
|
||||
|
||||
column.canFilter = getFirstDefined(
|
||||
columnDisableGlobalFilter === true ? false : undefined,
|
||||
disableGlobalFilter === true ? false : undefined,
|
||||
true
|
||||
)
|
||||
})
|
||||
|
||||
const filterableColumns = allColumns.filter(c => c.canFilter === true)
|
||||
|
||||
// Filters top level and nested rows
|
||||
const filterRows = filteredRows => {
|
||||
return filterMethod(
|
||||
filteredRows = filterMethod(
|
||||
filteredRows,
|
||||
allColumns.map(d => d.id),
|
||||
filterableColumns.map(d => d.id),
|
||||
globalFilterValue
|
||||
).map(row => {
|
||||
)
|
||||
|
||||
filteredRows.forEach(row => {
|
||||
filteredFlatRows.push(row)
|
||||
filteredRowsById[row.id] = row
|
||||
|
||||
return {
|
||||
...row,
|
||||
subRows:
|
||||
row.subRows && row.subRows.length
|
||||
? filterRows(row.subRows)
|
||||
: row.subRows,
|
||||
}
|
||||
row.subRows =
|
||||
row.subRows && row.subRows.length
|
||||
? filterRows(row.subRows)
|
||||
: row.subRows
|
||||
})
|
||||
|
||||
return filteredRows
|
||||
}
|
||||
|
||||
return [filterRows(rows), filteredFlatRows, filteredRowsById]
|
||||
@@ -131,10 +149,11 @@ function useInstance(instance) {
|
||||
globalFilterValue,
|
||||
globalFilter,
|
||||
userFilterTypes,
|
||||
allColumns,
|
||||
rows,
|
||||
flatRows,
|
||||
rowsById,
|
||||
allColumns,
|
||||
disableGlobalFilter,
|
||||
])
|
||||
|
||||
const getAutoResetGlobalFilter = useGetLatest(autoResetGlobalFilter)
|
||||
@@ -156,5 +175,6 @@ function useInstance(instance) {
|
||||
flatRows: globalFilteredFlatRows,
|
||||
rowsById: globalFilteredRowsById,
|
||||
setGlobalFilter,
|
||||
disableGlobalFilter,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import { getFirstDefined, flattenBy } from '../utils'
|
||||
import {
|
||||
actions,
|
||||
makePropGetter,
|
||||
defaultGroupByFn,
|
||||
ensurePluginOrder,
|
||||
useMountedLayoutEffect,
|
||||
useGetLatest,
|
||||
@@ -408,3 +407,14 @@ function prepareRow(row) {
|
||||
cell.isAggregated = !cell.isGrouped && !cell.isPlaceholder && row.canExpand
|
||||
})
|
||||
}
|
||||
|
||||
export function defaultGroupByFn(rows, columnId) {
|
||||
return rows.reduce((prev, row, i) => {
|
||||
// TODO: Might want to implement a key serializer here so
|
||||
// irregular column values can still be grouped if needed?
|
||||
const resKey = `${row.values[columnId]}`
|
||||
prev[resKey] = Array.isArray(prev[resKey]) ? prev[resKey] : []
|
||||
prev[resKey].push(row)
|
||||
return prev
|
||||
}, {})
|
||||
}
|
||||
|
||||
@@ -89,10 +89,6 @@ function useInstance(instance) {
|
||||
dispatch,
|
||||
data,
|
||||
manualPagination,
|
||||
manualGlobalFilter,
|
||||
manualFilters,
|
||||
manualGroupBy,
|
||||
manualSortBy,
|
||||
} = instance
|
||||
|
||||
ensurePluginOrder(
|
||||
@@ -110,10 +106,10 @@ function useInstance(instance) {
|
||||
}, [
|
||||
dispatch,
|
||||
manualPagination ? null : data,
|
||||
manualGlobalFilter ? null : globalFilter,
|
||||
manualFilters ? null : filters,
|
||||
manualGroupBy ? null : groupBy,
|
||||
manualSortBy ? null : sortBy,
|
||||
globalFilter,
|
||||
filters,
|
||||
groupBy,
|
||||
sortBy,
|
||||
])
|
||||
|
||||
const pageCount = manualPagination
|
||||
@@ -121,7 +117,10 @@ function useInstance(instance) {
|
||||
: Math.ceil(rows.length / pageSize)
|
||||
|
||||
const pageOptions = React.useMemo(
|
||||
() => (pageCount > 0 ? [...new Array(pageCount)].map((d, i) => i) : []),
|
||||
() =>
|
||||
pageCount > 0
|
||||
? [...new Array(pageCount)].fill(null).map((d, i) => i)
|
||||
: [],
|
||||
[pageCount]
|
||||
)
|
||||
|
||||
|
||||
@@ -95,22 +95,23 @@ function reducer(state, action, previousState, instance) {
|
||||
const selectAll =
|
||||
typeof setSelected !== 'undefined' ? setSelected : !isAllRowsSelected
|
||||
|
||||
if (selectAll) {
|
||||
const selectedRowIds = {}
|
||||
// Only remove/add the rows that are visible on the screen
|
||||
// Leave all the other rows that are selected alone.
|
||||
const selectedRowIds = Object.assign({}, state.selectedRowIds)
|
||||
|
||||
if (selectAll) {
|
||||
Object.keys(nonGroupedRowsById).forEach(rowId => {
|
||||
selectedRowIds[rowId] = true
|
||||
})
|
||||
|
||||
return {
|
||||
...state,
|
||||
selectedRowIds,
|
||||
}
|
||||
} else {
|
||||
Object.keys(nonGroupedRowsById).forEach(rowId => {
|
||||
delete selectedRowIds[rowId]
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
...state,
|
||||
selectedRowIds: {},
|
||||
selectedRowIds,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,15 +131,15 @@ function reducer(state, action, previousState, instance) {
|
||||
return state
|
||||
}
|
||||
|
||||
let newSelectedRowIds = { ...state.selectedRowIds }
|
||||
const newSelectedRowIds = { ...state.selectedRowIds }
|
||||
|
||||
const handleRowById = id => {
|
||||
const row = rowsById[id]
|
||||
|
||||
if (!row.isGrouped) {
|
||||
if (!isSelected && shouldExist) {
|
||||
if (shouldExist) {
|
||||
newSelectedRowIds[id] = true
|
||||
} else if (isSelected && !shouldExist) {
|
||||
} else {
|
||||
delete newSelectedRowIds[id]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import {
|
||||
ensurePluginOrder,
|
||||
defaultColumn,
|
||||
makePropGetter,
|
||||
defaultOrderByFn,
|
||||
useGetLatest,
|
||||
useMountedLayoutEffect,
|
||||
} from '../publicUtils'
|
||||
@@ -360,3 +359,17 @@ function useInstance(instance) {
|
||||
toggleSortBy,
|
||||
})
|
||||
}
|
||||
|
||||
export function defaultOrderByFn(arr, funcs, dirs) {
|
||||
return [...arr].sort((rowA, rowB) => {
|
||||
for (let i = 0; i < funcs.length; i += 1) {
|
||||
const sortFn = funcs[i]
|
||||
const desc = dirs[i] === false || dirs[i] === 'desc'
|
||||
const sortInt = sortFn(rowA, rowB)
|
||||
if (sortInt !== 0) {
|
||||
return desc ? -sortInt : sortInt
|
||||
}
|
||||
}
|
||||
return dirs[0] ? rowA.index - rowB.index : rowB.index - rowA.index
|
||||
})
|
||||
}
|
||||
|
||||
@@ -13,31 +13,6 @@ export const defaultColumn = {
|
||||
maxWidth: Number.MAX_SAFE_INTEGER,
|
||||
}
|
||||
|
||||
export function defaultOrderByFn(arr, funcs, dirs) {
|
||||
return [...arr].sort((rowA, rowB) => {
|
||||
for (let i = 0; i < funcs.length; i += 1) {
|
||||
const sortFn = funcs[i]
|
||||
const desc = dirs[i] === false || dirs[i] === 'desc'
|
||||
const sortInt = sortFn(rowA, rowB)
|
||||
if (sortInt !== 0) {
|
||||
return desc ? -sortInt : sortInt
|
||||
}
|
||||
}
|
||||
return dirs[0] ? rowA.index - rowB.index : rowB.index - rowA.index
|
||||
})
|
||||
}
|
||||
|
||||
export function defaultGroupByFn(rows, columnId) {
|
||||
return rows.reduce((prev, row, i) => {
|
||||
// TODO: Might want to implement a key serializer here so
|
||||
// irregular column values can still be grouped if needed?
|
||||
const resKey = `${row.values[columnId]}`
|
||||
prev[resKey] = Array.isArray(prev[resKey]) ? prev[resKey] : []
|
||||
prev[resKey].push(row)
|
||||
return prev
|
||||
}, {})
|
||||
}
|
||||
|
||||
function mergeProps(...propList) {
|
||||
return propList.reduce((props, next) => {
|
||||
const { style, className, ...rest } = next
|
||||
|
||||
@@ -65,14 +65,6 @@ export function assignColumnAccessor(column) {
|
||||
return column
|
||||
}
|
||||
|
||||
// Find the depth of the columns
|
||||
export function dedupeBy(arr, fn) {
|
||||
return [...arr]
|
||||
.reverse()
|
||||
.filter((d, i, all) => all.findIndex(dd => fn(dd) === fn(d)) === i)
|
||||
.reverse()
|
||||
}
|
||||
|
||||
export function decorateColumn(column, userDefaultColumn) {
|
||||
if (!userDefaultColumn) {
|
||||
throw new Error()
|
||||
|
||||
Reference in New Issue
Block a user