From e4366eb411e3c48e4458bb4bc43e09fcb590f785 Mon Sep 17 00:00:00 2001 From: Keny Shah Date: Wed, 11 Dec 2024 09:11:44 -0800 Subject: [PATCH 01/17] sticky-header-intersection-observer --- src/assets/scss/_recordset-table.scss | 57 +++++- src/components/recordset/recordset-table.tsx | 183 ++++++++++++++++++- 2 files changed, 236 insertions(+), 4 deletions(-) diff --git a/src/assets/scss/_recordset-table.scss b/src/assets/scss/_recordset-table.scss index 9276a2a76..2fb6d3344 100644 --- a/src/assets/scss/_recordset-table.scss +++ b/src/assets/scss/_recordset-table.scss @@ -5,7 +5,7 @@ overflow-y: hidden; } -.chaise-table.table { +.chaise-table.table, .sticky-header > table { border-top: 1px solid map.get(variables.$color-map, 'table-border'); border-bottom: 1px solid map.get(variables.$color-map, 'table-border'); margin: 0; @@ -262,3 +262,58 @@ display: none !important; position: absolute !important; } +.sticky-header { + position: fixed; + top: 0; + // z-index: 1000; + visibility:none; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); + overflow-x: hidden; +} +.sticky-header > table > thead > tr > th{ + font-weight: 400; + border-top: 1px solid map.get(variables.$color-map, 'table-border'); + position: relative; + padding: 7px; + + + &.actions-header { + white-space: nowrap; + text-align: center; + font-size: variables.$h4-font-size; + padding: 8px; + } + + .table-column-displayname { + font-size: variables.$h4-font-size; + } + + // preserve space for the icon on the right + // 20 for the "icon space" + // 10 for the "right indentation" + padding-right: 30px; + .table-heading-icons { + position: absolute; + bottom: 7px; + right: 10px; + font-size: variables.$h4-font-size; + + .table-column-spinner { + position: absolute; + bottom: 2px; + right: -5px; + + .spinner-border { + // NOTE: we should not customize the width/height since it causes wobbling effect + color: map.get(variables.$color-map, 'table-header-spinner'); + border-width: .16em; + } + } + } +} +.sticky-header{ +.sticky-header-table{ + table-layout: fixed; + width: 100%; +} +} \ No newline at end of file diff --git a/src/components/recordset/recordset-table.tsx b/src/components/recordset/recordset-table.tsx index 9f5174904..d9d902faf 100644 --- a/src/components/recordset/recordset-table.tsx +++ b/src/components/recordset/recordset-table.tsx @@ -60,6 +60,10 @@ const RecordsetTable = ({ const tableContainer = useRef(null); const stickyScrollbarRef = useRef(null); const tableEndRef = useRef(null); + const tableRef = useRef(null); + const outerTableRef = useRef(null); + const headRef = useRef(null); + const stickyHeaderRef = useRef(null); const [currSortColumn, setCurrSortColumn] = useState( @@ -70,6 +74,7 @@ const RecordsetTable = ({ // tracks whether a paging action has successfully occurred for this table // used for related tables to fire an event when the content has loaded to scroll back to the top of the related table const [pagingSuccess, setPagingSuccess] = useState(false); + const [headerTop, setHeaderTop] = useState(0); type RowConfig = { isSelected: boolean; @@ -161,7 +166,167 @@ const RecordsetTable = ({ } }, []); + useEffect(()=>{ + console.log('calllled'); + setHeaderTop(headRef.current!.getBoundingClientRect().top); + },[isInitialized]); + console.log(headerTop, headRef.current?.getBoundingClientRect()); + + useEffect(()=>{ + if(!outerTableRef.current || !headRef.current || !stickyHeaderRef.current){ + return; + } + + const observer = new IntersectionObserver( + ([entry]) => { + if(stickyHeaderRef.current){ + if (!entry.isIntersecting) { + stickyHeaderRef.current.style.visibility = 'visible'; + const hasScrollbar = outerTableRef.current!.scrollWidth > outerTableRef.current!.clientWidth; + console.log('hasScrollbar ',hasScrollbar); + stickyHeaderRef.current.style.top = `${headerTop}px`; + } else { + stickyHeaderRef.current.style.visibility = 'hidden'; + } + } + }, + { root: null, threshold: 0 } + ); + observer.observe(headRef.current); + + // Sync widths of the columns + const syncWidths = () => { + if(stickyHeaderRef.current && tableRef.current){ + + const originalThs = tableRef.current.querySelectorAll('tbody > tr > td'); + const stickyThs = stickyHeaderRef.current?.querySelectorAll('th'); + + // Loop through columns and set widths + stickyThs!.forEach((headerCol, index) => { + const dataCol = originalThs[index]; + if (dataCol instanceof HTMLElement) { // Ensure it's an HTML element + const colWidth = dataCol.offsetWidth; // Get the actual width of the column + headerCol.style.width = `${colWidth}px`; // Set width on sticky header + } + + }); + stickyHeaderRef.current.style.width = `${outerTableRef.current?.offsetWidth}px`; + } + }; + const handleScroll = () => { + if (stickyHeaderRef.current && stickyScrollbarRef.current) { + stickyHeaderRef.current.scrollLeft = stickyScrollbarRef.current.scrollLeft; + } + }; + + stickyScrollbarRef.current?.addEventListener('scroll', handleScroll); + + // Sync column widths on resize + window.addEventListener('resize', syncWidths); + + // Perform initial sync + syncWidths(); + + // Cleanup function + return () => { + observer.disconnect(); + stickyScrollbarRef.current?.removeEventListener('scroll', handleScroll); + window.removeEventListener('resize', syncWidths); + }; + + },[isLoading, headerTop]); + + // useEffect(() => { + // if (!outerTableRef.current || !headRef.current || !stickyHeaderRef.current) return; + + // // Intersection Observer for bottom scrollbar visibility + // const bottomObserver = new IntersectionObserver( + // ([entry]) => { + // if (stickyScrollbarRef.current) { + // if (entry.isIntersecting) { + // stickyScrollbarRef.current.classList.add('no-scroll-bar'); + // if(stickyHeaderRef.current){ + // stickyHeaderRef.current.style.visibility = 'hidden'; + // } + // } else { + // stickyScrollbarRef.current.classList.remove('no-scroll-bar'); + // } + // } + // }, + // { root: null, threshold: 0.1 } + // ); + + // // Intersection Observer for sticky header visibility + // const headerObserver = new IntersectionObserver( + // ([entry]) => { + // if (stickyHeaderRef.current) { + // if (!entry.isIntersecting) { + // stickyHeaderRef.current.style.visibility = 'visible'; + // stickyHeaderRef.current.style.top = `calc(${stickyScrollbarRef.current!.getBoundingClientRect().top}px + 15px)`; + // } else { + // stickyHeaderRef.current.style.visibility = 'hidden'; + // } + // } + // }, + // { root: null, threshold: 0 } + // ); + + // // Observe elements + // if (tableEndRef.current) bottomObserver.observe(tableEndRef.current); + // headerObserver.observe(headRef.current); + + // // Sync widths of the columns + // const syncWidths = () => { + // if (!stickyHeaderRef.current || !tableRef.current) return; + + // const originalThs = tableRef.current.querySelectorAll('tbody > tr > td'); + // const stickyThs = stickyHeaderRef.current?.querySelectorAll('th'); + + // stickyThs!.forEach((headerCol, index) => { + // const dataCol = originalThs[index]; + // if (dataCol instanceof HTMLElement) { + // const colWidth = dataCol.offsetWidth; + // headerCol.style.width = `${colWidth}px`; + // } + // }); + + // stickyHeaderRef.current.style.width = `${outerTableRef.current?.offsetWidth}px`; + + // console.log('headRef.current:', headRef.current?.offsetWidth, + // 'outerTableRef.current:', outerTableRef.current?.offsetWidth, + // 'stickyHeaderRef.current:', stickyHeaderRef.current.offsetWidth); + // }; + + // // Scroll synchronization + // const handleScroll = () => { + // if (stickyHeaderRef.current && stickyScrollbarRef.current) { + // stickyHeaderRef.current.scrollLeft = stickyScrollbarRef.current.scrollLeft; + // } + // }; + + // stickyScrollbarRef.current?.addEventListener('scroll', handleScroll); + + // // Sync column widths on resize + // window.addEventListener('resize', syncWidths); + + // // Perform initial sync + // syncWidths(); + + // // Cleanup function + // return () => { + // bottomObserver.disconnect(); + // headerObserver.disconnect(); + // stickyScrollbarRef.current?.removeEventListener('scroll', handleScroll); + // window.removeEventListener('resize', syncWidths); + // }; + // }, [isLoading, showSingleScrollbar]); + + +useEffect(()=>{ + console.log(stickyScrollbarRef.current?.scrollLeft, outerTableRef.current?.scrollLeft, tableContainer.current?.scrollLeft); + +},[stickyScrollbarRef.current?.scrollLeft]) /** * add the top horizontal scroll if needed */ @@ -380,6 +545,7 @@ const RecordsetTable = ({ ) } + console.log(columnModels.length); const renderColumnHeaders = () => { return columnModels.map((col: any, index: number) => { @@ -519,9 +685,9 @@ const RecordsetTable = ({ className='chaise-table-top-scroll-wrapper'>
-
- - +
+
+ {showActionButtons && renderActionsHeader()} {renderColumnHeaders()} @@ -536,6 +702,17 @@ const RecordsetTable = ({ top scrollbar when the bottom one is visible */}
+
+ + + {showActionButtons && renderActionsHeader()} + {renderColumnHeaders()} + + +
+
+ {!hasTimeoutError && numHiddenRecords > 0 &&
} {!hasTimeoutError && numHiddenRecords > 0 &&
diff --git a/test/e2e/locators/recordset.ts b/test/e2e/locators/recordset.ts index 692092da4..ad876a04c 100644 --- a/test/e2e/locators/recordset.ts +++ b/test/e2e/locators/recordset.ts @@ -162,7 +162,7 @@ export default class RecordsetLocators { } static getActionsHeader(container: Page | Locator): Locator { - return container.locator('.actions-header'); + return container.locator('.actions-header:not(.sticky)'); } static getRows(container: Page | Locator): Locator { From 2fb2a8604e19e2ac0dfe1dffdb365c5cc0f567de Mon Sep 17 00:00:00 2001 From: Keny Shah Date: Mon, 10 Feb 2025 10:32:09 -0800 Subject: [PATCH 06/17] Fix locators for recordset --- test/e2e/locators/recordset.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/locators/recordset.ts b/test/e2e/locators/recordset.ts index ad876a04c..40368ecbc 100644 --- a/test/e2e/locators/recordset.ts +++ b/test/e2e/locators/recordset.ts @@ -162,7 +162,7 @@ export default class RecordsetLocators { } static getActionsHeader(container: Page | Locator): Locator { - return container.locator('.actions-header:not(.sticky)'); + return container.locator('.actions-header').filter({ hasNot: container.locator('.sticky-header-table') }); } static getRows(container: Page | Locator): Locator { @@ -178,7 +178,7 @@ export default class RecordsetLocators { } static getColumnNames(container: Page | Locator): Locator { - return container.locator('.table-column-displayname > span:not(.sticky)'); + return container.locator('.table-column-displayname > span').filter({ hasNot: container.locator('.sticky-header-table') }); }; static getFirstColumn(container: Page | Locator): Locator { From e7a17f3bff8d8a435251974a943545c196807ac4 Mon Sep 17 00:00:00 2001 From: Keny Shah Date: Tue, 11 Feb 2025 09:35:38 -0800 Subject: [PATCH 07/17] Add new node version for Docker that's compatible with project --- test/e2e/docker/Dockerfile.chaise-test-env | 3 ++- test/e2e/docker/Dockerfile.chaise-test-env.local | 4 +++- test/e2e/locators/recordset.ts | 4 ++-- test/e2e/setup/playwright.setup.ts | 5 +++++ 4 files changed, 12 insertions(+), 4 deletions(-) diff --git a/test/e2e/docker/Dockerfile.chaise-test-env b/test/e2e/docker/Dockerfile.chaise-test-env index d8e2f5b47..9709781a1 100644 --- a/test/e2e/docker/Dockerfile.chaise-test-env +++ b/test/e2e/docker/Dockerfile.chaise-test-env @@ -1,6 +1,7 @@ # This creates a docker image with chaise and all the test dependencies installed. This can be directly used in a CI for headless testing. # for more information refer - docs/dev-docs/e2e-test-docker.md FROM ubuntu:20.04 +FROM node:20 RUN apt-get update \ && apt-get install -y wget gnupg \ @@ -19,7 +20,7 @@ RUN apt-get update && \ rsync \ git \ sudo \ - openjdk-8-jdk + openjdk-17-jdk # Install Node.js 16.x RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - diff --git a/test/e2e/docker/Dockerfile.chaise-test-env.local b/test/e2e/docker/Dockerfile.chaise-test-env.local index 25586148e..d64c4718d 100644 --- a/test/e2e/docker/Dockerfile.chaise-test-env.local +++ b/test/e2e/docker/Dockerfile.chaise-test-env.local @@ -3,6 +3,8 @@ # Make sure to use volume mounts to mount the chaise directory to the container. # for more information refer - docs/dev-docs/e2e-test-docker.md FROM ubuntu:20.04 +FROM node:20 + RUN apt-get update \ @@ -22,7 +24,7 @@ RUN apt-get update && \ rsync \ git \ sudo \ - openjdk-8-jdk + openjdk-17-jdk # Install Node.js 16.x RUN curl -fsSL https://deb.nodesource.com/setup_16.x | bash - diff --git a/test/e2e/locators/recordset.ts b/test/e2e/locators/recordset.ts index 40368ecbc..b7e8c5a2d 100644 --- a/test/e2e/locators/recordset.ts +++ b/test/e2e/locators/recordset.ts @@ -162,7 +162,7 @@ export default class RecordsetLocators { } static getActionsHeader(container: Page | Locator): Locator { - return container.locator('.actions-header').filter({ hasNot: container.locator('.sticky-header-table') }); + return container.locator('.actions-header').filter({ hasNot: container.locator('.sticky-header') }); } static getRows(container: Page | Locator): Locator { @@ -178,7 +178,7 @@ export default class RecordsetLocators { } static getColumnNames(container: Page | Locator): Locator { - return container.locator('.table-column-displayname > span').filter({ hasNot: container.locator('.sticky-header-table') }); + return container.locator('.table-column-displayname > span').filter({ hasNot: container.locator('.sticky-header') }); }; static getFirstColumn(container: Page | Locator): Locator { diff --git a/test/e2e/setup/playwright.setup.ts b/test/e2e/setup/playwright.setup.ts index 4c33c8ac5..e569184dd 100644 --- a/test/e2e/setup/playwright.setup.ts +++ b/test/e2e/setup/playwright.setup.ts @@ -128,6 +128,11 @@ async function checkUserSessions(): Promise<{ session: any, authCookie: string } * create the catalog and data */ async function createCatalog(testConfiguration: any, projectNames: string[], isManual?: boolean) { + if(!process.env.PWD){ + process.env.PWD='c/Users/kenys/Desktop/ISI/chaise'; + + } + console.log(process.env.PWD); return new Promise(async (resolve, reject) => { testConfiguration.setup.url = process.env.ERMREST_URL; From f6e1cf683f540e1af042ad9f59dc4a0901a3d80b Mon Sep 17 00:00:00 2001 From: Keny Shah Date: Tue, 11 Feb 2025 09:41:25 -0800 Subject: [PATCH 08/17] CLeanup --- src/components/recordset/recordset-table.tsx | 31 +------------------- test/e2e/setup/playwright.setup.ts | 6 ---- 2 files changed, 1 insertion(+), 36 deletions(-) diff --git a/src/components/recordset/recordset-table.tsx b/src/components/recordset/recordset-table.tsx index b7ac84090..ef0e391c2 100644 --- a/src/components/recordset/recordset-table.tsx +++ b/src/components/recordset/recordset-table.tsx @@ -166,35 +166,6 @@ const RecordsetTable = ({ } }, []); - // useEffect(() => { - // if (!headRef.current) return; - - // const updateHeaderTop = () => { - // if (headRef.current) { - // const newTop = headRef.current.getBoundingClientRect().top; - // setHeaderTop((prevTop) => (prevTop !== newTop ? newTop : prevTop)); - // } - // }; - - // // **Step 2: Observe only relevant DOM changes** - // const observer = new MutationObserver((mutationsList) => { - // for (const mutation of mutationsList) { - // if (mutation.removedNodes.length > 0) { - // updateHeaderTop(); - // break; - // } - // } - // }); - - // const observedNode = headRef.current.parentElement || document.body; - // observer.observe(observedNode, { childList: true, subtree: true }); - - // // Cleanup function - // return () => { - // observer.disconnect(); - // }; - // }, [isInitialized]); - useEffect(()=>{ setHeaderTop(headRef.current!.getBoundingClientRect().top); },[isInitialized]); @@ -229,7 +200,7 @@ const RecordsetTable = ({ // Loop through columns and set widths stickyThs!.forEach((headerCol, index) => { const dataCol = originalThs[index]; - if (dataCol instanceof HTMLElement) { // Ensure it's an HTML element + if (dataCol instanceof HTMLElement) { const colWidth = dataCol.offsetWidth; // Get the actual width of the column headerCol.style.width = `${colWidth}px`; // Set width on sticky header } diff --git a/test/e2e/setup/playwright.setup.ts b/test/e2e/setup/playwright.setup.ts index e569184dd..eb437f77f 100644 --- a/test/e2e/setup/playwright.setup.ts +++ b/test/e2e/setup/playwright.setup.ts @@ -128,12 +128,6 @@ async function checkUserSessions(): Promise<{ session: any, authCookie: string } * create the catalog and data */ async function createCatalog(testConfiguration: any, projectNames: string[], isManual?: boolean) { - if(!process.env.PWD){ - process.env.PWD='c/Users/kenys/Desktop/ISI/chaise'; - - } - console.log(process.env.PWD); - return new Promise(async (resolve, reject) => { testConfiguration.setup.url = process.env.ERMREST_URL; testConfiguration.setup.authCookie = testConfiguration.authCookie; From d51e1f5eaa52689f24fa111e031ebcd4c6041264 Mon Sep 17 00:00:00 2001 From: Keny Shah Date: Wed, 26 Mar 2025 14:36:30 -0700 Subject: [PATCH 09/17] Different approach for calculating header top to show sticky header for recordsettable --- src/assets/scss/_recordset-table.scss | 2 +- src/components/app-wrapper.tsx | 3 + src/components/navbar/navbar.tsx | 21 +++- src/components/recordset/recordset-table.tsx | 110 +++++++++++------- src/components/recordset/recordset.tsx | 26 ++++- src/hooks/navbar.ts | 16 +++ src/providers/navbar.tsx | 40 +++++++ src/utils/ui-utils.ts | 2 + test-results/.last-run.json | 11 -- test/e2e/docker/Dockerfile.chaise-test-env | 3 +- .../docker/Dockerfile.chaise-test-env.local | 5 +- test/e2e/locators/recordset.ts | 4 +- 12 files changed, 178 insertions(+), 65 deletions(-) create mode 100644 src/hooks/navbar.ts create mode 100644 src/providers/navbar.tsx delete mode 100644 test-results/.last-run.json diff --git a/src/assets/scss/_recordset-table.scss b/src/assets/scss/_recordset-table.scss index 2fb6d3344..64d1b024e 100644 --- a/src/assets/scss/_recordset-table.scss +++ b/src/assets/scss/_recordset-table.scss @@ -259,7 +259,7 @@ top: 0; } .no-scroll-bar { - display: none !important; + display: none !important; position: absolute !important; } .sticky-header { diff --git a/src/components/app-wrapper.tsx b/src/components/app-wrapper.tsx index d9eb40b40..7be5a778f 100644 --- a/src/components/app-wrapper.tsx +++ b/src/components/app-wrapper.tsx @@ -34,6 +34,7 @@ import { CLASS_NAMES } from '@isrd-isi-edu/chaise/src/utils/constants'; import { clickHref } from '@isrd-isi-edu/chaise/src/utils/ui-utils'; import { isSameOrigin } from '@isrd-isi-edu/chaise/src/utils/uri-utils'; import { windowRef } from '@isrd-isi-edu/chaise/src/utils/window-ref'; +import NavbarProvider from '@isrd-isi-edu/chaise/src/providers/navbar'; type AppWrapperProps = { /** @@ -353,6 +354,7 @@ const AppWrapper = ({ return ( + ( @@ -373,6 +375,7 @@ const AppWrapper = ({ {children} + ); diff --git a/src/components/navbar/navbar.tsx b/src/components/navbar/navbar.tsx index a1288eee2..6ac096011 100644 --- a/src/components/navbar/navbar.tsx +++ b/src/components/navbar/navbar.tsx @@ -38,6 +38,7 @@ import { import { isObjectAndNotNull, isStringAndNotEmpty } from '@isrd-isi-edu/chaise/src/utils/type-utils'; import { debounce } from '@isrd-isi-edu/chaise/src/utils/ui-utils'; import { MESSAGE_MAP } from '@isrd-isi-edu/chaise/src/utils/message-map'; +import useNavbar from '@isrd-isi-edu/chaise/src/hooks/navbar'; const ChaiseNavbar = (): JSX.Element => { const catalogId: string = ConfigService.catalogID; @@ -61,11 +62,16 @@ const ChaiseNavbar = (): JSX.Element => { const [openedDropDownIndex, setOpenedDropDownIndex] = useState(); const dropdownWrapper = useRef(null); + const headerRef = useRef(null); const isValueDefined = (val: any): boolean => (val !== undefined && val !== null); const isVersioned = (): boolean => (!!catalogId.split('@')[1]); + //To set the navHeader's height + const {setNavHeaderHeight} = useNavbar(); + + useEffect(() => { const root = typeof cc.navbarMenu === 'object' ? { ...cc.navbarMenu } : {}; @@ -377,8 +383,21 @@ const ChaiseNavbar = (): JSX.Element => { }); }; + // To create a ResizeObserver to monitor height changes of the nav header element + useEffect(() => { + const resizeObserver = new ResizeObserver((entries) => { + if (entries[0].contentRect) { + setNavHeaderHeight(entries[0].contentRect.height); + } + }); + + if (headerRef.current) resizeObserver.observe(headerRef.current); + + return () => resizeObserver.disconnect(); + }, []); + return ( -
{/* This div will be used as the target (end of table) for the intersection observer to hide the top scrollbar when the bottom one is visible */} -
- {config.displayMode.indexOf(RecordsetDisplayMode.RELATED) !== 0 && } {!hasTimeoutError && numHiddenRecords > 0 && diff --git a/src/components/recordset/recordset.tsx b/src/components/recordset/recordset.tsx index 88dcc6c0e..85ead38ad 100644 --- a/src/components/recordset/recordset.tsx +++ b/src/components/recordset/recordset.tsx @@ -47,6 +47,7 @@ import { isObjectAndKeyDefined } from '@isrd-isi-edu/chaise/src/utils/type-utils import { attachContainerHeightSensors, attachMainContainerPaddingSensor, copyToClipboard } from '@isrd-isi-edu/chaise/src/utils/ui-utils'; import { createRedirectLinkFromPath, getRecordsetLink, transformCustomFilter } from '@isrd-isi-edu/chaise/src/utils/uri-utils'; import { windowRef } from '@isrd-isi-edu/chaise/src/utils/window-ref'; +import useNavbar from '@isrd-isi-edu/chaise/src/hooks/navbar'; const Recordset = ({ initialReference, @@ -127,6 +128,7 @@ const RecordsetInner = ({ }: RecordsetInnerProps): JSX.Element => { const { dispatchError, errors } = useError(); + const { setTopContainerHeight } = useNavbar(); const { logRecordsetClientAction, @@ -167,12 +169,17 @@ const RecordsetInner = ({ const [facetsRegistered, setFacetsRegistered] = useState(false); const [savedQueryUpdated, setSavedQueryUpdated] = useState(false); + const [showStickyHeader, setShowStickyHeader] = useState(true); + const [permalinkTooltip, setPermalinkTooltip] = useState(MESSAGE_MAP.tooltip.permalink); const mainContainer = useRef(null); + const topContainer = useRef(null); const topRightContainer = useRef(null); const topLeftContainer = useRef(null); + const appContentContainer = useRef(null); + let showHeader = true; /** * The callbacks from faceting.tsx that we will use here @@ -369,6 +376,7 @@ const RecordsetInner = ({ // handle the scrollable container const resizeSensors = attachContainerHeightSensors(parentContainer, parentStickyArea); + // log the right click event on the permalink button const permalink = document.getElementById('permalink'); const logPermalink = () => (logRecordsetClientAction(LogActions.PERMALINK_RIGHT)); @@ -429,6 +437,19 @@ const RecordsetInner = ({ }, [isLoading]); + useEffect(() => { + const resizeObserver = new ResizeObserver((entries) => { + if (entries[0].contentRect) { + setTopContainerHeight(entries[0].contentRect.height); + } + }); + + if (topContainer.current) resizeObserver.observe(topContainer.current); + + return () => resizeObserver.disconnect(); + }, []); + + const scrollMainContainerToTop = () => { if (!mainContainer.current) return; @@ -811,6 +832,7 @@ const RecordsetInner = ({
{config.displayMode === RecordsetDisplayMode.FULLSCREEN &&