Compare commits

..
72 changed files with 5316 additions and 32880 deletions
+13 -20
View File
@@ -1,22 +1,15 @@
{
"parser": "babel-eslint",
"extends": ["react-app", "prettier"],
"plugins": [
"react-hooks"
],
"env": {
"es6": true
},
"parserOptions": {
"sourceType": "module"
},
"rules": {
"space-before-function-paren": 0,
"react/jsx-boolean-value": 0
},
"settings": {
"react": {
"version": "latest"
}
}
"parser": "babel-eslint",
"extends": ["standard", "standard-react"],
"env": {
"es6": true
},
"plugins": ["react"],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"space-before-function-paren": 0,
"react/jsx-boolean-value": 0
}
}
-5
View File
@@ -1,5 +0,0 @@
# These are supported funding model platforms
github: tannerlinsley
patreon: tannerlinsley
custom: https://youtube.com/tannerlinsley
+4 -4
View File
@@ -1,7 +1,7 @@
node_modules
lib
dist
es
node_modules/
lib/
dist/
es/
docs/build
react-table.js
react-table.min.js
-5
View File
@@ -1,5 +0,0 @@
module.exports = {
hooks: {
'pre-commit': 'lint-staged',
},
}
+33 -1060
View File
File diff suppressed because it is too large Load Diff
-17
View File
@@ -1,17 +0,0 @@
const path = require('path');
module.exports = {
moduleDirectories: [
'node_modules',
/*
* make 'test/utils' available in tests, e.g.
*
* const {myModule} = require('utils/my-test-helper')
*/
__dirname,
],
rootDir: path.resolve(__dirname, '../..'),
roots: ['<rootDir>/src', __dirname],
};
-13
View File
@@ -1,13 +0,0 @@
const {rootDir} = require('./jest.common');
module.exports = {
rootDir,
displayName: 'lint',
runner: 'jest-runner-eslint',
testMatch: ['<rootDir>/src/**/*.js'],
testPathIgnorePatterns: ['node_modules', 'coverage', 'dist', '.test.js'],
};
-15
View File
@@ -1,15 +0,0 @@
const commonConfig = require('./jest.common');
module.exports = {
...commonConfig,
displayName: 'unit',
coverageDirectory: '../../coverage',
testMatch: ['<rootDir>/tests/**/*.js'],
transform: {
// '^.+\\.js$': '<rootDir>/node_modules/babel-jest',
},
};
View File
+2
View File
File diff suppressed because one or more lines are too long
+1
View File
File diff suppressed because one or more lines are too long
BIN
View File
Binary file not shown.
-4
View File
@@ -1,4 +0,0 @@
{
"presets": ["react-app"],
"plugins": ["styled-components"]
}
-1
View File
@@ -1 +0,0 @@
SKIP_PREFLIGHT_CHECK=true
-7
View File
@@ -1,7 +0,0 @@
{
"extends": ["react-app", "prettier"],
"rules": {
// "eqeqeq": 0,
// "jsx-a11y/anchor-is-valid": 0
}
}
-23
View File
@@ -1,23 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
-29
View File
@@ -1,29 +0,0 @@
const path = require('path')
const resolveFrom = require('resolve-from')
const fixLinkedDependencies = config => {
config.resolve = {
...config.resolve,
alias: {
...config.resolve.alias,
react$: resolveFrom(path.resolve('node_modules'), 'react'),
'react-dom$': resolveFrom(path.resolve('node_modules'), 'react-dom'),
},
}
return config
}
const includeSrcDirectory = config => {
config.resolve = {
...config.resolve,
modules: [path.resolve('src'), ...config.resolve.modules],
}
return config
}
module.exports = [
['use-babel-config', '.babelrc'],
['use-eslint-config', '.eslintrc'],
fixLinkedDependencies,
// includeSrcDirectory,
]
-6
View File
@@ -1,6 +0,0 @@
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) and Rescripts.
You can:
- [Open this example in a new CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/basic)
- `yarn` and `yarn start` to run and edit the example
-35
View File
@@ -1,35 +0,0 @@
{
"private": true,
"scripts": {
"start": "rescripts start",
"build": "rescripts build",
"test": "rescripts test",
"eject": "rescripts eject"
},
"dependencies": {
"namor": "^1.1.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.1",
"react-table": "next",
"styled-components": "^4.3.2"
},
"devDependencies": {
"@rescripts/cli": "^0.0.11",
"@rescripts/rescript-use-babel-config": "^0.0.8",
"@rescripts/rescript-use-eslint-config": "^0.0.9",
"babel-eslint": "10.0.1"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

-38
View File
@@ -1,38 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
-15
View File
@@ -1,15 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
-121
View File
@@ -1,121 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import { useTable } from 'react-table'
import makeData from './makeData'
const Styles = styled.div`
padding: 1rem;
table {
border-spacing: 0;
border: 1px solid black;
tr {
:last-child {
td {
border-bottom: 0;
}
}
}
th,
td {
margin: 0;
padding: 0.5rem;
border-bottom: 1px solid black;
border-right: 1px solid black;
:last-child {
border-right: 0;
}
}
}
`
function Table({ columns, data }) {
// Use the state and functions returned from useTable to build your UI
const { getTableProps, headerGroups, rows, prepareRow } = useTable({
columns,
data,
})
// Render the UI for your table
return (
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getRowProps()}>
{headerGroup.headers.map(column => (
<th {...column.getHeaderProps()}>{column.render('Header')}</th>
))}
</tr>
))}
</thead>
<tbody>
{rows.map(
(row, i) =>
prepareRow(row) || (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
)}
</tbody>
</table>
)
}
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',
},
],
},
],
[]
)
const data = React.useMemo(() => makeData(20), [])
return (
<Styles>
<Table columns={columns} data={data} />
</Styles>
)
}
export default App
-9
View File
@@ -1,9 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
-13
View File
@@ -1,13 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
-12
View File
@@ -1,12 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
-40
View File
@@ -1,40 +0,0 @@
import namor from 'namor'
const range = len => {
const arr = []
for (let i = 0; i < len; i++) {
arr.push(i)
}
return arr
}
const newPerson = () => {
const statusChance = Math.random()
return {
firstName: namor.generate({ words: 1, numbers: 0 }),
lastName: namor.generate({ words: 1, numbers: 0 }),
age: Math.floor(Math.random() * 30),
visits: Math.floor(Math.random() * 100),
progress: Math.floor(Math.random() * 100),
status:
statusChance > 0.66
? 'relationship'
: statusChance > 0.33
? 'complicated'
: 'single',
}
}
export default function makeData(...lens) {
const makeDataLevel = (depth = 0) => {
const len = lens[depth]
return range(len).map(d => {
return {
...newPerson(),
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
}
})
}
return makeDataLevel()
}
-135
View File
@@ -1,135 +0,0 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
File diff suppressed because it is too large Load Diff
-4
View File
@@ -1,4 +0,0 @@
{
"presets": ["react-app"],
"plugins": ["styled-components"]
}
-1
View File
@@ -1 +0,0 @@
SKIP_PREFLIGHT_CHECK=true
@@ -1,7 +0,0 @@
{
"extends": ["react-app", "prettier"],
"rules": {
// "eqeqeq": 0,
// "jsx-a11y/anchor-is-valid": 0
}
}
@@ -1,23 +0,0 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
@@ -1,29 +0,0 @@
const path = require('path')
const resolveFrom = require('resolve-from')
const fixLinkedDependencies = config => {
config.resolve = {
...config.resolve,
alias: {
...config.resolve.alias,
react$: resolveFrom(path.resolve('node_modules'), 'react'),
'react-dom$': resolveFrom(path.resolve('node_modules'), 'react-dom'),
},
}
return config
}
const includeSrcDirectory = config => {
config.resolve = {
...config.resolve,
modules: [path.resolve('src'), ...config.resolve.modules],
}
return config
}
module.exports = [
['use-babel-config', '.babelrc'],
['use-eslint-config', '.eslintrc'],
fixLinkedDependencies,
// includeSrcDirectory,
]
@@ -1,6 +0,0 @@
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app) and Rescripts.
You can:
- [Open this example in a new CodeSandbox](https://codesandbox.io/s/github/tannerlinsley/react-table/tree/master/examples/basic)
- `yarn` and `yarn start` to run and edit the example
@@ -1,35 +0,0 @@
{
"private": true,
"scripts": {
"start": "rescripts start",
"build": "rescripts build",
"test": "rescripts test",
"eject": "rescripts eject"
},
"dependencies": {
"namor": "^1.1.2",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-scripts": "3.0.1",
"react-table": "next",
"styled-components": "^4.3.2"
},
"devDependencies": {
"@rescripts/cli": "^0.0.11",
"@rescripts/rescript-use-babel-config": "^0.0.8",
"@rescripts/rescript-use-eslint-config": "^0.0.9",
"babel-eslint": "10.0.1"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

@@ -1,38 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
@@ -1,15 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}
-130
View File
@@ -1,130 +0,0 @@
import React from 'react'
import styled from 'styled-components'
import { useTable, useSortBy } from 'react-table'
import makeData from './makeData'
const Styles = styled.div`
padding: 1rem;
table {
border-spacing: 0;
border: 1px solid black;
tr {
:last-child {
td {
border-bottom: 0;
}
}
}
th,
td {
margin: 0;
padding: 0.5rem;
border-bottom: 1px solid black;
border-right: 1px solid black;
:last-child {
border-right: 0;
}
}
}
`
function Table({ columns, data }) {
const { getTableProps, headerGroups, rows, prepareRow } = useTable(
{
columns,
data,
},
useSortBy
)
return (
<table {...getTableProps()}>
<thead>
{headerGroups.map(headerGroup => (
<tr {...headerGroup.getRowProps()}>
{headerGroup.headers.map(column => (
// Add the sorting props to control sorting. For this example
// we can add them into the header props
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render('Header')}
{/* Add a sort direction indicator */}
<span>
{column.sorted ? (column.sortedDesc ? ' 🔽' : ' 🔼') : ''}
</span>
</th>
))}
</tr>
))}
</thead>
<tbody>
{rows.map(
(row, i) =>
prepareRow(row) || (
<tr {...row.getRowProps()}>
{row.cells.map(cell => {
return <td {...cell.getCellProps()}>{cell.render('Cell')}</td>
})}
</tr>
)
)}
</tbody>
</table>
)
}
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',
},
],
},
],
[]
)
const data = React.useMemo(() => makeData(20), [])
return (
<Styles>
<Table columns={columns} data={data} />
</Styles>
)
}
export default App
@@ -1,9 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
ReactDOM.unmountComponentAtNode(div);
});
@@ -1,13 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
@@ -1,12 +0,0 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import * as serviceWorker from './serviceWorker';
ReactDOM.render(<App />, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();
@@ -1,40 +0,0 @@
import namor from 'namor'
const range = len => {
const arr = []
for (let i = 0; i < len; i++) {
arr.push(i)
}
return arr
}
const newPerson = () => {
const statusChance = Math.random()
return {
firstName: namor.generate({ words: 1, numbers: 0 }),
lastName: namor.generate({ words: 1, numbers: 0 }),
age: Math.floor(Math.random() * 30),
visits: Math.floor(Math.random() * 100),
progress: Math.floor(Math.random() * 100),
status:
statusChance > 0.66
? 'relationship'
: statusChance > 0.33
? 'complicated'
: 'single',
}
}
export default function makeData(...lens) {
const makeDataLevel = (depth = 0) => {
const len = lens[depth]
return range(len).map(d => {
return {
...newPerson(),
subRows: lens[depth + 1] ? makeDataLevel(depth + 1) : undefined,
}
})
}
return makeDataLevel()
}
@@ -1,135 +0,0 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
File diff suppressed because it is too large Load Diff
-16
View File
@@ -1,16 +0,0 @@
const path = require('path');
const lintProject = require('./configs/tests/jest.lint');
const unitProject = require('./configs/tests/jest.unit');
module.exports = {
...require('./configs/tests/jest.common'),
projects: [lintProject, unitProject],
watchPlugins: [
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname',
'jest-watch-select-projects',
],
};
-3
View File
@@ -1,3 +0,0 @@
module.exports = {
'*.js': ['prettier --write', 'git add'],
}
+4488 -6020
View File
File diff suppressed because it is too large Load Diff
+9 -23
View File
@@ -1,6 +1,6 @@
{
"name": "react-table",
"version": "7.0.0-alpha.8",
"version": "7.0.0-alpha.2",
"description": "A fast, lightweight, opinionated table and datagrid built on React",
"license": "MIT",
"homepage": "https://github.com/tannerlinsley/react-table#readme",
@@ -15,12 +15,11 @@
"datagrid"
],
"main": "dist/index.js",
"module": "dist/index.es.js",
"jsnext:main": "dist/index.es.js",
"_module": "dist/index.es.js",
"_jsnext:main": "dist/index.es.js",
"scripts": {
"test": "is-ci 'test:ci' 'test:dev'",
"test:dev": "jest --watch",
"test:ci": "jest",
"test": "cross-env CI=1 react-scripts test --env=jsdom",
"test:watch": "react-scripts test --env=jsdom",
"build": "rollup -c",
"start": "rollup -c -w",
"prepare": "yarn build",
@@ -47,30 +46,17 @@
"@babel/preset-react": "^7.0.0",
"@babel/runtime": "^7.2.0",
"@svgr/rollup": "^4.1.0",
"@testing-library/react": "^8.0.4",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "9.x",
"babel-eslint": "^10.0.1",
"cross-env": "^5.1.4",
"eslint": "5.x",
"eslint-config-prettier": "^4.3.0",
"eslint-config-react-app": "^4.0.1",
"eslint": "^5.10.0",
"eslint-config-standard": "^12.0.0",
"eslint-config-standard-react": "^7.0.2",
"eslint-plugin-flowtype": "2.x",
"eslint-plugin-import": "2.x",
"eslint-plugin-jsx-a11y": "6.x",
"eslint-plugin-import": "^2.13.0",
"eslint-plugin-node": "^8.0.0",
"eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-promise": "^4.0.0",
"eslint-plugin-react": "7.x",
"eslint-plugin-react-hooks": "1.5.0",
"eslint-plugin-react": "^7.10.0",
"eslint-plugin-standard": "^4.0.0",
"is-ci-cli": "^1.1.1",
"jest": "^24.8.0",
"jest-cli": "^24.8.0",
"jest-runner-eslint": "^0.7.4",
"jest-watch-select-projects": "^0.1.2",
"jest-watch-typeahead": "^0.3.1",
"rollup": "^0.68.0",
"rollup-plugin-babel": "^4.1.0",
"rollup-plugin-commonjs": "^9.1.3",
-6
View File
@@ -1,6 +0,0 @@
module.exports = {
trailingComma: 'es5',
tabWidth: 2,
semi: false,
singleQuote: true,
}
+21 -30
View File
@@ -6,36 +6,27 @@ import { uglify } from 'rollup-plugin-uglify'
import pkg from './package.json'
export default [
{
input: 'src/index.js',
output: {
export default {
input: 'src/index.js',
output: [
{
file: pkg.main,
format: 'cjs',
sourcemap: true
},
plugins: [
external(),
babel({
exclude: 'node_modules/**'
}),
resolve(),
commonjs(),
uglify()
]
},
{
input: 'src/index.js',
external: Object.keys(pkg.peerDependencies),
output: {
file: pkg.module,
format: 'es',
sourcemap: true
},
plugins: [
babel({
exclude: ['/**/node_modules/**']
})
]
}
]
}
// {
// file: pkg.module,
// format: "es",
// sourcemap: true
// }
],
plugins: [
external(),
babel({
exclude: 'node_modules/**'
}),
resolve(),
commonjs(),
uglify()
]
}
+4 -15
View File
@@ -1,20 +1,9 @@
const actions = {}
const types = {}
export { actions, types }
export { actions }
export const addActions = (...acts) => {
acts.forEach(action => {
if (actions[action]) {
throw new Error(
`An React Table action type called ${action} has already been registered!`
)
}
// Action values are formatted this way to discourage
// you (the dev) from interacting with them in any way
// other than importing `{ actions } from 'react-table'`
// and referencing an action via `actions[actionName]`
actions[action] = `React Table Action: ${action}`
types[actions[action]] = true
export const addActions = acts => {
Object.keys(acts).forEach(key => {
actions[key] = acts[key]
})
}
-56
View File
@@ -1,56 +0,0 @@
export const text = (rows, id, filterValue) => {
return rows.filter(row => {
const rowValue = row.values[id]
return rowValue !== undefined
? String(rowValue)
.toLowerCase()
.includes(String(filterValue).toLowerCase())
: true
})
}
export const exactText = (rows, id, filterValue) => {
return rows.filter(row => {
const rowValue = row.values[id]
return rowValue !== undefined
? String(rowValue).toLowerCase() === String(filterValue).toLowerCase()
: true
})
}
export const exactTextCase = (rows, id, filterValue) => {
return rows.filter(row => {
const rowValue = row.values[id]
return rowValue !== undefined
? String(rowValue) === String(filterValue)
: true
})
}
export const includes = (rows, id, filterValue) => {
return rows.filter(row => {
const rowValue = row.values[id]
return filterValue.includes(rowValue)
})
}
export const includesAll = (rows, id, filterValue) => {
return rows.filter(row => {
const rowValue = row.values[id]
return filterValue.every(val => rowValue.includes(val))
})
}
export const exact = (rows, id, filterValue) => {
return rows.filter(row => {
const rowValue = row.values[id]
return rowValue === filterValue
})
}
export const between = (rows, id, filterValue) => {
return rows.filter(row => {
const rowValue = row.values[id]
return rowValue >= filterValue[0] && rowValue <= filterValue[1]
})
}
+140 -164
View File
@@ -8,166 +8,16 @@ const propTypes = {
columns: PropTypes.arrayOf(
PropTypes.shape({
Cell: PropTypes.any,
Header: PropTypes.any,
Header: PropTypes.any
})
),
defaultColumn: PropTypes.any,
}
// Find the depth of the columns
function findMaxDepth(columns, depth = 0) {
return columns.reduce((prev, curr) => {
if (curr.columns) {
return Math.max(prev, findMaxDepth(curr.columns, depth + 1))
}
return depth
}, 0)
}
function decorateColumn(column, defaultColumn, parent, depth, index) {
// Apply the defaultColumn
column = { ...defaultColumn, ...column }
// First check for string accessor
let { id, accessor, Header } = column
if (typeof accessor === 'string') {
id = id || accessor
const accessorString = accessor
accessor = row => getBy(row, accessorString)
}
if (!id && typeof Header === 'string') {
id = Header
}
if (!id && column.columns) {
console.error(column)
throw new Error('A column ID (or unique "Header" value) is required!')
}
if (!id) {
console.error(column)
throw new Error('A column ID (or string accessor) is required!')
}
column = {
Header: ({ id }) => id,
Cell: ({ value }) => value,
show: true,
...column,
id,
accessor,
parent,
depth,
index,
}
return column
}
// Build the visible columns, headers and flat column list
function decorateColumnTree(columns, defaultColumn, parent, depth = 0) {
return columns.map((column, columnIndex) => {
column = decorateColumn(column, defaultColumn, parent, depth, columnIndex)
if (column.columns) {
column.columns = decorateColumnTree(
column.columns,
defaultColumn,
column,
depth + 1
)
}
return column
})
}
// Build the header groups from the bottom up
function makeHeaderGroups(columns, maxDepth, defaultColumn) {
const headerGroups = []
const removeChildColumns = column => {
delete column.columns
if (column.parent) {
removeChildColumns(column.parent)
}
}
columns.forEach(removeChildColumns)
const buildGroup = (columns, depth = 0) => {
const headerGroup = {
headers: [],
}
const parentColumns = []
const hasParents = columns.some(col => col.parent)
columns.forEach(column => {
const isFirst = !parentColumns.length
let latestParentColumn = [...parentColumns].reverse()[0]
// If the column has a parent, add it if necessary
if (column.parent) {
if (isFirst || latestParentColumn.originalID !== column.parent.id) {
parentColumns.push({
...column.parent,
originalID: column.parent.id,
id: [column.parent.id, parentColumns.length].join('_'),
})
}
} else if (hasParents) {
// If other columns have parents, add a place holder if necessary
const placeholderColumn = decorateColumn(
{
originalID: [column.id, 'placeholder', maxDepth - depth].join('_'),
id: [
column.id,
'placeholder',
maxDepth - depth,
parentColumns.length,
].join('_'),
},
defaultColumn
)
if (
isFirst ||
latestParentColumn.originalID !== placeholderColumn.originalID
) {
parentColumns.push(placeholderColumn)
}
}
// Establish the new columns[] 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)
}
}
headerGroup.headers.push(column)
})
headerGroups.push(headerGroup)
if (parentColumns.length) {
buildGroup(parentColumns)
}
}
buildGroup(columns)
return headerGroups.reverse()
)
}
export const useColumns = props => {
const {
debug,
columns: userColumns,
defaultColumn = {},
state: [{ groupBy }],
state: [{ groupBy }]
} = props
PropTypes.checkPropTypes(propTypes, props, 'property', 'useColumns')
@@ -176,37 +26,86 @@ export const useColumns = props => {
if (debug) console.info('getColumns')
// Decorate All the columns
let columnTree = decorateColumnTree(userColumns, defaultColumn)
let columnTree = decorateColumnTree(userColumns)
// Get the flat list of all columns
let columns = flattenBy(columnTree, 'columns')
columns = [
...groupBy.map(g => columns.find(col => col.id === g)),
...columns.filter(col => !groupBy.includes(col.id)),
...columns.filter(col => !groupBy.includes(col.id))
]
// Get headerGroups
const headerGroups = makeHeaderGroups(
columns,
findMaxDepth(columnTree),
defaultColumn
)
const headerGroups = makeHeaderGroups(columns, findMaxDepth(columnTree))
const headers = flattenBy(headerGroups, 'headers')
return {
columns,
headerGroups,
headers,
headers
}
}, [debug, defaultColumn, groupBy, userColumns])
}, [groupBy, userColumns])
return {
...props,
columns,
headerGroups,
headers,
headers
}
// Find the depth of the columns
function findMaxDepth(columns, depth = 0) {
return columns.reduce((prev, curr) => {
if (curr.columns) {
return Math.max(prev, findMaxDepth(curr.columns, depth + 1))
}
return depth
}, 0)
}
function decorateColumn(column, parent) {
// First check for string accessor
let { id, accessor, Header } = column
if (typeof accessor === 'string') {
id = id || accessor
const accessorString = accessor
accessor = row => getBy(row, accessorString)
}
if (!id && typeof Header === 'string') {
id = Header
}
if (!id) {
// Accessor, but no column id? This is bad.
console.error(column)
throw new Error('A column id is required!')
}
column = {
Header: '',
Cell: cell => cell.value,
show: true,
...column,
id,
accessor,
parent
}
return column
}
// Build the visible columns, headers and flat column list
function decorateColumnTree(columns, parent, depth = 0) {
return columns.map(column => {
column = decorateColumn(column, parent)
if (column.columns) {
column.columns = decorateColumnTree(column.columns, column, depth + 1)
}
return column
})
}
function flattenBy(columns, childKey) {
@@ -226,4 +125,81 @@ export const useColumns = props => {
return flatColumns
}
// Build the header groups from the bottom up
function makeHeaderGroups(columns, maxDepth) {
const headerGroups = []
const removeChildColumns = column => {
delete column.columns
if (column.parent) {
removeChildColumns(column.parent)
}
}
columns.forEach(removeChildColumns)
const buildGroup = (columns, depth = 0) => {
const headerGroup = {
headers: []
}
const parentColumns = []
const hasParents = columns.some(col => col.parent)
columns.forEach(column => {
const isFirst = !parentColumns.length
let latestParentColumn = [...parentColumns].reverse()[0]
// If the column has a parent, add it if necessary
if (column.parent) {
if (isFirst || latestParentColumn.originalID !== column.parent.id) {
parentColumns.push({
...column.parent,
originalID: column.parent.id,
id: [column.parent.id, parentColumns.length].join('_')
})
}
} else if (hasParents) {
// If other columns have parents, add a place holder if necessary
const placeholderColumn = decorateColumn({
originalID: [column.id, 'placeholder', maxDepth - depth].join('_'),
id: [
column.id,
'placeholder',
maxDepth - depth,
parentColumns.length
].join('_')
})
if (
isFirst ||
latestParentColumn.originalID !== placeholderColumn.originalID
) {
parentColumns.push(placeholderColumn)
}
}
// Establish the new columns[] 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)
}
}
headerGroup.headers.push(column)
})
headerGroups.push(headerGroup)
if (parentColumns.length) {
buildGroup(parentColumns)
}
}
buildGroup(columns)
return headerGroups.reverse()
}
}
@@ -3,15 +3,17 @@ import PropTypes from 'prop-types'
import { getBy, getFirstDefined, setBy } from '../utils'
import { addActions, actions } from '../actions'
import { defaultState } from '../hooks/useTableState'
import { defaultState } from './useTableState'
defaultState.expanded = {}
addActions('toggleExpanded', 'useExpanded')
addActions({
toggleExpanded: '__toggleExpanded__',
useExpanded: '__useExpanded__'
})
const propTypes = {
manualExpandedKey: PropTypes.string,
paginateSubRows: PropTypes.bool,
expandedKey: PropTypes.string
}
export const useExpanded = props => {
@@ -19,11 +21,11 @@ export const useExpanded = props => {
const {
debug,
columns,
rows,
manualExpandedKey = 'expanded',
expandedKey = 'expanded',
hooks,
state: [{ expanded }, setState],
paginateSubRows = true,
state: [{ expanded }, setState]
} = props
const toggleExpandedByPath = (path, set) => {
@@ -33,7 +35,7 @@ export const useExpanded = props => {
set = getFirstDefined(set, !existing)
return {
...old,
expanded: setBy(expanded, path, set),
expanded: setBy(expanded, path, set)
}
}, actions.toggleExpanded)
}
@@ -41,7 +43,6 @@ export const useExpanded = props => {
hooks.row.push(row => {
const { path } = row
row.toggleExpanded = set => toggleExpandedByPath(path, set)
return row
})
const expandedRows = useMemo(() => {
@@ -51,32 +52,27 @@ export const useExpanded = props => {
// Here we do some mutation, but it's the last stage in the
// immutable process so this is safe
const handleRow = (row, depth = 0, parentPath = []) => {
const handleRow = (row, index, depth = 0, parentPath = []) => {
// Compute some final state for the row
const path = [...parentPath, row.index]
const path = [...parentPath, index]
row.path = path
row.depth = depth
row.isExpanded =
(row.original && row.original[manualExpandedKey]) ||
getBy(expanded, path)
(row.original && row.original[expandedKey]) || getBy(expanded, path)
if (paginateSubRows || (!paginateSubRows && row.depth === 0)) {
expandedRows.push(row)
}
expandedRows.push(row)
if (row.isExpanded && row.subRows && row.subRows.length) {
row.subRows.forEach((row, i) => handleRow(row, depth + 1, path))
row.subRows.forEach((row, i) => handleRow(row, i, depth + 1, path))
}
return row
}
rows.forEach(row => handleRow(row))
rows.forEach((row, i) => handleRow(row, i))
return expandedRows
}, [debug, rows, manualExpandedKey, expanded, paginateSubRows])
}, [rows, expanded, columns])
const expandedDepth = findExpandedDepth(expanded)
@@ -84,7 +80,7 @@ export const useExpanded = props => {
...props,
toggleExpandedByPath,
expandedDepth,
rows: expandedRows,
rows: expandedRows
}
}
@@ -1,14 +1,15 @@
import { useMemo } from 'react'
import PropTypes from 'prop-types'
import { getFirstDefined, isFunction } from '../utils'
import * as filterTypes from '../filterTypes'
import { defaultFilterFn, getFirstDefined } from '../utils'
import { addActions, actions } from '../actions'
import { defaultState } from '../hooks/useTableState'
import { defaultState } from './useTableState'
defaultState.filters = {}
addActions('setFilter', 'setAllFilters')
addActions({
setFilter: '__setFilter__',
setAllFilters: '__setAllFilters__'
})
const propTypes = {
// General
@@ -17,12 +18,12 @@ const propTypes = {
filterFn: PropTypes.func,
filterAll: PropTypes.bool,
canFilter: PropTypes.bool,
Filter: PropTypes.any,
Filter: PropTypes.any
})
),
filterFn: PropTypes.func,
manualFilters: PropTypes.bool,
manualFilters: PropTypes.bool
}
export const useFilters = props => {
@@ -32,14 +33,26 @@ export const useFilters = props => {
debug,
rows,
columns,
filterTypes: userFilterTypes,
defaultFilter = filterTypes.text,
filterFn = defaultFilterFn,
manualFilters,
disableFilters,
hooks,
state: [{ filters }, setState],
state: [{ filters }, setState]
} = props
columns.forEach(column => {
const { id, accessor, canFilter } = column
column.canFilter = accessor
? getFirstDefined(
canFilter,
disableFilters === true ? false : undefined,
true
)
: false
// Was going to add this to the filter hook
column.filterValue = filters[id]
})
const setFilter = (id, val) => {
return setState(old => {
if (typeof val === 'undefined') {
@@ -47,8 +60,8 @@ export const useFilters = props => {
return {
...old,
filters: {
...rest,
},
...rest
}
}
}
@@ -56,8 +69,8 @@ export const useFilters = props => {
...old,
filters: {
...filters,
[id]: val,
},
[id]: val
}
}
}, actions.setFilter)
}
@@ -66,32 +79,17 @@ export const useFilters = props => {
return setState(old => {
return {
...old,
filters,
filters
}
}, actions.setAllFilters)
}
hooks.columns.push(columns => {
columns.forEach(column => {
const { id, accessor, canFilter } = column
// Determine if a column is filterable
column.canFilter = accessor
? getFirstDefined(
canFilter,
disableFilters === true ? false : undefined,
true
)
: false
// Provide the column a way of updating the filter value
column.setFilter = val => setFilter(column.id, val)
// Provide the current filter value to the column for
// convenience
column.filterValue = filters[id]
if (column.canFilter) {
column.setFilter = val => setFilter(column.id, val)
}
})
return columns
})
@@ -116,33 +114,15 @@ export const useFilters = props => {
return filteredSoFar
}
// Look up filter functions in this order:
// column function
// column string lookup on user filters
// column string lookup on built-in filters
// default function
// default string lookup on user filters
// default string lookup on built-in filters
const filterMethod =
isFunction(column.filter) ||
(userFilterTypes || {})[column.filter] ||
filterTypes[column.filter] ||
isFunction(defaultFilter) ||
(userFilterTypes || {})[defaultFilter] ||
filterTypes[defaultFilter]
const filterMethod = column.filterMethod || filterFn
if (!filterMethod) {
console.warn(
`Could not find a valid 'column.filter' for column with the ID: ${
column.id
}.`
)
return filteredSoFar
// If 'filterAll' is set to true, pass the entire dataset to the filter method
if (column.filterAll) {
return filterMethod(filteredSoFar, columnID, filterValue, column)
}
// Pass the rows, id, filterValue and column to the filterMethod
// to get the filtered rows back
return filterMethod(filteredSoFar, columnID, filterValue, column)
return filteredSoFar.filter(row =>
filterMethod(row, columnID, filterValue, column)
)
},
rows
)
@@ -154,7 +134,7 @@ export const useFilters = props => {
}
return {
...row,
subRows: filterRows(row.subRows),
subRows: filterRows(row.subRows)
}
})
@@ -170,20 +150,12 @@ export const useFilters = props => {
}
return filterRows(rows)
}, [
manualFilters,
filters,
debug,
rows,
columns,
userFilterTypes,
defaultFilter,
])
}, [rows, filters, manualFilters])
return {
...props,
setFilter,
setAllFilters,
rows: filteredRows,
rows: filteredRows
}
}
@@ -5,7 +5,7 @@ import { getFirstDefined, sum } from '../utils'
export const actions = {}
const propTypes = {
defaultFlex: PropTypes.number,
defaultFlex: PropTypes.number
}
export const useFlexLayout = props => {
@@ -16,14 +16,19 @@ export const useFlexLayout = props => {
hooks: {
columns: columnsHooks,
getRowProps,
getHeaderGroupProps,
getHeaderRowProps,
getHeaderProps,
getCellProps,
},
getCellProps
}
} = props
columnsHooks.push((columns, api) => {
const visibleColumns = columns.filter(column => column.visible)
const visibleColumns = columns.filter(column => {
column.visible =
typeof column.show === 'function' ? column.show(api) : !!column.show
return column.visible
})
const columnMeasurements = {}
let sumWidth = 0
@@ -47,33 +52,47 @@ export const useFlexLayout = props => {
const rowStyles = {
style: {
display: 'flex',
minWidth: `${sumWidth}px`,
},
minWidth: `${sumWidth}px`
}
}
api.rowStyles = rowStyles
getRowProps.push(() => rowStyles)
getHeaderGroupProps.push(() => rowStyles)
getHeaderRowProps.push(() => rowStyles)
getHeaderProps.push(column => ({
style: {
boxSizing: 'border-box',
...getStylesForColumn(column, columnMeasurements, defaultFlex, api),
},
...getStylesForColumn(column, columnMeasurements, defaultFlex, api)
}
// [refKey]: el => {
// renderedCellInfoRef.current[key] = {
// column,
// el
// };
// },
}))
getCellProps.push(cell => {
return {
style: {
// display: 'block',
// boxSizing: 'border-box',
...getStylesForColumn(
cell.column,
columnMeasurements,
defaultFlex,
undefined,
api
),
},
)
}
// [refKey]: el => {
// renderedCellInfoRef.current[columnPathStr] = {
// column,
// el
// };
// }
}
})
@@ -96,7 +115,7 @@ function getStylesForColumn(column, columnMeasurements, defaultFlex, api) {
return {
flex: `${flex} 0 auto`,
width: `${width}px`,
maxWidth: `${maxWidth}px`,
maxWidth: `${maxWidth}px`
}
}
@@ -108,7 +127,6 @@ function getSizesForColumn(
) {
if (columns) {
columns = columns
.filter(col => col.show || col.visible)
.map(column =>
getSizesForColumn(column, columnMeasurements, defaultFlex, api)
)
@@ -125,7 +143,7 @@ function getSizesForColumn(
return {
flex,
width,
maxWidth,
maxWidth
}
}
@@ -135,6 +153,63 @@ function getSizesForColumn(
width === 'auto'
? columnMeasurements[id] || defaultFlex
: getFirstDefined(width, minWidth, defaultFlex),
maxWidth,
maxWidth
}
}
// const resetRefs = () => {
// if (debug) console.info("resetRefs");
// renderedCellInfoRef.current = {};
// };
// const calculateAutoWidths = () => {
// RAF(() => {
// const newColumnMeasurements = {};
// Object.values(renderedCellInfoRef.current).forEach(({ column, el }) => {
// if (!el) {
// return;
// }
// let measurement = 0;
// const measureChildren = children => {
// if (children) {
// [].slice.call(children).forEach(child => {
// measurement = Math.max(
// measurement,
// Math.ceil(child.offsetWidth) || 0
// );
// measureChildren(child.children);
// });
// }
// return measurement;
// };
// const parentDims = getElementDimensions(el);
// measureChildren(el.children);
// newColumnMeasurements[column.id] = Math.max(
// newColumnMeasurements[column.id] || 0,
// measurement + parentDims.paddingLeft + parentDims.paddingRight
// );
// });
// const oldKeys = Object.keys(columnMeasurements);
// const newKeys = Object.keys(newColumnMeasurements);
// const needsUpdate =
// oldKeys.length !== newKeys.length ||
// oldKeys.some(key => {
// return columnMeasurements[key] !== newColumnMeasurements[key];
// });
// if (needsUpdate) {
// setState(old => {
// return {
// ...old,
// columnMeasurements: newColumnMeasurements
// };
// }, actions.updateAutoWidth);
// }
// });
// };
@@ -3,17 +3,19 @@ import PropTypes from 'prop-types'
import * as aggregations from '../aggregations'
import { addActions, actions } from '../actions'
import { defaultState } from '../hooks/useTableState'
import { defaultState } from './useTableState'
import {
mergeProps,
applyPropHooks,
defaultGroupByFn,
getFirstDefined,
getFirstDefined
} from '../utils'
defaultState.groupBy = []
addActions('toggleGroupBy')
addActions({
toggleGroupBy: '__toggleGroupBy__'
})
const propTypes = {
// General
@@ -21,12 +23,12 @@ const propTypes = {
PropTypes.shape({
aggregate: PropTypes.func,
canGroupBy: PropTypes.bool,
Aggregated: PropTypes.any,
Aggregated: PropTypes.any
})
),
groupByFn: PropTypes.func,
manualGrouping: PropTypes.bool,
aggregations: PropTypes.object,
aggregations: PropTypes.object
}
export const useGroupBy = props => {
@@ -41,7 +43,7 @@ export const useGroupBy = props => {
disableGrouping,
aggregations: userAggregations = {},
hooks,
state: [{ groupBy }, setState],
state: [{ groupBy }, setState]
} = props
columns.forEach(column => {
@@ -50,10 +52,10 @@ export const useGroupBy = props => {
column.canGroupBy = accessor
? getFirstDefined(
canGroupBy,
disableGrouping === true ? false : undefined,
true
)
canGroupBy,
disableGrouping === true ? false : undefined,
true
)
: false
column.Aggregated = column.Aggregated || column.Cell
@@ -66,12 +68,12 @@ export const useGroupBy = props => {
if (resolvedToggle) {
return {
...old,
groupBy: [...groupBy, id],
groupBy: [...groupBy, id]
}
}
return {
...old,
groupBy: groupBy.filter(d => d !== id),
groupBy: groupBy.filter(d => d !== id)
}
}, actions.toggleGroupBy)
}
@@ -95,14 +97,14 @@ export const useGroupBy = props => {
{
onClick: canGroupBy
? e => {
e.persist()
column.toggleGroupBy()
}
e.persist()
column.toggleGroupBy()
}
: undefined,
style: {
cursor: canGroupBy ? 'pointer' : undefined,
cursor: canGroupBy ? 'pointer' : undefined
},
title: 'Toggle GroupBy',
title: 'Toggle GroupBy'
},
applyPropHooks(api.hooks.getGroupByToggleProps, column, api),
props
@@ -167,7 +169,7 @@ export const useGroupBy = props => {
values,
subRows,
depth,
index,
index
}
return row
}
@@ -178,18 +180,10 @@ export const useGroupBy = props => {
// Assign the new data
return groupRecursively(rows, groupBy)
}, [
manualGroupBy,
groupBy,
debug,
rows,
columns,
userAggregations,
groupByFn,
])
}, [rows, groupBy, columns, manualGroupBy])
return {
...props,
rows: groupedRows,
rows: groupedRows
}
}
@@ -1,33 +1,28 @@
import React from 'react'
import { useMemo, useLayoutEffect } from 'react'
import PropTypes from 'prop-types'
//
import { addActions, actions } from '../actions'
import { defaultState } from '../hooks/useTableState'
import { defaultState } from './useTableState'
defaultState.pageSize = 10
defaultState.pageIndex = 0
addActions('pageChange', 'pageSizeChange')
addActions({
pageChange: '__pageChange__'
})
const propTypes = {
// General
manualPagination: PropTypes.bool,
manualPagination: PropTypes.bool
}
// SSR has issues with useLayoutEffect still, so pony-fill with useEffect during SSR
let useLayoutEffect =
typeof window !== 'undefined' && process.env.NODE_ENV === 'production'
? React.useLayoutEffect
: React.useEffect
export const usePagination = props => {
PropTypes.checkPropTypes(propTypes, props, 'property', 'usePagination')
const {
rows,
manualPagination,
disablePageResetOnDataChange,
debug,
state: [
{
@@ -36,34 +31,27 @@ export const usePagination = props => {
pageCount: userPageCount,
filters,
groupBy,
sortBy,
sortBy
},
setState,
],
setState
]
} = props
const pageOptions = React.useMemo(
() => [...new Array(userPageCount)].map((d, i) => i),
[userPageCount]
)
const rowDep = disablePageResetOnDataChange ? null : rows
useLayoutEffect(() => {
setState(
old => ({
...old,
pageIndex: 0,
pageIndex: 0
}),
actions.pageChange
)
}, [setState, rowDep, filters, groupBy, sortBy])
}, [filters, groupBy, sortBy])
const { pages, pageCount } = React.useMemo(() => {
const { pages, pageCount } = useMemo(() => {
if (manualPagination) {
return {
pages: [rows],
pageCount: userPageCount,
pageCount: userPageCount
}
}
if (debug) console.info('getPages')
@@ -84,10 +72,11 @@ export const usePagination = props => {
return {
pages,
pageCount,
pageOptions,
pageOptions
}
}, [manualPagination, debug, rows, pageOptions, userPageCount, pageSize])
}, [rows, pageSize, userPageCount])
const pageOptions = [...new Array(pageCount)].map((d, i) => i)
const page = manualPagination ? rows : pages[pageIndex] || []
const canPreviousPage = pageIndex > 0
const canNextPage = pageIndex < pageCount - 1
@@ -100,7 +89,7 @@ export const usePagination = props => {
}
return {
...old,
pageIndex,
pageIndex
}
}, actions.pageChange)
}
@@ -120,24 +109,21 @@ export const usePagination = props => {
return {
...old,
pageIndex,
pageSize,
pageSize
}
}, actions.pageSizeChange)
}, actions.setPageSize)
}
return {
...props,
pages,
pageOptions,
pageCount,
page,
canPreviousPage,
canNextPage,
gotoPage,
previousPage,
nextPage,
setPageSize,
pageIndex,
pageSize,
setPageSize
}
}
+6 -6
View File
@@ -1,8 +1,8 @@
import React from 'react'
import { useMemo } from 'react'
import PropTypes from 'prop-types'
const propTypes = {
subRowsKey: PropTypes.string,
subRowsKey: PropTypes.string
}
export const useRows = props => {
@@ -10,7 +10,7 @@ export const useRows = props => {
const { debug, columns, subRowsKey = 'subRows', data } = props
const accessedRows = React.useMemo(() => {
const accessedRows = useMemo(() => {
if (debug) console.info('getAccessedRows')
// Access the row's data
@@ -29,7 +29,7 @@ export const useRows = props => {
path: [i], // used to create a key for each row even if not nested
subRows,
depth,
cells: [{}], // This is a dummy cell
cells: [{}] // This is a dummy cell
}
// Override common array functions (and the dummy cell's getCellProps function)
@@ -57,10 +57,10 @@ export const useRows = props => {
// Use the resolved data
return data.map((d, i) => accessRow(d, i))
}, [debug, data, subRowsKey, columns])
}, [data, columns])
return {
...props,
rows: accessedRows,
rows: accessedRows
}
}
+37
View File
@@ -0,0 +1,37 @@
export const useSimpleLayout = props => {
const {
hooks: {
columns: columnsHooks,
getHeaderProps,
getCellProps
}
} = props
columnsHooks.push((columns, api) => {
columns.forEach(column => {
column.visible =
typeof column.show === 'function' ? column.show(api) : !!column.show
})
getHeaderProps.push(column => ({
style: {
boxSizing: 'border-box',
width: column.width !== undefined ? `${column.width}px` : 'auto'
}
}))
getCellProps.push(cell => {
return {
style: {
boxSizing: 'border-box',
width: cell.column.width !== undefined ? `${cell.column.width}px` : 'auto'
}
}
})
return columns
})
return props
}
@@ -2,37 +2,34 @@ import { useMemo } from 'react'
import PropTypes from 'prop-types'
import { addActions, actions } from '../actions'
import { defaultState } from '../hooks/useTableState'
import * as sortTypes from '../sortTypes'
import { defaultState } from './useTableState'
import {
mergeProps,
applyPropHooks,
getFirstDefined,
defaultOrderByFn,
isFunction,
defaultSortByFn
} from '../utils'
defaultState.sortBy = []
addActions('sortByChange')
addActions({
sortByChange: '__sortByChange__'
})
const propTypes = {
// General
columns: PropTypes.arrayOf(
PropTypes.shape({
sortBy: PropTypes.func,
defaultSortDesc: PropTypes.bool,
sortByFn: PropTypes.func,
efaultSortDesc: PropTypes.bool
})
),
orderByFn: PropTypes.func,
sortTypes: PropTypes.object,
defaultSortType: PropTypes.oneOfType([PropTypes.string, PropTypes.func]),
sortByFn: PropTypes.func,
manualSorting: PropTypes.bool,
disableSorting: PropTypes.bool,
defaultSortDesc: PropTypes.bool,
disableMultiSort: PropTypes.bool,
disableSortRemove: PropTypes.bool,
disableMultiRemove: PropTypes.bool,
disableMultiSort: PropTypes.bool
}
export const useSortBy = props => {
@@ -43,26 +40,22 @@ export const useSortBy = props => {
rows,
columns,
orderByFn = defaultOrderByFn,
defaultSort = 'alphanumeric',
sortTypes: userSortTypes,
sortByFn = defaultSortByFn,
manualSorting,
disableSorting,
defaultSortDesc,
disableSortRemove,
disableMultiRemove,
disableMultiSort,
hooks,
state: [{ sortBy }, setState],
state: [{ sortBy }, setState]
} = props
columns.forEach(column => {
const { accessor, canSortBy } = column
column.canSortBy = accessor
? getFirstDefined(
canSortBy,
disableSorting === true ? false : undefined,
true
)
canSortBy,
disableSorting === true ? false : undefined,
true
)
: false
})
@@ -80,77 +73,77 @@ export const useSortBy = props => {
// Find any existing sortBy for this column
const existingSortBy = sortBy.find(d => d.id === columnID)
const existingIndex = sortBy.findIndex(d => d.id == columnID)
const hasDescDefined = typeof desc !== 'undefined' && desc !== null
let newSortBy = []
// What should we do with this sort action?
// What should we do with this filter?
let action
if (!disableMultiSort && multi) {
if (existingSortBy) {
action = 'toggle'
if (!multi) {
if (sortBy.length <= 1 && existingSortBy) {
if (existingSortBy.desc) {
action = 'remove'
} else {
action = 'toggle'
}
} else {
action = 'add'
action = 'replace'
}
} else {
// Normal mode
if (existingIndex !== sortBy.length - 1) {
action = 'replace'
} else if (existingSortBy) {
action = 'toggle'
if (!existingSortBy) {
action = 'add'
} else {
action = 'replace'
if (hasDescDefined) {
action = 'set'
} else {
action = 'toggle'
}
}
}
// Handle toggle states that will remove the sortBy
if (
action === 'toggle' && // Must be toggling
!disableSortRemove && // If disableSortRemove, disable in general
!hasDescDefined && // Must not be setting desc
(multi ? !disableMultiRemove : true) && // If multi, don't allow if disableMultiRemove
((existingSortBy && // Finally, detect if it should indeed be removed
(existingSortBy.desc && !resolvedDefaultSortDesc)) ||
(!existingSortBy.desc && resolvedDefaultSortDesc))
) {
action = 'remove'
}
if (action === 'replace') {
newSortBy = [
{
id: columnID,
desc: hasDescDefined ? desc : resolvedDefaultSortDesc,
},
desc: hasDescDefined ? desc : resolvedDefaultSortDesc
}
]
} else if (action === 'add') {
newSortBy = [
...sortBy,
{
id: columnID,
desc: hasDescDefined ? desc : resolvedDefaultSortDesc,
},
desc: hasDescDefined ? desc : resolvedDefaultSortDesc
}
]
} else if (action === 'toggle') {
// This flips (or sets) the
} else if (action === 'set') {
newSortBy = sortBy.map(d => {
if (d.id === columnID) {
return {
...d,
desc: hasDescDefined ? desc : !existingSortBy.desc,
desc
}
}
return d
})
} else if (action === 'toggle') {
newSortBy = sortBy.map(d => {
if (d.id === columnID) {
return {
...d,
desc: !existingSortBy.desc
}
}
return d
})
} else if (action === 'remove') {
newSortBy = sortBy.filter(d => d.id !== columnID)
newSortBy = []
}
return {
...old,
sortBy: newSortBy,
sortBy: newSortBy
}
}, actions.sortByChange)
}
@@ -175,17 +168,17 @@ export const useSortBy = props => {
{
onClick: canSortBy
? e => {
e.persist()
column.toggleSortBy(
undefined,
!api.disableMultiSort && e.shiftKey
)
}
e.persist()
column.toggleSortBy(
undefined,
!api.disableMultiSort && e.shiftKey
)
}
: undefined,
style: {
cursor: canSortBy ? 'pointer' : undefined,
cursor: canSortBy ? 'pointer' : undefined
},
title: 'Toggle SortBy',
title: 'Toggle SortBy'
},
applyPropHooks(api.hooks.getSortByToggleProps, column, api),
props
@@ -212,11 +205,13 @@ export const useSortBy = props => {
}
if (debug) console.info('getSortedRows')
const sortTypesByColumnID = {}
const sortMethodsByColumnID = {}
columns.forEach(col => {
sortTypesByColumnID[col.id] = col.sortBy
})
columns
.filter(col => col.sortMethod)
.forEach(col => {
sortMethodsByColumnID[col.id] = col.sortMethod
})
const sortData = rows => {
// Use the orderByFn to compose multiple sortBy's together.
@@ -226,32 +221,21 @@ export const useSortBy = props => {
rows,
sortBy.map(sort => {
// Support custom sorting methods for each column
const columnSort = sortTypesByColumnID[sort.id]
// Look up sortBy functions in this order:
// column function
// column string lookup on user sortType
// column string lookup on built-in sortType
// default function
// default string lookup on user sortType
// default string lookup on built-in sortType
const sortMethod =
isFunction(columnSort) ||
(userSortTypes || {})[columnSort] ||
sortTypes[columnSort] ||
isFunction(defaultSort) ||
(userSortTypes || {})[defaultSort] ||
sortTypes[defaultSort]
const columnSortBy = sortMethodsByColumnID[sort.id]
// Return the correct sortFn
return (a, b) =>
sortMethod(a.values[sort.id], b.values[sort.id], sort.desc)
(columnSortBy || sortByFn)(
a.values[sort.id],
b.values[sort.id],
sort.desc
)
}),
// Map the directions
sortBy.map(d => !d.desc)
)
// If there are sub-rows, sort them
// TODO: this should be optimized. Not good to loop again
sortedData.forEach(row => {
if (!row.subRows) {
return
@@ -263,19 +247,10 @@ export const useSortBy = props => {
}
return sortData(rows)
}, [
manualSorting,
sortBy,
debug,
columns,
rows,
orderByFn,
userSortTypes,
defaultSort,
])
}, [rows, columns, sortBy, manualSorting])
return {
...props,
rows: sortedRows,
rows: sortedRows
}
}
+35 -52
View File
@@ -1,32 +1,24 @@
import PropTypes from 'prop-types'
//
import { applyHooks, applyPropHooks, mergeProps, flexRender } from '../utils'
import { flexRender, applyHooks, applyPropHooks, mergeProps } from '../utils'
import { useTableState } from './useTableState'
import { useColumns } from './useColumns'
import { useRows } from './useRows'
const renderErr =
'You must specify a render "type". This could be "Header", "Filter", or any other custom renderers you have set on your column.'
const propTypes = {
// General
data: PropTypes.array.isRequired,
debug: PropTypes.bool,
debug: PropTypes.bool
}
const renderErr =
'You must specify a valid render component. This could be "column.Cell", "column.Header", "column.Filter", "column.Aggregated" or any other custom renderer component.'
export const useTable = (props, ...plugins) => {
// Validate props
PropTypes.checkPropTypes(propTypes, props, 'property', 'useTable')
// Destructure props
let {
data,
state: userState,
useColumns: userUseColumns = useColumns,
useRows: userUseRows = useRows,
debug,
} = props
let { data = [], state: userState, debug } = props
debug = process.env.NODE_ENV === 'production' ? false : debug
@@ -44,11 +36,12 @@ export const useTable = (props, ...plugins) => {
headerGroups: [],
rows: [],
row: [],
renderableRows: [],
getTableProps: [],
getRowProps: [],
getHeaderGroupProps: [],
getHeaderRowProps: [],
getHeaderProps: [],
getCellProps: [],
getCellProps: []
}
// The initial api
@@ -56,60 +49,50 @@ export const useTable = (props, ...plugins) => {
...props,
data,
state,
hooks,
hooks
}
if (debug) console.time('hooks')
// Loop through plugins to build the api out
api = [userUseColumns, userUseRows, ...plugins]
.filter(Boolean)
.reduce((prev, next) => next(prev), api)
api = plugins.filter(Boolean).reduce((prev, next) => next(prev), api)
if (debug) console.timeEnd('hooks')
// Determine column visibility
api.columns.forEach(column => {
column.visible =
typeof column.show === 'function' ? column.show(api) : !!column.show
})
// Run the beforeRender hook
if (debug) console.time('hooks.beforeRender')
applyHooks(api.hooks.beforeRender, undefined, api)
if (debug) console.timeEnd('hooks.beforeRender')
// Allow hooks to decorate columns
if (debug) console.time('hooks.columns')
api.columns = applyHooks(api.hooks.columns, api.columns, api)
if (debug) console.timeEnd('hooks.columns')
// Allow hooks to decorate headers
if (debug) console.time('hooks.headers')
api.headers = applyHooks(api.hooks.headers, api.headers, api)
if (debug) console.timeEnd('hooks.headers')
;[...api.columns, ...api.headers].forEach(column => {
// Give columns/headers rendering power
column.render = (type, userProps = {}) => {
const Comp = typeof type === 'string' ? column[type] : type
if (typeof Comp === 'undefined') {
if (!type) {
throw new Error(renderErr)
}
return flexRender(Comp, {
return flexRender(column[type], {
...api,
...column,
...userProps,
...userProps
})
}
// Give columns/headers a default getHeaderProps
// Give columns/headers getHeaderProps
column.getHeaderProps = props =>
mergeProps(
{
key: ['header', column.id].join('_'),
colSpan: column.columns ? column.columns.length : 1,
key: ['header', column.id].join('_')
},
applyPropHooks(api.hooks.getHeaderProps, column, api),
props
)
})
// Allow hooks to decorate headerGroups
if (debug) console.time('hooks.headerGroups')
api.headerGroups = applyHooks(
api.hooks.headerGroups,
@@ -133,12 +116,12 @@ export const useTable = (props, ...plugins) => {
// Give headerGroups getRowProps
if (headerGroup.headers.length) {
headerGroup.getHeaderGroupProps = (props = {}) =>
headerGroup.getRowProps = (props = {}) =>
mergeProps(
{
key: [`header${i}`].join('_'),
key: [`header${i}`].join('_')
},
applyPropHooks(api.hooks.getHeaderGroupProps, headerGroup, api),
applyPropHooks(api.hooks.getHeaderRowProps, headerGroup, api),
props
)
return true
@@ -161,7 +144,7 @@ export const useTable = (props, ...plugins) => {
row.getRowProps = props =>
mergeProps(
{ key: ['row', ...path].join('_') },
applyPropHooks(api.hooks.getRowProps, row, api),
applyHooks(api.hooks.getRowProps, row, api),
props
)
@@ -175,33 +158,30 @@ export const useTable = (props, ...plugins) => {
const cell = {
column,
row,
value: row.values[column.id],
value: row.values[column.id]
}
// Give each cell a getCellProps base
cell.getCellProps = props => {
const columnPathStr = [path, column.id].join('_')
return mergeProps(
{
key: ['cell', columnPathStr].join('_'),
key: ['cell', columnPathStr].join('_')
},
applyPropHooks(api.hooks.getCellProps, cell, api),
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)
if (!type) {
throw new Error(
'You must specify a render "type". This could be "Cell", "Header", "Filter", "Aggregated" or any other custom renderers you have set on your column.'
)
}
return flexRender(Comp, {
return flexRender(column[type], {
...api,
...cell,
...userProps,
...userProps
})
}
@@ -212,5 +192,8 @@ export const useTable = (props, ...plugins) => {
api.getTableProps = userProps =>
mergeProps(applyPropHooks(api.hooks.getTableProps, api), userProps)
api.getRowProps = userProps =>
mergeProps(applyPropHooks(api.hooks.getRowProps, api), userProps)
return api
}
+16 -38
View File
@@ -1,6 +1,4 @@
import React from 'react'
//
import { types } from '../actions'
import { useState, useMemo } from 'react'
export const defaultState = {}
@@ -8,49 +6,29 @@ const defaultReducer = (old, newState) => newState
export const useTableState = (
initialState = {},
overrides,
{ reducer = defaultReducer, useState: userUseState = React.useState } = {}
overrides = {},
{ reducer = defaultReducer, useState: userUseState = useState } = {}
) => {
let [state, setState] = userUseState({
...defaultState,
...initialState,
...initialState
})
const overriddenState = React.useMemo(() => {
const overriddenState = useMemo(() => {
const newState = {
...state,
}
if (overrides) {
Object.keys(overrides).forEach(key => {
newState[key] = overrides[key]
})
...state
}
Object.keys(overrides).forEach(key => {
newState[key] = overrides[key]
})
return newState
}, [overrides, state])
}, [state, ...Object.values(overrides)])
const overriddenStateRef = React.useRef()
overriddenStateRef.current = overriddenState
const reducedSetState = (updater, type) =>
setState(old => {
const newState = updater(old)
return reducer(old, newState, type)
})
const reducedSetState = React.useCallback(
(updater, type) => {
if (!types[type]) {
console.info({
stateUpdaterFn: updater,
actionType: type,
currentState: overriddenStateRef.current,
})
throw new Error('Detected an unknown table action! (Details Above)')
}
return setState(old => {
const newState = updater(old)
return reducer(old, newState, type)
})
},
[reducer, setState]
)
return React.useMemo(() => [overriddenState, reducedSetState], [
overriddenState,
reducedSetState,
])
return [overriddenState, reducedSetState]
}
@@ -1,13 +1,13 @@
import React from 'react'
import { useState } from 'react'
// Token pagination behaves a bit differently from
// index based pagination. This hook aids in that process.
export const useTokenPagination = () => {
const [pageToken, setPageToken] = React.useState()
const [nextPageToken, setNextPageToken] = React.useState()
const [previousPageTokens, setPreviousPageTokens] = React.useState([])
const [pageIndex, setPageIndex] = React.useState(0)
const [pageToken, setPageToken] = useState()
const [nextPageToken, setNextPageToken] = useState()
const [previousPageTokens, setPreviousPageTokens] = useState([])
const [pageIndex, setPageIndex] = useState(0)
// Since we're using pagination tokens intead of index, we need
// to be a bit clever with page-like navigation here.
@@ -46,6 +46,6 @@ export const useTokenPagination = () => {
nextPage,
canPreviousPage,
canNextPage,
resetPagination,
resetPagination
}
}
+10 -11
View File
@@ -1,14 +1,13 @@
import * as utils from './utils'
export { utils }
export { useTable } from './hooks/useTable'
export { useColumns } from './hooks/useColumns'
export { useRows } from './hooks/useRows'
export { useTableState, defaultState } from './hooks/useTableState'
export { useExpanded } from './plugin-hooks/useExpanded'
export { useFilters } from './plugin-hooks/useFilters'
export { useGroupBy } from './plugin-hooks/useGroupBy'
export { useSortBy } from './plugin-hooks/useSortBy'
export { usePagination } from './plugin-hooks/usePagination'
export { useFlexLayout } from './plugin-hooks/useFlexLayout'
export { useTokenPagination } from './plugin-hooks/useTokenPagination'
export { actions, addActions } from './actions'
export { useSimpleLayout } from './hooks/useSimpleLayout'
export { useExpanded } from './hooks/useExpanded'
export { useFilters } from './hooks/useFilters'
export { useGroupBy } from './hooks/useGroupBy'
export { useSortBy } from './hooks/useSortBy'
export { usePagination } from './hooks/usePagination'
export { useTableState } from './hooks/useTableState'
export { useFlexLayout } from './hooks/useFlexLayout'
export { useTokenPagination } from './hooks/useTokenPagination'
export { actions } from './actions'
-77
View File
@@ -1,77 +0,0 @@
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) => {
// Force to strings (or "" for unsupported types)
a = toString(a)
b = toString(b)
// Split on number groups, but keep the delimiter
// Then remove falsey split values
a = a.split(reSplitAlphaNumeric).filter(Boolean)
b = b.split(reSplitAlphaNumeric).filter(Boolean)
// While
while (a.length && b.length) {
let aa = a.shift()
let bb = b.shift()
const an = parseInt(aa, 10)
const bn = parseInt(bb, 10)
const combo = [an, bn].sort()
// Both are string
if (isNaN(combo[0])) {
if (aa > bb) {
return 1
}
if (bb > aa) {
return -1
}
continue
}
// One is a string, one is a number
if (isNaN(combo[1])) {
return isNaN(an) ? -1 : 1
}
// Both are numbers
if (an > bn) {
return 1
}
if (bn > an) {
return -1
}
}
return a.length - b.length
}
export function datetime(a, b) {
a = a.getTime()
b = b.getTime()
return numeric(a, b)
}
export function numeric(a, b) {
return a === b ? 0 : a > b ? 1 : -1
}
// Utils
function toString(a) {
if (typeof a === 'number') {
if (isNaN(a) || a === Infinity || a === -Infinity) {
return ''
}
return String(a)
}
if (typeof a === 'string') {
return a
}
return ''
}
+40 -15
View File
@@ -28,6 +28,25 @@ export function defaultOrderByFn(arr, funcs, dirs) {
})
}
export function defaultSortByFn(a, b, desc) {
// force null and undefined to the bottom
a = a === null || a === undefined ? '' : a
b = b === null || b === undefined ? '' : b
// force any string values to lowercase
a = typeof a === 'string' ? a.toLowerCase() : a
b = typeof b === 'string' ? b.toLowerCase() : b
// Return either 1 or -1 to indicate a sort priority
if (a > b) {
return 1
}
if (a < b) {
return -1
}
// returning 0, undefined or any falsey value will defer to the next
// sorting mechanism or eventually the columns index via the orderByFn
return 0
}
export function getFirstDefined(...args) {
for (let i = 0; i < args.length; i += 1) {
if (typeof args[i] !== 'undefined') {
@@ -48,6 +67,14 @@ export function defaultGroupByFn(rows, grouper) {
}, {})
}
export function defaultFilterFn(row, id, value, column) {
return row.values[id] !== undefined
? String(row.values[id])
.toLowerCase()
.includes(String(value).toLowerCase())
: true
}
export function setBy(obj = {}, path, value) {
const recurse = (obj, depth = 0) => {
const key = path[depth]
@@ -56,7 +83,7 @@ export function setBy(obj = {}, path, value) {
depth === path.length - 1 ? value : recurse(target, depth + 1)
return {
...obj,
[key]: subValue,
[key]: subValue
}
}
@@ -68,11 +95,11 @@ export function getElementDimensions(element) {
const style = window.getComputedStyle(element)
const margins = {
left: parseInt(style.marginLeft),
right: parseInt(style.marginRight),
right: parseInt(style.marginRight)
}
const padding = {
left: parseInt(style.paddingLeft),
right: parseInt(style.paddingRight),
right: parseInt(style.paddingRight)
}
return {
left: Math.ceil(rect.left),
@@ -84,13 +111,17 @@ export function getElementDimensions(element) {
marginRight: margins.right,
paddingLeft: padding.left,
paddingRight: padding.right,
scrollWidth: element.scrollWidth,
scrollWidth: element.scrollWidth
}
}
export function flexRender(Comp, props) {
if (typeof Comp === 'function' || typeof Comp === 'object') {
return <Comp {...props} />
if (typeof Comp === 'function') {
return Object.getPrototypeOf(Comp).isReactComponent ? (
<Comp {...props} />
) : (
Comp(props)
)
}
return Comp
}
@@ -103,9 +134,9 @@ export const mergeProps = (...groups) => {
...rest,
style: {
...(props.style || {}),
...style,
...style
},
className: [props.className, className].filter(Boolean).join(' '),
className: [props.className, className].filter(Boolean).join(' ')
}
})
return props
@@ -121,7 +152,7 @@ export const warnUnknownProps = props => {
if (Object.keys(props).length) {
throw new Error(
`Unknown options passed to useReactTable:
${JSON.stringify(props, null, 2)}`
)
}
@@ -131,12 +162,6 @@ export function sum(arr) {
return arr.reduce((prev, curr) => prev + curr, 0)
}
export function isFunction(a) {
if (typeof a === 'function') {
return a
}
}
function makePathArray(obj) {
return flattenDeep(obj)
.join('.')
View File
+187 -3662
View File
File diff suppressed because it is too large Load Diff