From c81bc8400c3aa52efa1a370484a34f2aa76eb8c2 Mon Sep 17 00:00:00 2001 From: Jin Yi Date: Tue, 10 Mar 2026 11:29:42 +0900 Subject: [PATCH] fix: virtual scroll pagination not working in media asset list view (#9646) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Fix virtual scroll pagination not triggering in media asset panel list view. ## Changes **What**: `VirtualGrid` in `AssetsSidebarListView` was missing `maxColumns=1` and had an incorrect default item height (200px vs actual ~48px). Without `maxColumns`, `cols` was calculated as `floor(containerWidth / 200)` (e.g. 2), causing the row count to be halved and `isNearEnd` to never fire correctly. Added `:max-columns="1"` and `:default-item-height="48"` to fix pagination. Added regression tests to `VirtualGrid.test.ts`. ## Review Focus The root cause: `VirtualGrid.cols` computed as `floor(width/200)` instead of `1` for single-column list layout, breaking spacer heights and `approach-end` detection. Test covers both the fix (approach-end fires with maxColumns=1) and the bug reproduction (does not fire without it). ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-9646-fix-virtual-scroll-pagination-not-working-in-media-asset-list-view-31e6d73d3650813d973ad19638ad6933) by [Unito](https://www.unito.io) --- src/components/common/VirtualGrid.test.ts | 87 +++++++++++++++++++ .../sidebar/tabs/AssetsSidebarListView.vue | 2 + 2 files changed, 89 insertions(+) diff --git a/src/components/common/VirtualGrid.test.ts b/src/components/common/VirtualGrid.test.ts index b76640a7f7..2834e92199 100644 --- a/src/components/common/VirtualGrid.test.ts +++ b/src/components/common/VirtualGrid.test.ts @@ -155,6 +155,93 @@ describe('VirtualGrid', () => { wrapper.unmount() }) + it('emits approach-end for single-column list when scrolled near bottom', async () => { + const items = createItems(50) + mockedWidth.value = 400 + mockedHeight.value = 600 + mockedScrollY.value = 0 + + const wrapper = mount(VirtualGrid, { + props: { + items, + gridStyle: { + display: 'grid', + gridTemplateColumns: 'minmax(0, 1fr)' + }, + defaultItemHeight: 48, + defaultItemWidth: 200, + maxColumns: 1, + bufferRows: 1 + }, + slots: { + item: `` + }, + attachTo: document.body + }) + + await nextTick() + + expect(wrapper.emitted('approach-end')).toBeUndefined() + + // Scroll near the end: 50 items * 48px = 2400px total + // viewRows = ceil(600/48) = 13, buffer = 1 + // Need toCol >= items.length - cols*bufferRows = 50 - 1 = 49 + // toCol = (offsetRows + bufferRows + viewRows) * cols + // offsetRows = floor(scrollY / 48) + // Need (offsetRows + 1 + 13) * 1 >= 49 → offsetRows >= 35 + // scrollY = 35 * 48 = 1680 + mockedScrollY.value = 1680 + await nextTick() + + expect(wrapper.emitted('approach-end')).toBeDefined() + + wrapper.unmount() + }) + + it('does not emit approach-end without maxColumns in single-column layout', async () => { + // Demonstrates the bug: without maxColumns=1, cols is calculated + // from width/itemWidth (400/200 = 2), causing incorrect row math + const items = createItems(50) + mockedWidth.value = 400 + mockedHeight.value = 600 + mockedScrollY.value = 0 + + const wrapper = mount(VirtualGrid, { + props: { + items, + gridStyle: { + display: 'grid', + gridTemplateColumns: 'minmax(0, 1fr)' + }, + defaultItemHeight: 48, + defaultItemWidth: 200, + // No maxColumns — cols will be floor(400/200) = 2 + bufferRows: 1 + }, + slots: { + item: `` + }, + attachTo: document.body + }) + + await nextTick() + + // Same scroll position as the passing test + mockedScrollY.value = 1680 + await nextTick() + + // With cols=2, toCol = (35+1+13)*2 = 98, which exceeds items.length (50) + // remainingCol = 50-98 = -48, hasMoreToRender = false → isNearEnd = false + // The approach-end never fires at the correct scroll position + expect(wrapper.emitted('approach-end')).toBeUndefined() + + wrapper.unmount() + }) + it('forces cols to maxColumns when maxColumns is finite', async () => { mockedWidth.value = 100 mockedHeight.value = 200 diff --git a/src/components/sidebar/tabs/AssetsSidebarListView.vue b/src/components/sidebar/tabs/AssetsSidebarListView.vue index 91e9e02478..c6db1a23fb 100644 --- a/src/components/sidebar/tabs/AssetsSidebarListView.vue +++ b/src/components/sidebar/tabs/AssetsSidebarListView.vue @@ -18,6 +18,8 @@ class="flex-1" :items="assetItems" :grid-style="listGridStyle" + :max-columns="1" + :default-item-height="48" @approach-end="emit('approach-end')" >