@@ -56,7 +56,6 @@ const (
56type renderedItem struct {
56type renderedItem struct {
58 view string
58 view string
60 height int
59 height int
@@ -84,6 +83,8 @@ type list[T Item] struct {
84 renderedItems map[string]renderedItem
83 renderedItems map[string]renderedItem
86 rendered string
85 rendered string
89type listOption func(*confOptions)
90type listOption func(*confOptions)
@@ -209,7 +210,9 @@ func (l *list[T]) View() string {
209 lines := strings.Split(view, "\n")
210 lines := strings.Split(view, "\n")
211 start, end := l.viewPosition()
212 start, end := l.viewPosition()
212− lines = lines[start : end+1]
213+ viewStart := max(0, start)
214+ viewEnd := min(len(lines), end+1)
215+ lines = lines[viewStart:viewEnd]
213 return strings.Join(lines, "\n")
216 return strings.Join(lines, "\n")
@@ -245,7 +248,13 @@ func (l *list[T]) render() tea.Cmd {
245 return nil
248 return nil
247 l.setDefaultSelected()
250 l.setDefaultSelected()
252+ var focusChangeCmd tea.Cmd
248− focusCmd := l.focusSelectedItem()
254+ focusChangeCmd = l.focusSelectedItem()
256+ focusChangeCmd = l.blurSelectedItem()
249 // we are not rendering the first time
258 // we are not rendering the first time
250 if l.rendered != "" {
259 if l.rendered != "" {
251 l.rendered = ""
260 l.rendered = ""
@@ -258,7 +267,7 @@ func (l *list[T]) render() tea.Cmd {
258 if l.focused {
267 if l.focused {
259 l.scrollToSelection()
268 l.scrollToSelection()
261− return focusCmd
270+ return focusChangeCmd
263 finishIndex := l.renderIterator(0, true)
272 finishIndex := l.renderIterator(0, true)
264 // recalculate for the initial items
273 // recalculate for the initial items
@@ -276,9 +285,10 @@ func (l *list[T]) render() tea.Cmd {
276 if l.focused {
285 if l.focused {
277 l.scrollToSelection()
286 l.scrollToSelection()
279 return renderedMsg{}
289 return renderedMsg{}
281− return tea.Batch(focusCmd, renderCmd)
291+ return tea.Batch(focusChangeCmd, renderCmd)
284func (l *list[T]) setDefaultSelected() {
294func (l *list[T]) setDefaultSelected() {
@@ -304,11 +314,21 @@ func (l *list[T]) scrollToSelection() {
304 if rItem.start <= start && rItem.end >= end {
314 if rItem.start <= start && rItem.end >= end {
317+ // if we are moving by item we want to move the offset so that the
318+ // whole item is visible not just portions of it
320+ if rItem.start >= start && rItem.end <= end {
323+ defer func() { l.movingByItem = false }()
307− // item already in view do nothing
325+ // item already in view do nothing
308− if rItem.start >= start && rItem.start <= end {
326+ if rItem.start >= start && rItem.start <= end {
310− } else if rItem.end <= end && rItem.end >= start {
329+ if rItem.end >= start && rItem.end <= end {
314 if rItem.height >= l.height {
334 if rItem.height >= l.height {
@@ -320,11 +340,22 @@ func (l *list[T]) scrollToSelection() {
323− itemMiddleStart := rItem.start + rItem.height/2 + 1
324− if l.direction == DirectionForward {
325− l.offset = itemMiddleStart - l.height/2
327− l.offset = max(0, lipgloss.Height(l.rendered)-(itemMiddleStart+l.height/2))
343+ renderedLines := lipgloss.Height(l.rendered) - 1
345+ // If item is above the viewport, make it the first item
346+ if rItem.start < start {
347+ if l.direction == DirectionForward {
348+ l.offset = rItem.start
350+ l.offset = max(0, renderedLines-rItem.start-l.height+1)
352+ } else if rItem.end > end {
353+ // If item is below the viewport, make it the last item
354+ if l.direction == DirectionForward {
355+ l.offset = max(0, rItem.end-l.height+1)
357+ l.offset = max(0, renderedLines-rItem.end)
@@ -446,32 +477,26 @@ func (l *list[T]) focusSelectedItem() tea.Cmd {
446 if f, ok := any(item).(layout.Focusable); ok {
477 if f, ok := any(item).(layout.Focusable); ok {
447 if item.ID() == l.selectedItem && !f.IsFocused() {
478 if item.ID() == l.selectedItem && !f.IsFocused() {
448 cmds = append(cmds, f.Focus())
479 cmds = append(cmds, f.Focus())
449− if cache, ok := l.renderedItems[item.ID()]; ok {
480+ delete(l.renderedItems, item.ID())
451− l.renderedItems[item.ID()] = cache
453 } else if item.ID() != l.selectedItem && f.IsFocused() {
481 } else if item.ID() != l.selectedItem && f.IsFocused() {
454 cmds = append(cmds, f.Blur())
482 cmds = append(cmds, f.Blur())
455− if cache, ok := l.renderedItems[item.ID()]; ok {
483+ delete(l.renderedItems, item.ID())
457− l.renderedItems[item.ID()] = cache
462 return tea.Batch(cmds...)
487 return tea.Batch(cmds...)
465−func (l *list[T]) blurItems() tea.Cmd {
490+func (l *list[T]) blurSelectedItem() tea.Cmd {
491+ if l.selectedItem == "" || l.focused {
466 var cmds []tea.Cmd
494 var cmds []tea.Cmd
467 for _, item := range l.items {
495 for _, item := range l.items {
468 if f, ok := any(item).(layout.Focusable); ok {
496 if f, ok := any(item).(layout.Focusable); ok {
469 if item.ID() == l.selectedItem && f.IsFocused() {
497 if item.ID() == l.selectedItem && f.IsFocused() {
470 cmds = append(cmds, f.Blur())
498 cmds = append(cmds, f.Blur())
471− if cache, ok := l.renderedItems[item.ID()]; ok {
499+ delete(l.renderedItems, item.ID())
473− l.renderedItems[item.ID()] = cache
@@ -495,7 +520,7 @@ func (l *list[T]) renderIterator(startInx int, limitHeight bool) int {
496 item := l.items[inx]
521 item := l.items[inx]
497 var rItem renderedItem
522 var rItem renderedItem
498− if cache, ok := l.renderedItems[item.ID()]; ok && !cache.dirty {
523+ if cache, ok := l.renderedItems[item.ID()]; ok {
499 rItem = cache
524 rItem = cache
501 rItem = l.renderItem(item)
526 rItem = l.renderItem(item)
@@ -534,8 +559,8 @@ func (l *list[T]) AppendItem(T) tea.Cmd {
535// Blur implements List.
560// Blur implements List.
536func (l *list[T]) Blur() tea.Cmd {
561func (l *list[T]) Blur() tea.Cmd {
537− cmd := l.blurItems()
562+ l.focused = false
538− return tea.Batch(cmd, l.render())
563+ return l.render()
541// DeleteItem implements List.
566// DeleteItem implements List.
@@ -644,6 +669,7 @@ func (l *list[T]) SelectItemAbove() tea.Cmd {
645 item := l.items[newIndex]
670 item := l.items[newIndex]
646 l.selectedItem = item.ID()
671 l.selectedItem = item.ID()
672+ l.movingByItem = true
647 return l.render()
673 return l.render()
@@ -661,6 +687,7 @@ func (l *list[T]) SelectItemBelow() tea.Cmd {
662 item := l.items[newIndex]
688 item := l.items[newIndex]
663 l.selectedItem = item.ID()
689 l.selectedItem = item.ID()
690+ l.movingByItem = true
664 return l.render()
691 return l.render()
@@ -692,6 +719,8 @@ func (l *list[T]) SetSelected(id string) tea.Cmd {
692func (l *list[T]) reset() tea.Cmd {
719func (l *list[T]) reset() tea.Cmd {
693 var cmds []tea.Cmd
720 var cmds []tea.Cmd
694 l.rendered = ""
721 l.rendered = ""
695 l.indexMap = make(map[string]int)
724 l.indexMap = make(map[string]int)
696 l.renderedItems = make(map[string]renderedItem)
725 l.renderedItems = make(map[string]renderedItem)
697 for inx, item := range l.items {
726 for inx, item := range l.items {