Skip to content

Commit 8d86a67

Browse files
committed
fix(Table): scope expansion re-measure to changed rows; document RO limits
Address CodeRabbit nitpicks on #6379: - Watcher: diff the before/after TanStack expanded state and only call virtualizer.measureElement on rows whose expanded flag actually toggled, instead of sweeping every visible row on every change. When the state is the boolean sentinel `true` (expand-all) we fall back to the full sweep since there are no individual row ids to diff. - Watcher: drop the redundant inner `if (!virtualizer)` guard — the whole watch is already inside `if (virtualizer) { ... }`. - JSDoc: note that TanStack's ResizeObserver is attached to the main <tr> only, and the built-in expansion watcher fires on expand/collapse toggles but not on intra-expansion size changes (late-loading images, async content, nested toggles). Users whose expansion region can resize after mount should call `virtualizer.measure()` themselves.
1 parent 6f94efb commit 8d86a67

1 file changed

Lines changed: 39 additions & 8 deletions

File tree

src/runtime/components/Table.vue

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,13 @@ export interface TableProps<T extends TableData = TableData> extends TableOption
107107
* `measureElement` already includes the expanded region (e.g. by measuring a wrapper),
108108
* return only the collapsed-row height to avoid double-counting.
109109
*
110+
* TanStack Virtual's `ResizeObserver` is attached to the main `<tr>` only, and this
111+
* component re-measures rows whenever the TanStack expanded state toggles. It does **not**
112+
* observe size changes *inside* the expansion sibling — late-loading images, async content,
113+
* or nested toggles that resize the expansion region after mount won't trigger an automatic
114+
* re-measure. Call `virtualizer.measure()` (or `virtualizer.measureElement(row)`) yourself
115+
* in those cases.
116+
*
110117
* Note: when enabled, the divider between rows, sticky and row pinning properties are not supported.
111118
* @see https://tanstack.com/virtual/latest/docs/api/virtualizer#options
112119
* @defaultValue false
@@ -472,19 +479,43 @@ function measureRowRef(el: Element | ComponentPublicInstance | null) {
472479
virtualizer.value.measureElement(el as Element)
473480
}
474481
475-
// Re-measure rendered rows whenever the expanded state changes. Tanstack's
476-
// ResizeObserver is attached to the main `<tr>`, but expansion inserts a
477-
// *sibling* `<tr>` that doesn't resize the main row, so the RO never fires —
478-
// this manual re-measure keeps the virtualizer's cached heights in sync.
482+
// Re-measure rows whose expanded state toggled. TanStack's ResizeObserver is
483+
// attached to the main `<tr>`, but expansion inserts a *sibling* `<tr>` that
484+
// doesn't resize the main row, so the RO never fires — this manual re-measure
485+
// keeps the virtualizer's cached heights in sync. Diff the before/after state
486+
// and only re-measure rows whose expanded flag actually changed. When the
487+
// expanded state is the sentinel `true` (expand-all) we fall back to a full
488+
// sweep since there are no individual row ids to diff against.
479489
if (virtualizer) {
480490
watch(
481491
() => tableApi.getState().expanded,
482-
() => {
492+
(next, prev) => {
483493
nextTick(() => {
484494
const root = rootRef.value?.$el as HTMLElement | undefined
485-
if (!virtualizer) return
486-
root?.querySelectorAll<HTMLElement>('[data-index]').forEach((row) => {
487-
virtualizer.value.measureElement(row)
495+
if (!root) return
496+
497+
const measureAll = () => {
498+
root.querySelectorAll<HTMLElement>('[data-index]').forEach((row) => {
499+
virtualizer.value.measureElement(row)
500+
})
501+
}
502+
503+
if (typeof next === 'boolean' || typeof prev === 'boolean') {
504+
measureAll()
505+
return
506+
}
507+
508+
const prevMap = (prev ?? {}) as Record<string, boolean>
509+
const nextMap = (next ?? {}) as Record<string, boolean>
510+
const changedIds = new Set<string>()
511+
for (const id in nextMap) if (!!nextMap[id] !== !!prevMap[id]) changedIds.add(id)
512+
for (const id in prevMap) if (!!nextMap[id] !== !!prevMap[id]) changedIds.add(id)
513+
514+
changedIds.forEach((id) => {
515+
const index = tableApi.getRow(id)?.index
516+
if (index === undefined) return
517+
const el = root.querySelector<HTMLElement>(`[data-index="${index}"]`)
518+
if (el) virtualizer.value.measureElement(el)
488519
})
489520
})
490521
},

0 commit comments

Comments
 (0)