diff --git a/CHANGELOG.md b/CHANGELOG.md index 1d0623d..1475eea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ _2024-06-13_ - adds `row` and `index` as properties when using `expander` - adds `onExpandRow` to allow row expansion to be controlled +- adds ability to calculate `expanded` by using a function ## 0.5.2 diff --git a/example/src/examples/09-scroll.tsx b/example/src/examples/09-scroll.tsx index b92c091..c8de94f 100644 --- a/example/src/examples/09-scroll.tsx +++ b/example/src/examples/09-scroll.tsx @@ -45,10 +45,10 @@ const Example9 = () => { const [scrollToOffset, setScrollToOffset] = useState(""); const [scrollToNumber, setScrollToNumber] = useState(""); const scrollToIndex = useCallback(() => { - ref.current?.scrollToItem(parseInt(scrollToNumber)); + ref.current?.scrollToItem(Number(scrollToNumber)); }, [scrollToNumber]); const scrollToPixel = useCallback(() => { - ref.current?.scrollTo(parseInt(scrollToOffset)); + ref.current?.scrollTo(Number(scrollToOffset)); }, [scrollToOffset]); return ( @@ -93,10 +93,10 @@ const Example = () => { const [scrollToOffset, setScrollToOffset] = useState(""); const [scrollToNumber, setScrollToNumber] = useState(""); const scrollToIndex = useCallback(() => { - ref.current.scrollToItem(parseInt(scrollToNumber)); + ref.current.scrollToItem(Number(scrollToNumber)); }, [scrollToNumber]); const scrollToPixel = useCallback(() => { - ref.current.scrollTo(parseInt(scrollToOffset)); + ref.current.scrollTo(Number(scrollToOffset)); }, [scrollToOffset]); return ( diff --git a/example/src/examples/11-heights.tsx b/example/src/examples/11-heights.tsx index 4ee07a5..52e45c5 100644 --- a/example/src/examples/11-heights.tsx +++ b/example/src/examples/11-heights.tsx @@ -64,7 +64,7 @@ const Example11 = () => { type="number" value={tableHeight.toString()} onChange={e => { - setTableHeight(/-?\d+/.test(e.target.value) ? parseInt(e.target.value) : 0); + setTableHeight(/-?\d+/.test(e.target.value) ? Number(e.target.value) : 0); }} /> @@ -75,7 +75,7 @@ const Example11 = () => { type="number" value={minTableHeight.toString()} onChange={e => { - setMinTableHeight(/-?\d+/.test(e.target.value) ? parseInt(e.target.value) : 0); + setMinTableHeight(/-?\d+/.test(e.target.value) ? Number(e.target.value) : 0); }} /> @@ -86,7 +86,7 @@ const Example11 = () => { type="number" value={maxTableHeight.toString()} onChange={e => { - setMaxTableHeight(/-?\d+/.test(e.target.value) ? parseInt(e.target.value) : 0); + setMaxTableHeight(/-?\d+/.test(e.target.value) ? Number(e.target.value) : 0); }} /> diff --git a/index.d.ts b/index.d.ts index 917afec..f988f32 100644 --- a/index.d.ts +++ b/index.d.ts @@ -2,8 +2,6 @@ import { CSSProperties, ForwardedRef, ReactNode } from "react"; export type SortDirection = "ASC" | "DESC" | null; -type CacheFunction = (dataIndex: number, forceUpdate?: boolean) => void; - export type ExpanderProps = { /** * the data for the row @@ -41,7 +39,7 @@ export type CellProps = { /** * an optional function that can be used to clear the size cache */ - clearSizeCache: CacheFunction; + clearSizeCache: (dataIndex: number, forceUpdate?: boolean) => void; /** * optional custom styles for each cell */ @@ -104,7 +102,7 @@ export type SubComponentProps = { /** * an optional function that can be used to clear the size cache */ - clearSizeCache: CacheFunction; + clearSizeCache: (dataIndex: number, forceUpdate?: boolean) => void; }; export type FooterProps = { @@ -299,9 +297,10 @@ export type TableProps = { */ footerClassname?: string; /** - * set expanded rows + * set expanded rows. Could be an object or a function that takes the index of + * the row and returns a boolean. */ - expandedRows?: { [x: number]: boolean }; + expandedRows?: { [x: number]: boolean } | ((index: number) => boolean); /** * called when a row is expanded * @param value information about the row that is expanded/shrunk @@ -338,6 +337,10 @@ export type TableProps = { * more row customization options. */ rowRenderer?: (props: RowRenderProps) => JSX.Element; + /** + * advanced: this may help address flicker when toggling all rows + */ + forceReset?: boolean; /** * a ref for specific table functions */ diff --git a/src/Row.tsx b/src/Row.tsx index 9a1134c..14d6df4 100644 --- a/src/Row.tsx +++ b/src/Row.tsx @@ -1,6 +1,6 @@ import React, { memo, useCallback, useContext, useLayoutEffect, useRef } from "react"; import { ListChildComponentProps } from "react-window"; -import { CacheFunction, ColumnProps, RowRenderProps, SubComponentProps } from "../index"; +import { ColumnProps, RowRenderProps, SubComponentProps } from "../index"; import { TableContext } from "./TableContext"; import Minus from "./svg/minus-circle.svg"; import Plus from "./svg/plus-circle.svg"; @@ -13,7 +13,7 @@ type TableCellProps = { prevWidth: number; column: ColumnProps; isExpanded: boolean; - clearSizeCache: CacheFunction; + clearSizeCache: (dataIndex: number, forceUpdate?: boolean) => void; onExpanderClick: ( event: React.MouseEvent | undefined, isExpanded: boolean @@ -39,7 +39,8 @@ interface RowProps extends Omit, "data"> { rowContainerClassname: string | ((index: number) => string); rowContainerStyle: React.CSSProperties | ((index: number) => React.CSSProperties); useRowWidth: boolean; - clearSizeCache: CacheFunction; + forceReset?: boolean; + clearSizeCache: (dataIndex: number, forceUpdate?: boolean) => void; calculateHeight: ( queryParam: number | HTMLElement | null, optionalDataIndex?: number | null @@ -196,6 +197,7 @@ function Row({ rowContainerStyle, rowContainerClassname, useRowWidth, + forceReset, rowRenderer, onExpandRow, clearSizeCache, @@ -206,7 +208,8 @@ function Row({ // hooks const expandedCalledRef = useRef(false); const rowRef = useRef(null); - const { dispatch, uuid, columns, expanded, pixelWidths } = useContext(TableContext); + const { dispatch, uuid, columns, expanded, expandedCache, pixelWidths } = + useContext(TableContext); // variables const { height } = style; @@ -216,7 +219,7 @@ function Row({ const rowKey = `${uuid}-${key}`; // expanded - const isExpanded = !!expanded[key]; + const isExpanded = expanded ? expanded(index) : !!expandedCache[key]; const containerHeight = !rowHeight ? undefined : isExpanded && SubComponent ? rowHeight : "100%"; // sub component props @@ -249,14 +252,14 @@ function Row({ // on expansion, clear the cache // every time isExpanded/pixelWidth changes, check the height useLayoutEffect(() => { - if (!expandedCalledRef.current) { + if (!expandedCalledRef.current && !forceReset) { resetHeight(); } else { clearSizeCache(index, true); } expandedCalledRef.current = false; - }, [isExpanded, resetHeight, index, clearSizeCache]); + }, [isExpanded, resetHeight, index, clearSizeCache, forceReset]); return (
{ const node = tableRef.current?.children[1].children[0] as HTMLElement; - const resetIndex = parseInt(node?.dataset.index || "0") + 1; + const resetIndex = Number(node?.dataset.index || "0") + 1; tree.clearFromIndex(resetIndex); listRef.current.resetAfterIndex(resetIndex); }, 50); @@ -172,7 +172,7 @@ const ListComponent = forwardRef(function ( // manually change the `top` and `height` for visible rows elements.forEach((node, i) => { - const dataIndex = parseInt(node.dataset.index || "0"); + const dataIndex = Number(node.dataset.index || "0"); // if the row is incorrect, update the tops going forward const height: number = cache[dataIndex + 1].size; @@ -180,7 +180,7 @@ const ListComponent = forwardRef(function ( // case 0: the first element, where the top is correct if (i === 0) { - prevTop = parseInt(node.style.top); + prevTop = Number(node.style.top); prevHeight = computed; if (height !== computed) { @@ -267,7 +267,7 @@ const ListComponent = forwardRef(function ( // add calculated height to tree [...tableRef.current.children[1].children].forEach(e => { const node = e as HTMLDivElement; - const dataIndex = parseInt(node.dataset.index || "0"); + const dataIndex = Number(node.dataset.index || "0"); if (!tree.hasIndex(dataIndex)) { tree.insert({ index: dataIndex, @@ -314,7 +314,7 @@ const Table = forwardRef(function ( footerClassname, maxTableHeight, minTableHeight, - expandedRows = {}, + expandedRows, borders = false, minColumnWidth = 80, stickyFooter = false, @@ -358,7 +358,6 @@ const Table = forwardRef(function ( uuid, columns, minColumnWidth, - expanded: expandedRows, onSort, sortColumn: sortColumn || null, sortDirection: sortDirection || null, @@ -368,7 +367,9 @@ const Table = forwardRef(function ( stickyFooter, footerComponent, footerClassname, - footerStyle + footerStyle, + expanded: typeof expandedRows === "function" ? expandedRows : undefined, + expandedCache: expandedRows && typeof expandedRows !== "function" ? expandedRows : {} }} > = { sortDirection: SortDirection; stickyFooter: boolean; footerComponent?: (props: FooterProps) => React.ReactNode; - expanded: { + expanded?: (index: number) => boolean; + expandedCache: { [key: string | number]: boolean; }; id?: string; @@ -40,7 +41,7 @@ type TableState = { const baseState: TableState = { dispatch: () => {}, - expanded: {}, + expandedCache: {}, columns: [], pixelWidths: [], rows: [], @@ -60,6 +61,7 @@ const fields: (keyof InitialState)[] = [ "onSort", "columns", "expanded", + "expandedCache", "tableStyle", "headerStyle", "headerClassname", @@ -92,7 +94,7 @@ function reducer(state: TableState, action: Action): TableState { case "updateExpanded": return { ...state, - expanded: { ...state.expanded, [key]: !state.expanded[key] } + expandedCache: { ...state.expandedCache, [key]: !state.expandedCache[key] } }; case "updatePixelWidths": return { ...state, pixelWidths: widths };