Skip to content

Commit

Permalink
Merge pull request #84 from mckervinc/feature/resize-observer
Browse files Browse the repository at this point in the history
v0.5.0 - handle resize in a modern fashion
  • Loading branch information
mckervinc committed Oct 3, 2023
2 parents 5e9e006 + 11d26d8 commit 96d2754
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 124 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
# CHANGELOG

## 0.5.0

_2023-10-02_

### Features

- uses a wrapper library to handle observing the table container for width/height changes
- initial flicker for variable-row-size tables should be mitigated
- more typescript specifications

## 0.4.10

_2023-09-30_

###
### Features

- added ability to specify the `footerHeight`
- removed some typescript warnings
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import { Table } from "react-fluid-table";

const data = _.range(100).map(i => ({
id: i + 1,
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
email: faker.internet.email()
firstName: randFirstName(),
lastName: randLastName(),
email: randEmail()
}));

const columns = [
Expand Down
10 changes: 6 additions & 4 deletions example/src/ColumnProps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,16 @@ const data: PropData[] = [
type: "FooterElement",
description: (
<div>
This property allows you to customize the content inside of a footer cell. The library will create
a cell container for you with the proper column widths and resizability. If this field is
defined, then this will get rendered inside of the cell container in the footer.
This property allows you to customize the content inside of a footer cell. The library will
create a cell container for you with the proper column widths and resizability. If this
field is defined, then this will get rendered inside of the cell container in the footer.
</div>
)
}
];

const ColumnPropsTable = () => <StyledTable data={data} columns={columns} tableHeight={400} />;
const ColumnPropsTable = () => (
<StyledTable borders data={data} columns={columns} tableHeight={400} />
);

export default ColumnPropsTable;
9 changes: 5 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "react-fluid-table",
"version": "0.4.10",
"version": "0.5.0",
"description": "A React table inspired by react-window",
"author": "Mckervin Ceme <mckervinc@live.com>",
"license": "MIT",
Expand Down Expand Up @@ -65,28 +65,29 @@
"@testing-library/react-hooks": "^8.0.1",
"@types/react-window": "^1.8.5",
"cross-env": "^7.0.3",
"eslint-config-standard-react": "^13.0.0",
"eslint": "8.50.0",
"eslint-config-standard": "^17.1.0",
"eslint-config-standard-react": "^13.0.0",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.28.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^5.0.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.33.2",
"eslint": "8.50.0",
"gh-pages": "^6.0.0",
"postcss": "^8.4.30",
"prettier": "^3.0.3",
"react": "^18.2.0",
"rollup": "^3.29.3",
"rollup-plugin-analyzer": "^4.0.0",
"rollup-plugin-bundle-size": "^1.0.3",
"rollup-plugin-peer-deps-external": "^2.2.4",
"rollup-plugin-postcss": "^4.0.2",
"rollup-plugin-visualizer": "^5.9.2",
"rollup": "^3.29.3",
"typescript": "^5.2.2"
},
"dependencies": {
"react-resize-detector": "^9.1.0",
"react-window": "^1.8.9"
},
"volta": {
Expand Down
3 changes: 2 additions & 1 deletion rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ const config = {
url({ exclude: ["**/*.svg"] }),
babel({
extensions,
exclude: "node_modules/**"
exclude: "node_modules/**",
babelHelpers: "bundled"
}),
resolve({ extensions }),
commonjs(),
Expand Down
75 changes: 13 additions & 62 deletions src/AutoSizer.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import React, { useContext, useMemo } from "react";
import { useResizeDetector } from "react-resize-detector";
import { TableContext } from "./TableContext";
import { DEFAULT_FOOTER_HEIGHT, DEFAULT_HEADER_HEIGHT, DEFAULT_ROW_HEIGHT } from "./constants";
import { findFooterByUuid, findHeaderByUuid } from "./util";
Expand Down Expand Up @@ -90,7 +91,7 @@ const calculateHeight = (
const borderOffset = !!table ? table.offsetHeight - table.clientHeight : 0;

// if there are rows, let's do the calculation
if (!!nodes.length) {
if (nodes.length) {
if (rowHeight > 0) {
return headerOffset + nodes.length * rowHeight + footerOffset + borderOffset;
}
Expand All @@ -116,8 +117,8 @@ const calculateHeight = (
/**
* This is a skinny AutoSizer based on react-virtualized-auto-sizer.
* This removes the `bailout` functionality in order to allow the Table
* to generate its own height. This also ignores a resize if the
* dimensions of the window did not actually change (one less render).
* to generate its own height. This uses ResizeObserver to observe the
* container when it changes in order to provide the correct height
*/
const AutoSizer = ({
numRows,
Expand All @@ -131,22 +132,22 @@ const AutoSizer = ({
children
}: AutoSizerProps) => {
// hooks
const resizeRef = useRef(0);
const ref = useRef<HTMLDivElement>(null);
const {
ref,
width: containerWidth,
height: containerHeight
} = useResizeDetector<HTMLDivElement>();
const { uuid, columns, footerComponent } = useContext(TableContext);
const [dimensions, setDimensions] = useState({ containerHeight: 0, containerWidth: 0 });

// variables
const { containerHeight, containerWidth } = dimensions;
const hasFooter = useMemo(
() => !!footerComponent || !!columns.find(c => !!c.footer),
[columns, footerComponent]
);
const fixedTableSize = !!tableHeight && tableHeight > 0 && !!tableWidth && tableWidth > 0;

// calculate the computed height
const computedHeight = useMemo(() => {
if (!!tableHeight && tableHeight > 0) {
if (tableHeight && tableHeight > 0) {
return tableHeight;
}

Expand All @@ -163,64 +164,14 @@ const AutoSizer = ({
// calculate the actual height of the table
const height = findCorrectHeight({
computedHeight,
containerHeight,
containerHeight: containerHeight || 0,
tableHeight: tableHeight || 0,
minHeight: minTableHeight || 0,
maxHeight: maxTableHeight || 0
});

// get actual width
const width = !!tableWidth && tableWidth > 0 ? tableWidth : containerWidth;

// functions
const calculateDimensions = useCallback(() => {
// base cases
if (!ref.current?.parentElement || fixedTableSize) {
return;
}

// get style
const parent = ref.current.parentElement;
const style = window.getComputedStyle(parent);
const paddingLeft = parseInt(style.paddingLeft) || 0;
const paddingRight = parseInt(style.paddingRight) || 0;
const paddingTop = parseInt(style.paddingTop) || 0;
const paddingBottom = parseInt(style.paddingBottom) || 0;

// find new dimensions
const newHeight = Math.max((parent.offsetHeight || 0) - paddingTop - paddingBottom, 0);
const newWidth = Math.max((parent.offsetWidth || 0) - paddingLeft - paddingRight, 0);

// update state
setDimensions(prev => {
const { containerHeight: oldHeight, containerWidth: oldWidth } = prev;
if (oldHeight !== newHeight || oldWidth !== newWidth) {
return { containerHeight: newHeight, containerWidth: newWidth };
}

return prev;
});
}, [fixedTableSize]);

const onResize = useCallback(() => {
window.clearTimeout(resizeRef.current);
resizeRef.current = window.setTimeout(calculateDimensions, 40);
}, [calculateDimensions]);

// effects
// on mount, calculate the dimensions
useEffect(() => calculateDimensions(), [calculateDimensions]);

// on resize, we have to re-calculate the dimensions
useEffect(() => {
window.removeEventListener("resize", onResize);
window.addEventListener("resize", onResize);
const m = resizeRef.current;
return () => {
window.clearTimeout(m);
window.removeEventListener("resize", onResize);
};
}, [onResize]);
const width = tableWidth && tableWidth > 0 ? tableWidth : containerWidth || 0;

return <div ref={ref}>{height || width ? children({ height, width }) : null}</div>;
};
Expand Down
10 changes: 5 additions & 5 deletions src/Row.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import React, { SVGProps, useCallback, useContext, useLayoutEffect, useRef } fro
import { CacheFunction, ColumnProps, RowRenderProps, SubComponentProps } from "../index";
//@ts-ignore TS2307
import Minus from "./svg/minus-circle.svg";
import { ListChildComponentProps } from "react-window";
import { TableContext } from "./TableContext";
//@ts-ignore TS2307
import Plus from "./svg/plus-circle.svg";
import { TableContext } from "./TableContext";
import { ListChildComponentProps } from "react-window";
import { cx } from "./util";

interface TableCellProps<T> {
Expand Down Expand Up @@ -197,7 +197,7 @@ function Row<T>({
const containerHeight = !rowHeight ? undefined : isExpanded && SubComponent ? rowHeight : "100%";

// sub component props
const subProps = { row, index, isExpanded, clearSizeCache };
const subProps: SubComponentProps<T> = { row, index, isExpanded, clearSizeCache };

// row styling
const borderBottom = borders ? undefined : "none";
Expand All @@ -221,7 +221,7 @@ function Row<T>({
if (height !== calculateHeight(rowRef.current, index)) {
clearSizeCache(index);
}
}, [rowRef, index, height, calculateHeight, clearSizeCache, pixelWidths]);
}, [index, height, calculateHeight, clearSizeCache, pixelWidths]);

// effects
// on expansion, clear the cache
Expand All @@ -234,7 +234,7 @@ function Row<T>({
}

expandedCalledRef.current = false;
}, [isExpanded, expandedCalledRef, resetHeight, index, clearSizeCache]);
}, [isExpanded, resetHeight, index, clearSizeCache]);

return (
<div
Expand Down

0 comments on commit 96d2754

Please sign in to comment.