on main help sign in
fix(chat): fix focus selection
13c3585Kujtim Hoxha
internal/tui/components/chat/chat.go+12−21
@@ -46,8 +46,7 @@ type messageListCmp struct {
46 width, height int
46 width, height int
47 session session.Session
47 session session.Session
48 listCmp list.ListModel
48 listCmp list.ListModel
49− focused bool // Focus state for styling
50− previousSelected int // Last selected item index for restoring focus
49+ previousSelected int // Last selected item index for restoring focus
51
50
52 lastUserMessageTime int64
51 lastUserMessageTime int64
53}
52}
@@ -56,20 +55,21 @@ type messageListCmp struct {
56// and reverse ordering (newest messages at bottom).
55// and reverse ordering (newest messages at bottom).
57func NewMessagesListCmp(app *app.App) MessageListCmp {
56func NewMessagesListCmp(app *app.App) MessageListCmp {
58 defaultKeymaps := list.DefaultKeyMap()
57 defaultKeymaps := list.DefaultKeyMap()
58+ listCmp := list.New(
59+ list.WithGapSize(1),
60+ list.WithReverse(true),
61+ list.WithKeyMap(defaultKeymaps),
62+ )
59 return &messageListCmp{
63 return &messageListCmp{
60− app: app,
64+ app: app,
61− listCmp: list.New(
65+ listCmp: listCmp,
62− list.WithGapSize(1),
63− list.WithReverse(true),
64− list.WithKeyMap(defaultKeymaps),
65− ),
66 previousSelected: list.NoSelection,
66 previousSelected: list.NoSelection,
67 }
67 }
68}
68}
69
69
70// Init initializes the component (no initialization needed).
70// Init initializes the component (no initialization needed).
71func (m *messageListCmp) Init() tea.Cmd {
71func (m *messageListCmp) Init() tea.Cmd {
72− return nil
72+ return tea.Sequence(m.listCmp.Init(), m.listCmp.Blur())
73}
73}
74
74
75// Update handles incoming messages and updates the component state.
75// Update handles incoming messages and updates the component state.
@@ -483,24 +483,15 @@ func (m *messageListCmp) SetSize(width int, height int) tea.Cmd {
483
483
484// Blur implements MessageListCmp.
484// Blur implements MessageListCmp.
485func (m *messageListCmp) Blur() tea.Cmd {
485func (m *messageListCmp) Blur() tea.Cmd {
486− m.focused = false
487− m.previousSelected = m.listCmp.SelectedIndex()
488− m.listCmp.ClearSelection()
489− return nil
486+ return m.listCmp.Blur()
490}
487}
491
488
492// Focus implements MessageListCmp.
489// Focus implements MessageListCmp.
493func (m *messageListCmp) Focus() tea.Cmd {
490func (m *messageListCmp) Focus() tea.Cmd {
494− m.focused = true
495− if m.previousSelected != list.NoSelection {
496− m.listCmp.SetSelected(m.previousSelected)
497− } else {
498− m.listCmp.SetSelected(len(m.listCmp.Items()) - 1)
499− }
500− return nil
491+ return m.listCmp.Focus()
501}
492}
502
493
503// IsFocused implements MessageListCmp.
494// IsFocused implements MessageListCmp.
504func (m *messageListCmp) IsFocused() bool {
495func (m *messageListCmp) IsFocused() bool {
505− return m.focused
496+ return m.listCmp.IsFocused()
506}
497}
internal/tui/components/core/list/list.go+29−16
@@ -31,6 +31,7 @@ const (
31type ListModel interface {
31type ListModel interface {
32 util.Model
32 util.Model
33 layout.Sizeable
33 layout.Sizeable
34+ layout.Focusable
34 SetItems([]util.Model) tea.Cmd // Replace all items in the list
35 SetItems([]util.Model) tea.Cmd // Replace all items in the list
35 AppendItem(util.Model) tea.Cmd // Add an item to the end of the list
36 AppendItem(util.Model) tea.Cmd // Add an item to the end of the list
36 PrependItem(util.Model) tea.Cmd // Add an item to the beginning of the list
37 PrependItem(util.Model) tea.Cmd // Add an item to the beginning of the list
@@ -40,7 +41,6 @@ type ListModel interface {
40 Items() []util.Model // Get all items in the list
41 Items() []util.Model // Get all items in the list
41 SelectedIndex() int // Get the index of the currently selected item
42 SelectedIndex() int // Get the index of the currently selected item
42 SetSelected(int) tea.Cmd // Set the selected item by index and scroll to it
43 SetSelected(int) tea.Cmd // Set the selected item by index and scroll to it
43− ClearSelection() tea.Cmd // Clear the current selection
44 Filter(string) tea.Cmd // Filter items based on a search term
44 Filter(string) tea.Cmd // Filter items based on a search term
45}
45}
46
46
@@ -143,6 +143,8 @@ type model struct {
143 inputStyle lipgloss.Style // Style for the input field
143 inputStyle lipgloss.Style // Style for the input field
144 hideFilterInput bool // Whether to hide the filter input field
144 hideFilterInput bool // Whether to hide the filter input field
145 currentSearch string // Current search term for filtering
145 currentSearch string // Current search term for filtering
146+
147+ isFocused bool // Whether the list is currently focused
146}
148}
147
149
148// listOptions is a function type for configuring list options.
150// listOptions is a function type for configuring list options.
@@ -238,6 +240,7 @@ func New(opts ...listOptions) ListModel {
238 selectionState: selectionState{selectedIndex: NoSelection},
240 selectionState: selectionState{selectedIndex: NoSelection},
239 filterPlaceholder: "Type to filter...",
241 filterPlaceholder: "Type to filter...",
240 inputStyle: t.S().Base.Padding(0, 1, 1, 1),
242 inputStyle: t.S().Base.Padding(0, 1, 1, 1),
243+ isFocused: true,
241 }
244 }
242 for _, opt := range opts {
245 for _, opt := range opts {
243 opt(m)
246 opt(m)
@@ -746,8 +749,10 @@ func (m *model) ensureVisibleReverse(cachedItem renderedItem) {
746func (m *model) goToBottom() tea.Cmd {
749func (m *model) goToBottom() tea.Cmd {
747 cmds := []tea.Cmd{m.blurSelected()}
750 cmds := []tea.Cmd{m.blurSelected()}
748 m.viewState.reverse = true
751 m.viewState.reverse = true
752+ if m.isFocused {
749− m.selectionState.selectedIndex = m.findLastSelectableItem()
753+ m.selectionState.selectedIndex = m.findLastSelectableItem()
750− cmds = append(cmds, m.focusSelected())
754+ cmds = append(cmds, m.focusSelected())
755+ }
751 m.ResetView()
756 m.ResetView()
752 return tea.Batch(cmds...)
757 return tea.Batch(cmds...)
753}
758}
@@ -774,6 +779,9 @@ func (m *model) ResetView() {
774// focusSelected gives focus to the currently selected item if it supports focus.
779// focusSelected gives focus to the currently selected item if it supports focus.
775// Triggers a re-render of the item to show its focused state.
780// Triggers a re-render of the item to show its focused state.
776func (m *model) focusSelected() tea.Cmd {
781func (m *model) focusSelected() tea.Cmd {
782+ if !m.isFocused {
783+ return nil // No focus change if the list is not focused
784+ }
777 if !m.selectionState.isValidIndex(len(m.filteredItems)) {
785 if !m.selectionState.isValidIndex(len(m.filteredItems)) {
778 return nil
786 return nil
779 }
787 }
@@ -953,9 +961,7 @@ func (m *model) UpdateItem(inx int, item util.Model) {
953 }
961 }
954 m.filteredItems[inx] = item
962 m.filteredItems[inx] = item
955 if m.selectionState.selectedIndex == inx {
963 if m.selectionState.selectedIndex == inx {
956− if i, ok := m.filteredItems[m.selectionState.selectedIndex].(layout.Focusable); ok {
964+ m.focusSelected()
957− i.Focus()
958− }
959 }
965 }
960 m.setItemSize(inx)
966 m.setItemSize(inx)
961 m.rerenderItem(inx)
967 m.rerenderItem(inx)
@@ -1334,14 +1340,21 @@ func (m *model) SetSelected(index int) tea.Cmd {
1334 return tea.Batch(cmds...)
1340 return tea.Batch(cmds...)
1335}
1341}
1336
1342
1337−// ClearSelection clears the current selection and focus.
1343+// Blur implements ListModel.
1338−func (m *model) ClearSelection() tea.Cmd {
1344+func (m *model) Blur() tea.Cmd {
1339− cmds := []tea.Cmd{}
1345+ m.isFocused = false
1340− if m.selectionState.selectedIndex >= 0 && m.selectionState.selectedIndex < len(m.filteredItems) {
1346+ cmd := m.blurSelected()
1341− if i, ok := m.filteredItems[m.selectionState.selectedIndex].(layout.Focusable); ok {
1347+ return cmd
1342− cmds = append(cmds, i.Blur())
1348+}
1343− }
1349+
1344− }
1350+// Focus implements ListModel.
1345− m.selectionState.selectedIndex = NoSelection
1351+func (m *model) Focus() tea.Cmd {
1346− return tea.Batch(cmds...)
1352+ m.isFocused = true
1353+ cmd := m.focusSelected()
1354+ return cmd
1355+}
1356+
1357+// IsFocused implements ListModel.
1358+func (m *model) IsFocused() bool {
1359+ return m.isFocused
1347}
1360}