From dd9e8ca15c72160f63fb017777a76029d65cbcef Mon Sep 17 00:00:00 2001 From: Ayman Bagabas Date: Wed, 21 Jan 2026 11:48:42 -0500 Subject: [PATCH] fix(ui): list: move focused logic to render callback --- internal/ui/dialog/models_list.go | 1 + internal/ui/list/filterable.go | 1 + internal/ui/list/focus.go | 13 +++++++++++++ internal/ui/list/list.go | 20 +++++++++++++++----- 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 internal/ui/list/focus.go diff --git a/internal/ui/dialog/models_list.go b/internal/ui/dialog/models_list.go index bbd4dafad3591db2e62624243fd9ae919bed5206..c0eaba437154a78df3865ab9cd0e96c5c9c57321 100644 --- a/internal/ui/dialog/models_list.go +++ b/internal/ui/dialog/models_list.go @@ -26,6 +26,7 @@ func NewModelsList(sty *styles.Styles, groups ...ModelGroup) *ModelsList { groups: groups, t: sty, } + f.RegisterRenderCallback(list.FocusedRenderCallback(f.List)) return f } diff --git a/internal/ui/list/filterable.go b/internal/ui/list/filterable.go index d3c227f0234028aea22fcc397d861c263cab034a..ed018e2e1e26e7d5a5bbc091b17c572331811dc5 100644 --- a/internal/ui/list/filterable.go +++ b/internal/ui/list/filterable.go @@ -31,6 +31,7 @@ func NewFilterableList(items ...FilterableItem) *FilterableList { List: NewList(), items: items, } + f.RegisterRenderCallback(FocusedRenderCallback(f.List)) f.SetItems(items...) return f } diff --git a/internal/ui/list/focus.go b/internal/ui/list/focus.go new file mode 100644 index 0000000000000000000000000000000000000000..6bdee37afa39a69d6d321b1894c6a5f221fc307d --- /dev/null +++ b/internal/ui/list/focus.go @@ -0,0 +1,13 @@ +package list + +// FocusedRenderCallback is a helper function that returns a render callback +// that marks items as focused during rendering. +func FocusedRenderCallback(list *List) RenderCallback { + return func(idx, selectedIdx int, item Item) Item { + if focusable, ok := item.(Focusable); ok { + focusable.SetFocused(list.Focused() && idx == selectedIdx) + return focusable.(Item) + } + return item + } +} diff --git a/internal/ui/list/list.go b/internal/ui/list/list.go index 8806551c537ecbfdcba8169bc05d7de79183b0ba..78cb437d361f8ec05d81acfa98f2e87a23755d58 100644 --- a/internal/ui/list/list.go +++ b/internal/ui/list/list.go @@ -49,9 +49,13 @@ func NewList(items ...Item) *List { return l } +// RenderCallback defines a function that can modify an item before it is +// rendered. +type RenderCallback func(idx, selectedIdx int, item Item) Item + // RegisterRenderCallback registers a callback to be called when rendering // items. This can be used to modify items before they are rendered. -func (l *List) RegisterRenderCallback(cb func(idx, selectedIdx int, item Item) Item) { +func (l *List) RegisterRenderCallback(cb RenderCallback) { l.renderCallbacks = append(l.renderCallbacks, cb) } @@ -66,6 +70,11 @@ func (l *List) SetGap(gap int) { l.gap = gap } +// Gap returns the gap between items. +func (l *List) Gap() int { + return l.gap +} + // SetReverse shows the list in reverse order. func (l *List) SetReverse(reverse bool) { l.reverse = reverse @@ -101,10 +110,6 @@ func (l *List) getItem(idx int) renderedItem { } } - if focusable, isFocusable := item.(Focusable); isFocusable { - focusable.SetFocused(l.focused && idx == l.selectedIdx) - } - rendered := item.Render(l.width) rendered = strings.TrimRight(rendered, "\n") height := countLines(rendered) @@ -348,6 +353,11 @@ func (l *List) RemoveItem(idx int) { } } +// Focused returns whether the list is focused. +func (l *List) Focused() bool { + return l.focused +} + // Focus sets the focus state of the list. func (l *List) Focus() { l.focused = true