From f87fc6b86c1b83fe967bd9e1dc25d0687dd903b7 Mon Sep 17 00:00:00 2001 From: yashin4112 Date: Tue, 21 Jul 2026 12:46:45 +0530 Subject: [PATCH 1/4] fix: improve responsive behavior of AssetMapper and EntryMapper tables to ensure pagination and footer remain visible on zoom --- .../components/ContentMapper/assetMapper.tsx | 40 +++- .../components/ContentMapper/entryMapper.tsx | 196 ++++++++++-------- ui/src/components/ContentMapper/index.scss | 138 +++++++++++- 3 files changed, 280 insertions(+), 94 deletions(-) diff --git a/ui/src/components/ContentMapper/assetMapper.tsx b/ui/src/components/ContentMapper/assetMapper.tsx index 89fd69a8..3b6f68a7 100644 --- a/ui/src/components/ContentMapper/assetMapper.tsx +++ b/ui/src/components/ContentMapper/assetMapper.tsx @@ -68,6 +68,41 @@ const AssetMapper = ({ fetchAssets('', { seedSelection: true }); }, []); + // react-window sizes its virtual scroll viewport from this JS number, not CSS. The layout + // caps the mapper to the available step-content area, so we measure the bounded outer box + // (.entry-asset-mapper) minus the fixed chrome (toggle, search/panel row, pagination bar, + // Save footer) and feed that back — otherwise the table renders a fixed-height viewport + // that overruns the viewport on zoom and pushes pagination + Save off-screen. + const [tableHeight, setTableHeight] = useState(() => window.innerHeight - 520); + useEffect(() => { + const measure = () => { + // Anchor on the OUTER bounded box, NOT on .Table/.Table__body — those are sized BY + // react-window from this number, so reading them creates a runaway feedback loop. + const box = document.querySelector('.entry-asset-mapper') as HTMLElement | null; + const toggle = document.querySelector('.mapper-view-toggle') as HTMLElement | null; + const panel = document.querySelector('.asset-mapper-table .TablePanel') as HTMLElement | null; + const footer = document.querySelector('.asset-mapper-table .mapper-footer') as HTMLElement | null; + const boxH = box?.clientHeight ?? window.innerHeight - 246; + const RESERVE = + (toggle?.offsetHeight ?? 0) + + (panel?.offsetHeight ?? 64) + + (footer?.offsetHeight ?? 65) + + 56; // pagination bar (fixed) + small buffer + const avail = boxH - RESERVE; + if (avail > 80) setTableHeight(Math.floor(avail)); + }; + measure(); + const box = document.querySelector('.entry-asset-mapper') as HTMLElement | null; + const ro = new ResizeObserver(measure); + if (box) ro.observe(box); + window.addEventListener('resize', measure); + return () => { + ro.disconnect(); + window.removeEventListener('resize', measure); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tableData?.length]); + // Single server-paginated fetch (same pattern as entryMapper's fetchEntries). The // Venus table drives paging by calling fetchData with { skip, limit, searchText }; // we ask the API for just that page and use the returned `count` as the grand total. @@ -277,10 +312,9 @@ const AssetMapper = ({ version="v2" testId="no-results-found-page" /> : -
+
{ ), accessor: accessorCall, id: 'uuid', - width: '250px', + width: '340px', }, { disableSortBy: true, Header: {`${newMigrationData?.legacy_cms?.selectedCms?.title} UIDs:`}, accessor: accessorForCMSUid, - id: '1' + id: '1', + width: '360px', }, { disableSortBy: true, @@ -493,11 +494,41 @@ const EntryMapper = ({ handleStepChange }: entryMapperProps) => { } ]; - // Must match the .Table__body height in index.scss so the react-window list is exactly - // as tall as the scroll body. Leave ~140px below for the search row, pagination bar and - // Save footer; the body scrolls internally so all rows of a page stay reachable. - const calcHeight = () => window.innerHeight - 520; - const tableHeight = calcHeight(); + // react-window sizes its virtual scroll viewport from this JS number, not CSS. The layout + // flexes the table body to fill the available space (responsive on zoom), so we measure + // that rendered body height and feed it back here — otherwise react-window renders a + // fixed-height viewport that doesn't match the flexed body and scrolling breaks. + const [tableHeight, setTableHeight] = useState(() => window.innerHeight - 520); + useEffect(() => { + const measure = () => { + // Anchor on the OUTER bounded box (.entry-asset-mapper is capped at calc(100vh-246px)), + // NOT on .Table/.Table__body — those are sized BY react-window from this very number, so + // reading them creates a runaway feedback loop. Subtract only the fixed chrome that sits + // inside the box: the toggle row, the search/panel row, pagination bar and Save footer. + const box = document.querySelector('.entry-asset-mapper') as HTMLElement | null; + const toggle = document.querySelector('.mapper-view-toggle') as HTMLElement | null; + const panel = document.querySelector('.entry-mapper-container .TablePanel') as HTMLElement | null; + const footer = document.querySelector('.entry-mapper-table .mapper-footer') as HTMLElement | null; + const boxH = box?.clientHeight ?? window.innerHeight - 246; + const RESERVE = + (toggle?.offsetHeight ?? 0) + + (panel?.offsetHeight ?? 64) + + (footer?.offsetHeight ?? 65) + + 56; // pagination bar (fixed) + small buffer + const avail = boxH - RESERVE; + if (avail > 80) setTableHeight(Math.floor(avail)); + }; + measure(); + const box = document.querySelector('.entry-asset-mapper') as HTMLElement | null; + const ro = new ResizeObserver(measure); + if (box) ro.observe(box); + window.addEventListener('resize', measure); + return () => { + ro.disconnect(); + window.removeEventListener('resize', measure); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [contentTypeUid, tableData?.length]); return ( isLoading || newMigrationData?.isprojectMapped @@ -619,82 +650,85 @@ const EntryMapper = ({ handleStepChange }: entryMapperProps) => { {/* Entry Mapping Table */}
-
-
0 ? ' has-locale-select' : ''}`}> - {localeOptions?.length > 0 && ( -
- setSelectedLocale(opt)} + isSearchable={false} + isClearable={false} + placeholder="Select locale" + width="240px" + version="v2" + /> +
+ )} +
- } - /> - {(totalCounts > 0 || (tableData?.length ?? 0) > 0) && ( -
-
- {/* Total Entries: {totalCounts} */} -
- + key={contentTypeUid || 'entry-mapper-table'} + loading={loading} + canSearch={true} + totalCounts={totalCounts ?? 0} + data={[...tableData]} + columns={columns} + uniqueKey={'id'} + isRowSelect={true} + fullRowSelect={true} + fetchTableData={fetchData} + tableHeight={tableHeight} + equalWidthColumns={false} + columnSelector={false} + v2Features={{ pagination: true, isNewEmptyState: true }} + rowPerPageOptions={[10, 30, 50, 100]} + minBatchSizeToFetch={30} + initialRowSelectedData={initialRowSelectedData} + initialSelectedRowIds={rowIds} + itemSize={70} + getSelectedRow={handleSelectedEntries} + rowSelectCheckboxProp={{ key: '_canSelect', value: true }} + name={{ + singular: '', + plural: `${totalCounts === 0 ? 'Count' : ''}` + }} + customEmptyState={ + + } + /> + {(totalCounts > 0 || (tableData?.length ?? 0) > 0) && ( +
+
+ {/* Total Entries: {totalCounts} */}
- )} + +
+ )}
diff --git a/ui/src/components/ContentMapper/index.scss b/ui/src/components/ContentMapper/index.scss index c8987d35..e1a2f920 100644 --- a/ui/src/components/ContentMapper/index.scss +++ b/ui/src/components/ContentMapper/index.scss @@ -128,6 +128,16 @@ max-width: 75%; width: 100%; padding: 0; + min-height: 0; + + // The table wrapper fills the fields column so its child .entry-mapper-table + // has a bounded height to flex into (keeps pagination + Save footer in view on zoom). + .entry-table-wrapper.entry-mapper-container { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + } } .cta-wrapper { border-top: 1px solid $color-base-gray-40; @@ -138,7 +148,13 @@ position: relative; } .table-container { - flex: 1 0 auto; + flex: 1 1 auto; + min-height: 0; + // Panels sit side by side and must stretch to the container's full height so the + // fields column (and its internally-scrolling table) is height-bounded, not + // content-sized. align-content:stretch keeps a single flex line filling the height. + align-items: stretch; + align-content: stretch; } .table-wrapper { flex: 1; @@ -486,10 +502,7 @@ div .table-row { // Asset mapper table: venus's .Table sets min-height: 31.25rem, forcing the table // tall and pushing the pagination bar down with a dead gap. Pin an explicit height // so the table + pagination end where we want and the Save button can sit below. -.entry-asset-mapper .step-container .Table { - height: 400px !important; - min-height: 0px !important; -} + .custom-empty-state { .Icon--original { @@ -531,8 +544,17 @@ div .table-row { .entry-asset-mapper { display: flex; flex-direction: column; - height: 100%; + // Match the usable step-content area: .stepContent is height:calc(100vh - 56px) with a + // 190px top padding for the fixed stepper header, so the real space below it is + // calc(100vh - 246px). Pinning to that makes this subtree self-bounding (the stepper + // tree above isn't flex, so height:100% grew to content height on zoom and pushed the + // Save footer off with no scroll). Inner flex distributes the rest; the body scrolls + // and pagination + Save stay pinned. + height: calc(100vh - 246px); + max-height: calc(100vh - 246px); width: 100%; + overflow: hidden; + min-height: 0; // The active child mapper (its own .step-container) fills the space left by the toggle. > .step-container { @@ -633,10 +655,19 @@ div .table-row { // search row, pagination bar and Save footer. // Entry mapper only — the asset mapper lets venus size the body from its measured // tableHeight prop, and this !important would override those inline styles. + // Let the scroll body flex to fill the space .entry-mapper-table hands it, rather + // than a fixed calc(100vh - …). 100vh doesn't shrink on zoom, so a fixed height keeps + // the body full-size and shoves the pagination bar under/past the Save footer. Flexing + // makes the body shrink with the available height so pagination + footer stay in view. + // The body height is driven by react-window via the measured `tableHeight` prop (see + // entryMapper.tsx) so its native scroll works. Don't override height here — just make it + // the flex item that takes the leftover space and never forces the .Table taller than its + // container (which would push pagination/footer off-screen). &:not(.asset-mapper-container) .Table__body, &:not(.asset-mapper-container) .Table.TableWithPaginated .Table__body { - height: calc(100vh - 520px) !important; - max-height: calc(100vh - 520px) !important; + flex: 0 0 auto !important; + min-height: 0 !important; + overflow-y: auto !important; } // Empty state ("No Records Found"): venus renders a plain .Table__body with no height, @@ -674,16 +705,26 @@ div .table-row { } } - // Target actual column containers + // Target actual column containers. Honour the per-column `width` venus applies inline + // from the `columns` config (flex-basis: auto reads that width) instead of forcing every + // column to an equal `flex: 1 1 0`. Columns with an explicit width hold it; the last + // column (no width in config) grows to fill the remaining space. .Table__head__column, .Table__body__column { - flex: 1 1 0 !important; + flex: 0 0 auto; min-width: 0 !important; display: flex !important; align-items: center; border-right: 1px solid $color-brand-secondary-lightest !important; } + // The final column has no fixed width in the config — let it absorb the leftover width + // so the row still spans the full table. + .Table__head__column:last-of-type, + .Table__body__column:last-of-type { + flex: 1 1 auto; + } + // Long filenames/paths must wrap and stay inside their own column instead // of overflowing into the next one. min-width:0 above lets the flex item // actually shrink; these make the inner content honour the column width. @@ -706,3 +747,80 @@ div .table-row { } } } + +// Asset mapper table — responsive like the entry mapper. Flex column that fills the +// bounded step area; the venus table is content-sized (its body height comes from the +// measured `tableHeight` prop in assetMapper.tsx, not CSS, so react-window's native scroll +// works), and the Save footer stays pinned below. +.asset-mapper-table { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + overflow: hidden; + + // Size to content, not flex-grow — flex-grow would balloon the body and, since we measure + // to set tableHeight, create a runaway loop. + .Table { + flex: 0 0 auto; + height: auto !important; + min-height: 0 !important; + display: flex; + flex-direction: column; + } + + .Table__body { + flex: 0 0 auto !important; + min-height: 0 !important; + overflow-y: auto !important; + } + + .TableFooter, + .Pagination, + .Table__pagination { + flex: 0 0 auto; + } + + .mapper-footer { + flex: 0 0 auto; + position: static; + } +} + +// Make the entry-mapper table area responsive so the pagination bar never slides +// under (or past) the Save footer when the effective viewport shrinks on zoom. +// It's a flex column that owns all the vertical space between the locale/search row +// and the footer; the table grows/shrinks to fit and its body scrolls internally, +// while the footer stays pinned at the bottom. +.entry-mapper-table { + display: flex; + flex-direction: column; + flex: 1 1 auto; + min-height: 0; + overflow: hidden; + + // Size to content, NOT flex-grow. react-window sizes the body from the measured + // `tableHeight` prop (entryMapper.tsx); letting .Table flex-grow would balloon the body + // and, since we measure to set that prop, create a runaway loop. Content-sized keeps + // .Table = body(tableHeight) + panel + pagination, leaving room for the footer below. + .Table { + flex: 0 0 auto; + min-height: 0 !important; + height: auto !important; + display: flex; + flex-direction: column; + } + + // Keep pagination pinned just above the footer regardless of table height. + .TableFooter, + .Pagination, + .Table__pagination { + flex: 0 0 auto; + } + + // Footer never gets pushed off-screen — it's the last flex child. + .mapper-footer { + flex: 0 0 auto; + position: static; + } +} \ No newline at end of file From 3c7700627946133d5148364ae940f0c27a1a2da4 Mon Sep 17 00:00:00 2001 From: yashin4112 Date: Tue, 21 Jul 2026 12:59:16 +0530 Subject: [PATCH 2/4] snykfix: update esbuild version to 0.28.1 in package.json and package-lock.json --- ui/package-lock.json | 241 ++++++++++++++++++++++++------------------- ui/package.json | 3 +- 2 files changed, 136 insertions(+), 108 deletions(-) diff --git a/ui/package-lock.json b/ui/package-lock.json index ce719b2c..1fce8f33 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -987,12 +987,13 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==" }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", - "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "aix" @@ -1002,12 +1003,13 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz", - "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -1017,12 +1019,13 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz", - "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -1032,12 +1035,13 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz", - "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "android" @@ -1047,12 +1051,13 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz", - "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1062,12 +1067,13 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz", - "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "darwin" @@ -1077,12 +1083,13 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz", - "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -1092,12 +1099,13 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz", - "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -1107,12 +1115,13 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz", - "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1122,12 +1131,13 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz", - "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1137,12 +1147,13 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz", - "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1152,12 +1163,13 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz", - "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1167,12 +1179,13 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz", - "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1182,12 +1195,13 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz", - "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1197,12 +1211,13 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz", - "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1212,12 +1227,13 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz", - "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1227,12 +1243,13 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz", - "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "linux" @@ -1242,12 +1259,13 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz", - "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -1257,12 +1275,13 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz", - "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -1272,12 +1291,13 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz", - "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -1287,12 +1307,13 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz", - "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -1302,12 +1323,13 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz", - "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "openharmony" @@ -1317,12 +1339,13 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz", - "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "sunos" @@ -1332,12 +1355,13 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz", - "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1347,12 +1371,13 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz", - "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -1362,12 +1387,13 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz", - "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], + "license": "MIT", "optional": true, "os": [ "win32" @@ -5310,10 +5336,11 @@ } }, "node_modules/esbuild": { - "version": "0.27.3", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz", - "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -5321,32 +5348,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.3", - "@esbuild/android-arm": "0.27.3", - "@esbuild/android-arm64": "0.27.3", - "@esbuild/android-x64": "0.27.3", - "@esbuild/darwin-arm64": "0.27.3", - "@esbuild/darwin-x64": "0.27.3", - "@esbuild/freebsd-arm64": "0.27.3", - "@esbuild/freebsd-x64": "0.27.3", - "@esbuild/linux-arm": "0.27.3", - "@esbuild/linux-arm64": "0.27.3", - "@esbuild/linux-ia32": "0.27.3", - "@esbuild/linux-loong64": "0.27.3", - "@esbuild/linux-mips64el": "0.27.3", - "@esbuild/linux-ppc64": "0.27.3", - "@esbuild/linux-riscv64": "0.27.3", - "@esbuild/linux-s390x": "0.27.3", - "@esbuild/linux-x64": "0.27.3", - "@esbuild/netbsd-arm64": "0.27.3", - "@esbuild/netbsd-x64": "0.27.3", - "@esbuild/openbsd-arm64": "0.27.3", - "@esbuild/openbsd-x64": "0.27.3", - "@esbuild/openharmony-arm64": "0.27.3", - "@esbuild/sunos-x64": "0.27.3", - "@esbuild/win32-arm64": "0.27.3", - "@esbuild/win32-ia32": "0.27.3", - "@esbuild/win32-x64": "0.27.3" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escape-string-regexp": { diff --git a/ui/package.json b/ui/package.json index 4a865ca8..edc6a72a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -67,7 +67,8 @@ "lodash": "^4.18.1", "dompurify": ">=3.4.11", "form-data": ">=4.0.6", - "ws": ">=8.21.0" + "ws": ">=8.21.0", + "esbuild": ">=0.28.0" }, "eslintConfig": { "extends": [ From 8731ab9c9e4eb8edbb30170bcb6fa35836b0900f Mon Sep 17 00:00:00 2001 From: yashin4112 Date: Tue, 21 Jul 2026 15:19:23 +0530 Subject: [PATCH 3/4] commentResolved: refactor AssetMapper and EntryMapper to use useMeasuredTableHeight for responsive table height --- .../components/ContentMapper/assetMapper.tsx | 48 +++------- .../components/ContentMapper/entryMapper.tsx | 41 ++------ .../ContentMapper/useMeasuredTableHeight.ts | 94 +++++++++++++++++++ 3 files changed, 112 insertions(+), 71 deletions(-) create mode 100644 ui/src/components/ContentMapper/useMeasuredTableHeight.ts diff --git a/ui/src/components/ContentMapper/assetMapper.tsx b/ui/src/components/ContentMapper/assetMapper.tsx index 3b6f68a7..0957a8c4 100644 --- a/ui/src/components/ContentMapper/assetMapper.tsx +++ b/ui/src/components/ContentMapper/assetMapper.tsx @@ -1,5 +1,5 @@ // Libraries -import { useEffect, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { useSelector } from 'react-redux'; import { useParams } from 'react-router-dom'; import { @@ -37,6 +37,9 @@ import { computeChangedUids, } from './assetMapper.utils'; +// Hooks +import { useMeasuredTableHeight } from './useMeasuredTableHeight'; + // Styles and Assets import './index.scss'; @@ -64,44 +67,17 @@ const AssetMapper = ({ // stranded on the full-page empty state with no way to clear the search. const [searchText, setSearchText] = useState(''); + const tableWrapperRef = useRef(null); + useEffect(() => { fetchAssets('', { seedSelection: true }); }, []); - // react-window sizes its virtual scroll viewport from this JS number, not CSS. The layout - // caps the mapper to the available step-content area, so we measure the bounded outer box - // (.entry-asset-mapper) minus the fixed chrome (toggle, search/panel row, pagination bar, - // Save footer) and feed that back — otherwise the table renders a fixed-height viewport - // that overruns the viewport on zoom and pushes pagination + Save off-screen. - const [tableHeight, setTableHeight] = useState(() => window.innerHeight - 520); - useEffect(() => { - const measure = () => { - // Anchor on the OUTER bounded box, NOT on .Table/.Table__body — those are sized BY - // react-window from this number, so reading them creates a runaway feedback loop. - const box = document.querySelector('.entry-asset-mapper') as HTMLElement | null; - const toggle = document.querySelector('.mapper-view-toggle') as HTMLElement | null; - const panel = document.querySelector('.asset-mapper-table .TablePanel') as HTMLElement | null; - const footer = document.querySelector('.asset-mapper-table .mapper-footer') as HTMLElement | null; - const boxH = box?.clientHeight ?? window.innerHeight - 246; - const RESERVE = - (toggle?.offsetHeight ?? 0) + - (panel?.offsetHeight ?? 64) + - (footer?.offsetHeight ?? 65) + - 56; // pagination bar (fixed) + small buffer - const avail = boxH - RESERVE; - if (avail > 80) setTableHeight(Math.floor(avail)); - }; - measure(); - const box = document.querySelector('.entry-asset-mapper') as HTMLElement | null; - const ro = new ResizeObserver(measure); - if (box) ro.observe(box); - window.addEventListener('resize', measure); - return () => { - ro.disconnect(); - window.removeEventListener('resize', measure); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [tableData?.length]); + // Responsive table height for the asset mapper — see useMeasuredTableHeight for the why. + const tableHeight = useMeasuredTableHeight(tableWrapperRef, [tableData?.length], { + panelSelector: '.TablePanel', + footerSelector: '.mapper-footer', + }); // Single server-paginated fetch (same pattern as entryMapper's fetchEntries). The // Venus table drives paging by calling fetchData with { skip, limit, searchText }; @@ -312,7 +288,7 @@ const AssetMapper = ({ version="v2" testId="no-results-found-page" /> : -
+
{ } ]; - // react-window sizes its virtual scroll viewport from this JS number, not CSS. The layout - // flexes the table body to fill the available space (responsive on zoom), so we measure - // that rendered body height and feed it back here — otherwise react-window renders a - // fixed-height viewport that doesn't match the flexed body and scrolling breaks. - const [tableHeight, setTableHeight] = useState(() => window.innerHeight - 520); - useEffect(() => { - const measure = () => { - // Anchor on the OUTER bounded box (.entry-asset-mapper is capped at calc(100vh-246px)), - // NOT on .Table/.Table__body — those are sized BY react-window from this very number, so - // reading them creates a runaway feedback loop. Subtract only the fixed chrome that sits - // inside the box: the toggle row, the search/panel row, pagination bar and Save footer. - const box = document.querySelector('.entry-asset-mapper') as HTMLElement | null; - const toggle = document.querySelector('.mapper-view-toggle') as HTMLElement | null; - const panel = document.querySelector('.entry-mapper-container .TablePanel') as HTMLElement | null; - const footer = document.querySelector('.entry-mapper-table .mapper-footer') as HTMLElement | null; - const boxH = box?.clientHeight ?? window.innerHeight - 246; - const RESERVE = - (toggle?.offsetHeight ?? 0) + - (panel?.offsetHeight ?? 64) + - (footer?.offsetHeight ?? 65) + - 56; // pagination bar (fixed) + small buffer - const avail = boxH - RESERVE; - if (avail > 80) setTableHeight(Math.floor(avail)); - }; - measure(); - const box = document.querySelector('.entry-asset-mapper') as HTMLElement | null; - const ro = new ResizeObserver(measure); - if (box) ro.observe(box); - window.addEventListener('resize', measure); - return () => { - ro.disconnect(); - window.removeEventListener('resize', measure); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [contentTypeUid, tableData?.length]); + // Responsive table height for the entry mapper — see useMeasuredTableHeight for the why. + const tableHeight = useMeasuredTableHeight(tableWrapperRef, [contentTypeUid, tableData?.length], { + panelSelector: '.TablePanel', + footerSelector: '.mapper-footer', + }); return ( isLoading || newMigrationData?.isprojectMapped diff --git a/ui/src/components/ContentMapper/useMeasuredTableHeight.ts b/ui/src/components/ContentMapper/useMeasuredTableHeight.ts new file mode 100644 index 00000000..278dc824 --- /dev/null +++ b/ui/src/components/ContentMapper/useMeasuredTableHeight.ts @@ -0,0 +1,94 @@ +import { RefObject, useEffect, useState } from 'react'; + +/** + * Measures the height to hand to InfiniteScrollTable's `tableHeight` prop for the + * delta-iteration mappers (Entry / Asset). + * + * react-window sizes its virtual scroll viewport from that JS number, not CSS. The layout + * caps the mapper to the available step-content area, so we measure the bounded outer box + * (.entry-asset-mapper) minus the fixed chrome (toggle, search/panel row, pagination bar, + * Save footer) and feed that back — otherwise the table renders a fixed-height viewport that + * overruns the viewport on zoom and pushes pagination + Save off-screen. + * + * Anchoring on the OUTER box (never on .Table/.Table__body) is deliberate: those are sized BY + * react-window from this very number, so reading them would create a runaway feedback loop. + * + * All DOM reads are scoped to `wrapperRef` (the mapper's own wrapper element) rather than a + * global document.querySelector, so a missing node is contained to this subtree and — for + * the chrome elements we can't find — surfaces a dev-only warning instead of silently falling + * back to the magic constants. + */ +export interface MeasuredTableHeightOptions { + /** Selector for the table's search/panel row, resolved within `wrapperRef`. */ + panelSelector: string; + /** Selector for the Save footer, resolved within `wrapperRef`. */ + footerSelector: string; +} + +// Fixed chrome fallbacks, used only until the real elements are mounted/measured. +const PANEL_FALLBACK = 64; +const FOOTER_FALLBACK = 65; +const PAGINATION_AND_BUFFER = 56; // pagination bar (fixed height) + small buffer +const MIN_USABLE_HEIGHT = 80; // floor for the table body so it never collapses/goes negative +// Fallback for the bounded box height (matches .entry-asset-mapper's calc(100vh - 246px) cap), +// used only when the box isn't mounted/measured yet. +const BOX_HEIGHT_FALLBACK = 246; +const BOX_SELECTOR = '.entry-asset-mapper'; +const TOGGLE_SELECTOR = '.mapper-view-toggle'; + +export function useMeasuredTableHeight( + wrapperRef: RefObject, + deps: unknown[], + { panelSelector, footerSelector }: MeasuredTableHeightOptions, +): number { + const [tableHeight, setTableHeight] = useState(() => window.innerHeight - 520); + + useEffect(() => { + const measure = () => { + const wrapper = wrapperRef.current; + if (!wrapper) return; + + // The bounded box is a shared ancestor of the mapper; reach it from the wrapper so the + // read stays scoped and doesn't grab the wrong mapper's box if both are ever mounted. + const box = wrapper.closest(BOX_SELECTOR) as HTMLElement | null; + const toggle = box?.querySelector(TOGGLE_SELECTOR) as HTMLElement | null; + const panel = wrapper.querySelector(panelSelector) as HTMLElement | null; + const footer = wrapper.querySelector(footerSelector) as HTMLElement | null; + + if (import.meta.env.DEV) { + // A rename/markup change in venus would drop us to the magic constants and quietly + // regress the layout — warn loudly in dev so it's caught rather than shipped. + if (!box) console.warn(`useMeasuredTableHeight: "${BOX_SELECTOR}" not found — falling back.`); + if (!panel) console.warn(`useMeasuredTableHeight: "${panelSelector}" not found — using ${PANEL_FALLBACK}px fallback.`); + if (!footer) console.warn(`useMeasuredTableHeight: "${footerSelector}" not found — using ${FOOTER_FALLBACK}px fallback.`); + } + + // `||` not `??`: a momentarily 0-height box (measured before layout settles) should + // hit the fallback too, otherwise boxH is 0 and `avail` goes negative. + const boxH = box?.clientHeight || window.innerHeight - BOX_HEIGHT_FALLBACK; + const reserve = + (toggle?.offsetHeight ?? 0) + + (panel?.offsetHeight ?? PANEL_FALLBACK) + + (footer?.offsetHeight ?? FOOTER_FALLBACK) + + PAGINATION_AND_BUFFER; + // Clamp rather than skip: at extreme zoom `avail` can dip low, but keeping the previous + // (possibly large) value would re-expose the overflow this hook exists to prevent. + setTableHeight(Math.max(MIN_USABLE_HEIGHT, Math.floor(boxH - reserve))); + }; + + measure(); + const box = wrapperRef.current?.closest(BOX_SELECTOR) as HTMLElement | null; + const ro = new ResizeObserver(measure); + // Observe the bounded box when we can (it's what actually resizes); fall back to the + // wrapper so we still react to layout changes if the box lookup misses. + ro.observe(box ?? wrapperRef.current ?? document.body); + window.addEventListener('resize', measure); + return () => { + ro.disconnect(); + window.removeEventListener('resize', measure); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, deps); + + return tableHeight; +} \ No newline at end of file From da10003bf6efbc2e5b05d007df86471c47a25b03 Mon Sep 17 00:00:00 2001 From: yashin4112 Date: Tue, 21 Jul 2026 18:58:12 +0530 Subject: [PATCH 4/4] comments: adjust table column flex properties for better responsiveness and update initial table height calculation --- ui/src/components/ContentMapper/index.scss | 9 +++++---- .../components/ContentMapper/useMeasuredTableHeight.ts | 9 ++++++++- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/ui/src/components/ContentMapper/index.scss b/ui/src/components/ContentMapper/index.scss index e1a2f920..57193a51 100644 --- a/ui/src/components/ContentMapper/index.scss +++ b/ui/src/components/ContentMapper/index.scss @@ -706,12 +706,13 @@ div .table-row { } // Target actual column containers. Honour the per-column `width` venus applies inline - // from the `columns` config (flex-basis: auto reads that width) instead of forcing every - // column to an equal `flex: 1 1 0`. Columns with an explicit width hold it; the last - // column (no width in config) grows to fill the remaining space. + // from the `columns` config (flex-basis: auto reads that width) as the STARTING size, + // instead of forcing every column to an equal `flex: 1 1 0`. flex-shrink:1 (+ min-width:0) + // lets the fixed-width columns give way on narrow viewports so the row never exceeds the + // available width and forces a horizontal scrollbar; they hold their width when there's room. .Table__head__column, .Table__body__column { - flex: 0 0 auto; + flex: 0 1 auto; min-width: 0 !important; display: flex !important; align-items: center; diff --git a/ui/src/components/ContentMapper/useMeasuredTableHeight.ts b/ui/src/components/ContentMapper/useMeasuredTableHeight.ts index 278dc824..1b0e559c 100644 --- a/ui/src/components/ContentMapper/useMeasuredTableHeight.ts +++ b/ui/src/components/ContentMapper/useMeasuredTableHeight.ts @@ -33,6 +33,9 @@ const MIN_USABLE_HEIGHT = 80; // floor for the table body so it never collapses/ // Fallback for the bounded box height (matches .entry-asset-mapper's calc(100vh - 246px) cap), // used only when the box isn't mounted/measured yet. const BOX_HEIGHT_FALLBACK = 246; +// Reserve when no chrome is measured yet (toggle defaults to 0). Same terms as measure()'s +// `reserve` so the pre-measure guess uses the hook's own box model, not a stray constant. +const RESERVE_FALLBACK = PANEL_FALLBACK + FOOTER_FALLBACK + PAGINATION_AND_BUFFER; const BOX_SELECTOR = '.entry-asset-mapper'; const TOGGLE_SELECTOR = '.mapper-view-toggle'; @@ -41,7 +44,11 @@ export function useMeasuredTableHeight( deps: unknown[], { panelSelector, footerSelector }: MeasuredTableHeightOptions, ): number { - const [tableHeight, setTableHeight] = useState(() => window.innerHeight - 520); + // Pre-measure guess: same model as measure() (box fallback − reserve), clamped to the floor + // so the one frame react-window renders before the effect runs never gets a negative height. + const [tableHeight, setTableHeight] = useState(() => + Math.max(MIN_USABLE_HEIGHT, window.innerHeight - BOX_HEIGHT_FALLBACK - RESERVE_FALLBACK), + ); useEffect(() => { const measure = () => {