on main help sign in
fix(tui): fix list wrap behave when it has unfocusable items (#1312)
cc08edcdawndiy
internal/tui/exp/list/list.go+19−3
@@ -864,6 +864,7 @@ func (l *list[T]) selectLastItem() {
864}
864}
865
865
866func (l *list[T]) firstSelectableItemAbove(inx int) int {
866func (l *list[T]) firstSelectableItemAbove(inx int) int {
867+ unfocusableCount := 0
867 for i := inx - 1; i >= 0; i-- {
868 for i := inx - 1; i >= 0; i-- {
868 if i < 0 || i >= len(l.items) {
869 if i < 0 || i >= len(l.items) {
869 continue
870 continue
@@ -873,14 +874,16 @@ func (l *list[T]) firstSelectableItemAbove(inx int) int {
873 if _, ok := any(item).(layout.Focusable); ok {
874 if _, ok := any(item).(layout.Focusable); ok {
874 return i
875 return i
875 }
876 }
877+ unfocusableCount++
876 }
878 }
877− if inx == 0 && l.wrap {
879+ if unfocusableCount == inx && l.wrap {
878 return l.firstSelectableItemAbove(len(l.items))
880 return l.firstSelectableItemAbove(len(l.items))
879 }
881 }
880 return ItemNotFound
882 return ItemNotFound
881}
883}
882
884
883func (l *list[T]) firstSelectableItemBelow(inx int) int {
885func (l *list[T]) firstSelectableItemBelow(inx int) int {
886+ unfocusableCount := 0
884 itemsLen := len(l.items)
887 itemsLen := len(l.items)
885 for i := inx + 1; i < itemsLen; i++ {
888 for i := inx + 1; i < itemsLen; i++ {
886 if i < 0 || i >= len(l.items) {
889 if i < 0 || i >= len(l.items) {
@@ -891,8 +894,9 @@ func (l *list[T]) firstSelectableItemBelow(inx int) int {
891 if _, ok := any(item).(layout.Focusable); ok {
894 if _, ok := any(item).(layout.Focusable); ok {
892 return i
895 return i
893 }
896 }
897+ unfocusableCount++
894 }
898 }
895− if inx == itemsLen-1 && l.wrap {
899+ if unfocusableCount == itemsLen-inx-1 && l.wrap {
896 return l.firstSelectableItemBelow(-1)
900 return l.firstSelectableItemBelow(-1)
897 }
901 }
898 return ItemNotFound
902 return ItemNotFound
@@ -1352,6 +1356,14 @@ func (l *list[T]) SelectItemAbove() tea.Cmd {
1352 }
1356 }
1353 // Pre-allocate with expected capacity
1357 // Pre-allocate with expected capacity
1354 cmds := make([]tea.Cmd, 0, 2)
1358 cmds := make([]tea.Cmd, 0, 2)
1359+ if newIndex > l.selectedItemIdx && l.selectedItemIdx > 0 && l.offset > 0 {
1360+ // this means there is a section above and not showing on the top, move to the top
1361+ newIndex = l.selectedItemIdx
1362+ cmd := l.GoToTop()
1363+ if cmd != nil {
1364+ cmds = append(cmds, cmd)
1365+ }
1366+ }
1355 if newIndex == 1 {
1367 if newIndex == 1 {
1356 peakAboveIndex := l.firstSelectableItemAbove(newIndex)
1368 peakAboveIndex := l.firstSelectableItemAbove(newIndex)
1357 if peakAboveIndex == ItemNotFound {
1369 if peakAboveIndex == ItemNotFound {
@@ -1383,12 +1395,16 @@ func (l *list[T]) SelectItemBelow() tea.Cmd {
1383
1395
1384 newIndex := l.firstSelectableItemBelow(l.selectedItemIdx)
1396 newIndex := l.firstSelectableItemBelow(l.selectedItemIdx)
1385 if newIndex == ItemNotFound {
1397 if newIndex == ItemNotFound {
1386− // no item above
1398+ // no item below
1387 return nil
1399 return nil
1388 }
1400 }
1389 if newIndex < 0 || newIndex >= len(l.items) {
1401 if newIndex < 0 || newIndex >= len(l.items) {
1390 return nil
1402 return nil
1391 }
1403 }
1404+ if newIndex < l.selectedItemIdx {
1405+ // reset offset when wrap to the top to show the top section if it exists
1406+ l.offset = 0
1407+ }
1392 l.prevSelectedItemIdx = l.selectedItemIdx
1408 l.prevSelectedItemIdx = l.selectedItemIdx
1393 l.selectedItemIdx = newIndex
1409 l.selectedItemIdx = newIndex
1394 l.movingByItem = true
1410 l.movingByItem = true