window-table
Version:
Windowing Table for React based on React Window
270 lines (258 loc) • 14.1 kB
JavaScript
import { useReducer as useReducer$1, createElement, useMemo as useMemo$3, memo as memo$1, createContext as createContext$1, Fragment, useContext as useContext$1, useState as useState$1 } from 'react';
import { areEqual as areEqual$1, FixedSizeList as FixedSizeList$1, VariableSizeList as VariableSizeList$1 } from 'react-window';
import debounce from 'lodash/debounce';
import isEqual from 'lodash/isEqual';
import AutoSizer from 'react-virtualized-auto-sizer';
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __rest(s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0)
t[p[i]] = s[p[i]];
return t;
}
var useMemo = useMemo$3, useReducer = useReducer$1;
// Define the initial state of dimensions
// Also to be used as a state which will not trigger a re-render on changes
// So that we can change state from the useReducer, only when required
var cache = {
header: [0, 0],
row: [0, 0],
table: [0, 0]
};
var reducer = function (state, _a) {
var _b;
var entity = _a.entity, dimensions = _a.dimensions;
if (entity) {
// Keep updates in cache
cache = __assign({}, cache, (_b = {}, _b[entity] = dimensions, _b));
if (!isEqual(state[entity], cache[entity])) {
return cache;
}
}
return state;
};
var Measurer = function (_a) {
var measure = _a.measure, entity = _a.entity, debounceWait = _a.debounceWait;
var debounced = useMemo(function () { return debounce(measure, debounceWait, { leading: true }); }, [measure, debounceWait]);
var dispatch = debounceWait > 0 ? debounced : measure;
return (createElement(AutoSizer, null, function (_a) {
var height = _a.height, width = _a.width;
dispatch({ dimensions: [height, width], entity: entity });
return null;
}));
};
var useTableMeasurer = function () {
return useReducer(reducer, cache);
};
var objectProps = ['style', 'sampleRow'];
var otherProps = [
'columns',
'data',
'rowHeight',
'height',
'width',
'className',
'rowClassName',
'classNamePrefix'
];
function areTablePropsEqual(prev, next) {
var areObjectPropsEqual = objectProps.every(function (propName) {
return JSON.stringify(prev[propName]) === JSON.stringify(next[propName]);
});
if (!areObjectPropsEqual) {
return false;
}
return otherProps.every(function (propName) { return prev[propName] === next[propName]; });
}
var FixedSizeList = FixedSizeList$1, VariableSizeList = VariableSizeList$1, areEqual = areEqual$1;
var useContext = useContext$1, createContext = createContext$1, memo = memo$1, useMemo$1 = useMemo$3;
var TableContext = createContext({
columns: [],
data: [],
Cell: 'div',
Row: 'div',
classNamePrefix: '',
rowClassName: ''
});
var RowCells = function (_a) {
var columns = _a.columns, classNamePrefix = _a.classNamePrefix, datum = _a.datum, Cell = _a.Cell, _b = _a.index, index = _b === void 0 ? 0 : _b;
return (createElement(Fragment, null, columns.map(function (column, i) {
var key = column.key, width = column.width, _a = column.Component, Component = _a === void 0 ? 'div' : _a;
// Using i as the key, because it doesn't matter much,
// as we are only looping through columns in one row only
return (createElement(Cell, { key: i, style: {
width: width + "px",
flexGrow: width,
display: 'inline-block',
overflow: 'auto',
boxSizing: 'border-box'
}, className: classNamePrefix + "table-cell", row: datum, column: column, index: index },
createElement(Component, { row: datum, column: column, index: index }, datum[key])));
})));
};
var RowRenderer = function (_a) {
var index = _a.index, style = _a.style;
var _b = useContext(TableContext), columns = _b.columns, data = _b.data, Cell = _b.Cell, classNamePrefix = _b.classNamePrefix, Row = _b.Row, rowClassName = _b.rowClassName;
var rowClassNameStr = useMemo$1(function () {
return typeof rowClassName === 'function' ? rowClassName(index) : rowClassName;
}, [index, rowClassName]);
return (createElement(Row, { style: __assign({}, style, { display: 'flex' }), className: "" + classNamePrefix + rowClassNameStr, index: index, row: data[index] },
createElement(RowCells, { datum: data[index], Cell: Cell, classNamePrefix: classNamePrefix, columns: columns, index: index })));
};
var MemoRowRenderer = memo(RowRenderer, areEqual);
var HeaderRowRenderer = function (_a) {
var Header = _a.Header, HeaderRow = _a.HeaderRow, DefaultHeaderCell = _a.HeaderCell, children = _a.children;
var _b = useContext(TableContext), columns = _b.columns, classNamePrefix = _b.classNamePrefix;
return (createElement(Header, { className: classNamePrefix + "table-header" },
createElement(HeaderRow, { style: {
display: 'flex'
}, className: classNamePrefix + "table-header-row" },
children,
columns.map(function (column) {
var key = column.key, width = column.width, title = column.title, _a = column.HeaderCell, HeaderCell = _a === void 0 ? DefaultHeaderCell : _a;
return (createElement(HeaderCell, { key: "header" + key, style: {
width: width + "px",
display: 'inline-block',
flexGrow: width
}, className: classNamePrefix + "table-header-cell", column: column }, title));
}))));
};
function WindowTable(_a) {
var columns = _a.columns, data = _a.data, rowHeight = _a.rowHeight, height = _a.height, width = _a.width, _b = _a.overscanCount, overscanCount = _b === void 0 ? 1 : _b, _c = _a.style, style = _c === void 0 ? {} : _c, _d = _a.Cell, Cell = _d === void 0 ? 'div' : _d, _e = _a.HeaderCell, HeaderCell = _e === void 0 ? 'div' : _e, _f = _a.Table, Table = _f === void 0 ? 'div' : _f, _g = _a.Header, Header = _g === void 0 ? 'div' : _g, _h = _a.HeaderRow, HeaderRow = _h === void 0 ? 'div' : _h, _j = _a.Row, Row = _j === void 0 ? 'div' : _j, _k = _a.Body, Body = _k === void 0 ? 'div' : _k, _l = _a.sampleRowIndex, sampleRowIndex = _l === void 0 ? 0 : _l, sampleRow = _a.sampleRow, _m = _a.className, className = _m === void 0 ? '' : _m, _o = _a.rowClassName, rowClassName = _o === void 0 ? 'table-row' : _o, _p = _a.classNamePrefix, classNamePrefix = _p === void 0 ? '' : _p, _q = _a.debounceWait, debounceWait = _q === void 0 ? 0 : _q, rest = __rest(_a, ["columns", "data", "rowHeight", "height", "width", "overscanCount", "style", "Cell", "HeaderCell", "Table", "Header", "HeaderRow", "Row", "Body", "sampleRowIndex", "sampleRow", "className", "rowClassName", "classNamePrefix", "debounceWait"]);
var List = rowHeight && typeof rowHeight === 'function'
? VariableSizeList
: FixedSizeList;
var columnWidthsSum = columns.reduce(function (sum, _a) {
var width = _a.width;
return sum + width;
}, 0);
var _r = useTableMeasurer(), dimensions = _r[0], measure = _r[1];
var _s = dimensions.table, tableHeight = _s[0], tableWidth = _s[1];
var headerHeight = dimensions.header[0];
var sampleRowHeight = dimensions.row[0];
var bodyHeight = (height || tableHeight) - headerHeight;
var effectiveWidth = width || Math.max(columnWidthsSum, tableWidth);
var tableClassName = classNamePrefix + "table " + className;
var TableBody = function (_a) {
var children = _a.children, props = __rest(_a, ["children"]);
return (createElement(Table, __assign({}, props, { className: tableClassName }),
createElement(Body, { className: classNamePrefix + "table-body" }, children)));
};
return (createElement("div", __assign({ style: __assign({ height: height ? height + "px" : 'calc(100% - 16px)', width: width ? width + "px" : '100%', overflow: 'auto', maxHeight: '100vh' }, style) }, rest),
!rowHeight && !!data.length && (
/*Measure row height only if not supplied explicitly*/
createElement(Table, { style: {
height: 0,
opacity: 0,
display: 'grid',
margin: 0,
width: effectiveWidth + "px"
}, className: tableClassName },
createElement(HeaderRowRenderer, { Header: Header, HeaderRow: HeaderRow, HeaderCell: HeaderCell },
createElement(Measurer, { measure: measure, entity: "header", debounceWait: debounceWait })),
createElement(Body, { className: classNamePrefix + "table-body" },
createElement(Row, { className: classNamePrefix + "table-row" },
createElement(Measurer, { measure: measure, entity: "row", debounceWait: debounceWait }),
createElement(RowCells, { datum: sampleRow || data[sampleRowIndex], columns: columns, classNamePrefix: classNamePrefix, Cell: Cell }))))),
createElement(TableContext.Provider, { value: {
columns: columns,
data: data,
Cell: Cell,
Row: Row,
classNamePrefix: classNamePrefix,
rowClassName: rowClassName
} },
createElement("div", null,
tableWidth > 0 && (createElement(Table, { style: { width: effectiveWidth + "px", marginBottom: 0 }, className: tableClassName },
createElement(HeaderRowRenderer, { Header: Header, HeaderRow: HeaderRow, HeaderCell: HeaderCell }))),
!!data.length && (createElement(List, { height: bodyHeight, itemCount: data.length, itemSize: rowHeight || sampleRowHeight, width: effectiveWidth, innerElementType: TableBody, overscanCount: overscanCount }, MemoRowRenderer)))),
(!height || !width) && (
/*Measure table dimensions only if explicit height or width are not supplied*/
createElement(Measurer, { measure: measure, entity: "table", debounceWait: debounceWait }))));
}
var WindowTable$1 = memo(WindowTable, areTablePropsEqual);
var getTHead = function (headerClassName) {
if (headerClassName === void 0) { headerClassName = ''; }
var THead = function (props) {
return (createElement("thead", __assign({}, props, { className: headerClassName + " " + props.className })));
};
return THead;
};
function Html5Table(_a) {
var headerClassName = _a.headerClassName, props = __rest(_a, ["headerClassName"]);
return (createElement(WindowTable$1, __assign({ Cell: "td", HeaderCell: "th", Header: getTHead(headerClassName), HeaderRow: "tr", Row: "tr", Body: "tbody", Table: "table" }, props)));
}
var useState = useState$1, useMemo$2 = useMemo$3;
/**
* A hook giving a combination of immediate and debounced state
* @param initialState
* @param wait
*/
function useDebouncedState(initialState, wait) {
if (wait === void 0) { wait = 100; }
var _a = useState(initialState), immediateState = _a[0], setImmediateState = _a[1];
var _b = useState(initialState), debouncedState = _b[0], setState = _b[1];
var setDebouncedState = useMemo$2(function () { return debounce(setState, wait); }, [wait]);
var setCombinedState = function (state) {
setDebouncedState.cancel();
setImmediateState(state);
setDebouncedState(state);
};
return [immediateState, debouncedState, setCombinedState];
}
/**
* A hook for fast data filtering
* @param filterFn
* @param data
* @param filterText
*/
var useFilter = function (filterFn, data, filterText) {
return useMemo$2(function () {
if (!filterText.length) {
return data;
}
return filterFn(data, filterText);
}, [data, filterFn, filterText]);
};
/**
* A simple utility for creating functions for trivial data filtering
* @param fields
*/
function createFilter(fields) {
return function (originalData, filterText) {
return originalData.filter(function (data) {
return fields.some(function (field) {
var fieldData = data[field] ? String(data[field]) : '';
return !!(filterText &&
fieldData.toLowerCase().includes(filterText.trim().toLowerCase()));
});
});
};
}
export default WindowTable$1;
export { Html5Table, WindowTable$1 as WindowTable, createFilter, useDebouncedState, useFilter };
//# sourceMappingURL=index.es.js.map