From 8cdf3bf91fd7aa8fcd46998b83c2289fbff45c0c Mon Sep 17 00:00:00 2001 From: Simon Guo Date: Tue, 11 Jun 2024 12:04:22 +0800 Subject: [PATCH] fix(Table): fix `flexGrow` is invalid when the table is hidden and then shown (#489) * fix(Table): fix `flexGrow` is invalid when the table is hidden and then shown * fix: fix failed tests * test: update tests --- docs/examples/FluidColumnTable.md | 77 ++++++++++++++++------------ docs/index.tsx | 7 ++- package.json | 2 +- pnpm-lock.yaml | 63 ++++++++++++++++++----- src/utils/useIntersectionObserver.ts | 44 ++++++++++++++++ src/utils/useTableDimension.ts | 12 +++++ test/Table.test.tsx | 10 +++- 7 files changed, 165 insertions(+), 50 deletions(-) create mode 100644 src/utils/useIntersectionObserver.ts diff --git a/docs/examples/FluidColumnTable.md b/docs/examples/FluidColumnTable.md index 185b29a..962c533 100644 --- a/docs/examples/FluidColumnTable.md +++ b/docs/examples/FluidColumnTable.md @@ -5,44 +5,55 @@ ```js const data = mockUsers(20); -const App = () => { +const CustomTable = ({ flexGrow }) => { return ( - { - console.log(sortColumn, sortType); - }} - > - - Id - - + <> +
{ + console.log(sortColumn, sortType); + }} + > + + Id + + + + + First Name + + - - First Name - - + + Last Name + + - - Last Name - - + + City {flexGrow ? (flexGrow={1}) : null} + + - - - City (flexGrow={1}) - - - + + Company {flexGrow ? (flexGrow={2}) : null} + + +
+ + ); +}; - - - Company (flexGrow={2}) - - - - +const App = () => { + return ( + + + + + + + + ); }; diff --git a/docs/index.tsx b/docs/index.tsx index d6ab856..2ace050 100644 --- a/docs/index.tsx +++ b/docs/index.tsx @@ -11,7 +11,8 @@ import { Divider, Input, Loader, - Placeholder + Placeholder, + Tabs } from 'rsuite'; import clone from 'lodash/clone'; import isFunction from 'lodash/isFunction'; @@ -40,6 +41,7 @@ import 'rsuite/Nav/styles/index.less'; import 'rsuite/Input/styles/index.less'; import 'rsuite/Stack/styles/index.less'; import 'rsuite/Divider/styles/index.less'; +import 'rsuite/Tabs/styles/index.less'; import './less/index.less'; const dependencies = { @@ -76,7 +78,8 @@ const dependencies = { ArrowDownIcon, ArrowUpIcon, SortIcon, - Placeholder + Placeholder, + Tabs }; const examples = [ diff --git a/package.json b/package.json index 8356e0f..a99e96e 100644 --- a/package.json +++ b/package.json @@ -129,7 +129,7 @@ "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", - "rsuite": "^5.21.0", + "rsuite": "^5.64.0", "sinon": "^11.1.2", "sinon-chai": "^3.7.0", "style-loader": "^0.13.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5684d14..469b769 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -257,8 +257,8 @@ devDependencies: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) rsuite: - specifier: ^5.21.0 - version: 5.21.0(react-dom@18.2.0)(react@18.2.0) + specifier: ^5.64.0 + version: 5.64.0(react-dom@18.2.0)(react@18.2.0) sinon: specifier: ^11.1.2 version: 11.1.2 @@ -5060,6 +5060,13 @@ packages: resolution: {integrity: sha512-66NzehAJZM5HrH/2FW6C0tgaMIywDF5I9n7PWgvdSciohlYQbCFcSf5XA6hhIqQdFbfrnZDD8NGLo9pDRzO5hQ==} dependencies: '@babel/runtime': 7.18.9 + dev: false + + /dom-lib@3.3.1: + resolution: {integrity: sha512-N2mpo8qQmB9wIMZJVjER+BSh4GJiZZ7S6EjnMtyETcXo90hpITUDXpUhqOcfXZ2ZefytuYYKTZMp3CGR2X+tDA==} + dependencies: + '@babel/runtime': 7.20.1 + dev: true /dom-serialize@2.2.1: resolution: {integrity: sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==} @@ -6355,6 +6362,13 @@ packages: engines: {node: '>=0.10.0'} dev: true + /get-value@3.0.1: + resolution: {integrity: sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA==} + engines: {node: '>=6.0'} + dependencies: + isobject: 3.0.1 + dev: true + /getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} dependencies: @@ -7576,6 +7590,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-primitive@3.0.1: + resolution: {integrity: sha512-GljRxhWvlCNRfZyORiH77FwdFwGcMO620o37EOYC0ORWdq+WYNVqW0w2Juzew4M+L81l6/QS3t5gkkihyRqv9w==} + engines: {node: '>=0.10.0'} + dev: true + /is-promise@2.2.2: resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==} dev: true @@ -10445,6 +10464,14 @@ packages: /react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + /react-use-set@1.0.0(react@18.2.0): + resolution: {integrity: sha512-6BBbOcWc/tOKuwd9gDtdunvOr/g40S0SkCBYvrSJvpI0upzNlHmLoeDvylnoP8PrjQXItClAFxseVGGhEkk7kw==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + dev: true + /react-window@1.8.8(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-D4IiBeRtGXziZ1n0XklnFGu7h9gU684zepqyKzgPNzrsrk7xOCxni+TCckjg2Nr/DiaEEGVVmnhYSlT2rB47dQ==} engines: {node: '>8.0.0'} @@ -10941,8 +10968,8 @@ packages: glob: 7.2.3 dev: true - /rsuite-table@5.7.2(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-JwfxAR8lXVXM9PRQGJMbayciMcVFpHWExAhfY53h6JYKC7LfPeBx/Z6k4P7PO0grFkVGBdFW3ZgHxhCD1ur/eA==} + /rsuite-table@5.18.2(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-IelmlHraExYgrkT13WWVENhCywWjBxPkpF2zpsqvMcwzaNAg9lHaVVyajcOKczqGB24NGRE6WgBF5n1RC6XAww==} peerDependencies: prop-types: ^15.7.2 react: '>=16.8.0' @@ -10952,7 +10979,7 @@ packages: '@juggle/resize-observer': 3.4.0 '@rsuite/icons': 1.0.2(react-dom@18.2.0)(react@18.2.0) classnames: 2.3.1 - dom-lib: 3.1.3 + dom-lib: 3.3.1 lodash: 4.17.21 prop-types: 15.8.1 react: 18.2.0 @@ -10960,8 +10987,8 @@ packages: react-is: 17.0.2 dev: true - /rsuite@5.21.0(react-dom@18.2.0)(react@18.2.0): - resolution: {integrity: sha512-mSEGFRHzVMPmEY7lj+gCmwsJxITDo/WarbJILf+ohQicQYRI/RdVLavoHsBWeg6Qen4Y/o1Ts89KxokA7igEuA==} + /rsuite@5.64.0(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-yknbgzUOkb66dX3fFxTnQSlonW1D53fQgY6V4g1Iu8kBNH7tduPaZghLcQ65DAGBa2pGvtrkCZUrLaied1NMBw==} peerDependencies: react: '>=16.8.0' react-dom: '>=16.8.0' @@ -10975,14 +11002,15 @@ packages: '@types/react-window': 1.8.5 classnames: 2.3.1 date-fns: 2.29.3 - dom-lib: 3.1.3 + dom-lib: 3.3.1 lodash: 4.17.21 prop-types: 15.8.1 react: 18.2.0 react-dom: 18.2.0(react@18.2.0) + react-use-set: 1.0.0(react@18.2.0) react-window: 1.8.8(react-dom@18.2.0)(react@18.2.0) - rsuite-table: 5.7.2(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0) - schema-typed: 2.0.3 + rsuite-table: 5.18.2(prop-types@15.8.1)(react-dom@18.2.0)(react@18.2.0) + schema-typed: 2.2.2 dev: true /rtlcss@2.6.2: @@ -11040,10 +11068,11 @@ packages: dependencies: loose-envify: 1.4.0 - /schema-typed@2.0.3: - resolution: {integrity: sha512-4KckVnJjTtVugYpSAoQrcH4quE4yIVTvI/nHEqtwdceBr/ZCuH2LfV8/gaZFrYU7cwwyufLKaswt28aqQ1T9ww==} + /schema-typed@2.2.2: + resolution: {integrity: sha512-hRmqKr5V6UyhmZ0FixRVetgxvudRPjDynVZZRNq6t4EZHii7U33vmqd9uap3s4aqBcDg1JtubMNvCEmsZTpm3Q==} dependencies: - '@babel/runtime': 7.20.1 + get-value: 3.0.1 + set-value: 4.1.0 dev: true /schema-utils@1.0.0: @@ -11207,6 +11236,14 @@ packages: split-string: 3.1.0 dev: true + /set-value@4.1.0: + resolution: {integrity: sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==} + engines: {node: '>=11.0'} + dependencies: + is-plain-object: 2.0.4 + is-primitive: 3.0.1 + dev: true + /setprototypeof@1.1.0: resolution: {integrity: sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==} dev: true diff --git a/src/utils/useIntersectionObserver.ts b/src/utils/useIntersectionObserver.ts new file mode 100644 index 0000000..a426c80 --- /dev/null +++ b/src/utils/useIntersectionObserver.ts @@ -0,0 +1,44 @@ +import { useState, useEffect, RefObject } from 'react'; + +/** + * useIntersectionObserver Hook + * + * @param ref - Ref object of the element to be observed + */ +const useIntersectionObserver = (ref?: RefObject): boolean => { + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + // Check if the browser supports IntersectionObserver + if (!('IntersectionObserver' in window)) { + // If not supported, optionally set to visible or handle fallback logic + setIsVisible(true); // Fallback: Set to visible + return; + } + + // Create an IntersectionObserver instance + const observer = new IntersectionObserver(entries => { + entries.forEach(entry => { + setIsVisible(entry.isIntersecting); + }); + }); + + const element = ref?.current; + + // Start observing the target element + if (element) { + observer.observe(element); + } + + // Cleanup function to unobserve the element when the component unmounts or dependencies change + return () => { + if (element) { + observer.unobserve(element); + } + }; + }, [ref]); + + return isVisible; +}; + +export default useIntersectionObserver; diff --git a/src/utils/useTableDimension.ts b/src/utils/useTableDimension.ts index 04b5cb3..fe4fa81 100644 --- a/src/utils/useTableDimension.ts +++ b/src/utils/useTableDimension.ts @@ -6,6 +6,7 @@ import { SCROLLBAR_WIDTH } from '../constants'; import { ResizeObserver } from '@juggle/resize-observer'; import useMount from './useMount'; import useUpdateLayoutEffect from './useUpdateLayoutEffect'; +import useIntersectionObserver from './useIntersectionObserver'; import isNumberOrTrue from './isNumberOrTrue'; import { RowDataType, ElementOffset } from '../@types/common'; import debounce from 'lodash/debounce'; @@ -296,6 +297,17 @@ const useTableDimension = (props: TableDimensionPr calculateTableContentWidth ]); + const isVisible = useIntersectionObserver(tableRef); + + useUpdateLayoutEffect(() => { + // When the table is visible, the width of the table is recalculated. + // fix: https://github.com/rsuite/rsuite/issues/397 + if (isVisible) { + calculateTableWidth(); + calculateTableContentWidth(); + } + }, [isVisible]); + const setScrollY = useCallback((value: number) => { scrollY.current = value; }, []); diff --git a/test/Table.test.tsx b/test/Table.test.tsx index 7ffa533..5a2ee57 100644 --- a/test/Table.test.tsx +++ b/test/Table.test.tsx @@ -4,7 +4,15 @@ import Table, { TableInstance } from '../src/Table'; import Cell from '../src/Cell'; import Column from '../src/Column'; import HeaderCell from '../src/HeaderCell'; -import { ItemDataType } from 'rsuite/esm/@types/common'; + +interface ItemDataType extends Record { + label?: React.ReactNode; + value?: T; + groupBy?: string; + parent?: ItemDataType; + children?: ItemDataType[]; + loading?: boolean; +} type Row = { id: number;