Tree title overflow → on-demand measurement (DES-132)
Version: 0.4.0 · Type: ✨ Feature
Related issue: DES-132 (sub-task under the DES-75 Tree parent issue)
Problem
The left-side folder list in web4 (FolderTreeAdapter) showed noticeable lag once the folder count reached the hundreds. The DOM rows themselves are not complex, so the root cause is not the number of nodes but the fact that on every TreeNode mount, useTitleOverflow synchronously runs measureTitleOverflow once:
titleEl.clientWidth // read layout
host.appendChild(clone) // write DOM
clone.offsetWidth // read layout → forced synchronous reflow
clone.remove() // write DOM
When hundreds or thousands of nodes mount at once, this alternating "write → read → write → read" triggers an equal number of forced synchronous reflows (layout thrashing). The browser cannot batch them, which is the main source of the lag. On top of that, each node keeps a permanent ResizeObserver, which further amplifies the cost.
Changed Files
src/components/Tree/Tree.tsxsrc/components/Tree/__tests__/Tree.test.tsxsrc/components/Tree/__tests__/Tree.ct.spec.tsx
Changes
useTitleOverflowchanged from "measure on mount + a permanent per-node ResizeObserver" to on-demand measurement: it now returns[overflowed, measure], andmeasureonly runs once when the pointer enters / the row is focused. If the same title has already been measured, it is skipped. The per-node reflow during mount drops to 0.- The
TreeNoderow container addsonPointerEnter/onFocusto triggermeasure(onFocuspreserves keyboard accessibility, so a focused row can also trigger the tooltip wrapping). - The tooltip is only needed on hover / focus anyway, so deferring the measurement to that moment is imperceptible to the user.
Behavior Change
(contract update)
- An overflowing title is no longer immediately wrapped in a Tooltip trigger on mount; it is wrapped after the pointer first enters / the row is first focused.
- After removing the ResizeObserver, container width changes (such as dragging the sidebar) no longer trigger an automatic re-measure; the stale state refreshes the next time the pointer enters / the row is focused.
- Tests updated accordingly: the tooltip assertions in
Tree.test.tsx/Tree.ct.spec.tsxnow first trigger pointer enter / hover before assertingdata-state.
Notes
This is a per-node optimization that reduces the fixed cost per node; the total node count is still linear. It significantly relieves the hundreds-to-one-or-two-thousand node scenarios. If an extreme scenario with several thousand nodes appears later, a virtual list (flatten + viewport rendering) will be needed to fully solve it.