@@ -34,6 +34,12 @@ type List struct {
3434 // renderCallbacks is a list of callbacks to apply when rendering items.
3535 renderCallbacks []func(idx, selectedIdx int, item Item) Item
3636
37+ // totalHeightCache is a cached value of the total rendered height of
38+ // all items. It is invalidated whenever the item set changes or the
39+ // viewport width changes (which can alter per-item line counts).
40+ totalHeightCache int
41+ totalHeightValid bool
42+
3743 // cache is the F6 list-level render memo, keyed by item pointer.
3844 // Each entry stores the rendered content, a pre-split slice of
3945 // lines (so AtBottom / Render / VisibleItemIndices /
@@ -158,15 +164,25 @@ func (l *List) Len() int {
158164}
159165
160166// TotalHeight returns the total height of all items in the list.
167+// The result is cached and only recomputed when the item set or
168+// viewport width changes.
161169func (l *List) TotalHeight() int {
170+ if l.totalHeightValid {
171+ return l.totalHeightCache
172+ }
162173 total := 0
163174 for idx := range l.items {
164− item := l.getItem(idx)
165− total += item.height
175+ entry := l.renderItemEntry(idx)
176+ if entry == nil {
177+ continue
178+ }
179+ total += entry.height
166180 if l.gap > 0 && idx < len(l.items)-1 {
167181 total += l.gap
168182 }
169183 }
184+ l.totalHeightCache = total
185+ l.totalHeightValid = true
170186 return total
171187}
172188
@@ -289,6 +305,11 @@ func (l *List) renderItemEntry(idx int) *listCacheEntry {
289305 entry = &listCacheEntry{}
290306 l.cache[rawItem] = entry
291307 }
308+ // If the item's rendered height changed, the cached total height is
309+ // no longer valid and must be recomputed on the next TotalHeight call.
310+ if entry.height != height {
311+ l.totalHeightValid = false
312+ }
292313 entry.width = l.width
293314 entry.version = finalVersion
294315 entry.frozen = frozen
@@ -303,6 +324,7 @@ func (l *List) invalidateAll() {
303324 for k := range l.cache {
304325 delete(l.cache, k)
305326 }
327+ l.totalHeightValid = false
306328}
307329
308330// Invalidate drops the cache entry for the given item, forcing a
@@ -575,6 +597,7 @@ func (l *List) PrependItems(items ...Item) {
575597 if l.selectedIdx != -1 {
576598 l.selectedIdx += len(items)
577599 }
600+ l.totalHeightValid = false
578601}
579602
580603// SetItems sets the items in the list. Cache entries for items that
@@ -586,11 +609,13 @@ func (l *List) SetItems(items ...Item) {
586609 l.offsetIdx = min(l.offsetIdx, len(l.items)-1)
587610 l.offsetLine = 0
588611 l.retainCacheFor(items)
612+ l.totalHeightValid = false
589613}
590614
591615// AppendItems appends items to the list.
592616func (l *List) AppendItems(items ...Item) {
593617 l.items = append(l.items, items...)
618+ l.totalHeightValid = false
594619}
595620
596621// RemoveItem removes the item at the given index from the list.
@@ -623,6 +648,7 @@ func (l *List) RemoveItem(idx int) {
623648 l.offsetIdx = max(0, len(l.items)-1)
624649 l.offsetLine = 0
625650 }
651+ l.totalHeightValid = false
626652}
627653
628654// Focused returns whether the list is focused.