fix(ui): list: move focused logic to render callback

Ayman Bagabas created

Change summary

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(-)

Detailed changes

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
 }
 

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
 }

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
+	}
+}

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