@@ -23,7 +23,6 @@ type HasAnim interface {
24 Spinning() bool
24 Spinning() bool
26−type renderedMsg struct{}
28type List[T Item] interface {
27type List[T Item] interface {
29 util.Model
28 util.Model
@@ -77,6 +76,7 @@ type confOptions struct {
77 selectedItem string
76 selectedItem string
78 focused bool
77 focused bool
79 resize bool
78 resize bool
82type list[T Item] struct {
82type list[T Item] struct {
@@ -156,6 +156,12 @@ func WithResizeByList() ListOption {
159+func WithEnableMouse() ListOption {
160+ return func(l *confOptions) {
161+ l.enableMouse = true
159func New[T Item](items []T, opts ...ListOption) List[T] {
165func New[T Item](items []T, opts ...ListOption) List[T] {
160 list := &list[T]{
166 list := &list[T]{
161 confOptions: &confOptions{
167 confOptions: &confOptions{
@@ -188,6 +194,11 @@ func (l *list[T]) Init() tea.Cmd {
188// Update implements List.
194// Update implements List.
189func (l *list[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
195func (l *list[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
190 switch msg := msg.(type) {
196 switch msg := msg.(type) {
197+ case tea.MouseWheelMsg:
199+ return l.handleMouseWheel(msg)
191 case anim.StepMsg:
202 case anim.StepMsg:
192 var cmds []tea.Cmd
203 var cmds []tea.Cmd
193 for _, item := range l.items.Slice() {
204 for _, item := range l.items.Slice() {
@@ -229,6 +240,17 @@ func (l *list[T]) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
229 return l, nil
240 return l, nil
243+func (l *list[T]) handleMouseWheel(msg tea.MouseWheelMsg) (tea.Model, tea.Cmd) {
246+ case tea.MouseWheelDown:
247+ cmd = l.MoveDown(ViewportDefaultScrollSize)
248+ case tea.MouseWheelUp:
249+ cmd = l.MoveUp(ViewportDefaultScrollSize)
232// View implements List.
254// View implements List.
233func (l *list[T]) View() string {
255func (l *list[T]) View() string {
234 if l.height <= 0 || l.width <= 0 {
256 if l.height <= 0 || l.width <= 0 {
@@ -292,9 +314,8 @@ func (l *list[T]) render() tea.Cmd {
293 // we are not rendering the first time
315 // we are not rendering the first time
294 if l.rendered != "" {
316 if l.rendered != "" {
296 // rerender everything will mostly hit cache
317 // rerender everything will mostly hit cache
297− _ = l.renderIterator(0, false)
318+ l.rendered, _ = l.renderIterator(0, false, "")
298 if l.direction == DirectionBackward {
319 if l.direction == DirectionBackward {
299 l.recalculateItemPositions()
320 l.recalculateItemPositions()
@@ -304,14 +325,17 @@ func (l *list[T]) render() tea.Cmd {
305 return focusChangeCmd
326 return focusChangeCmd
307− finishIndex := l.renderIterator(0, true)
328+ rendered, finishIndex := l.renderIterator(0, true, "")
329+ l.rendered = rendered
308 // recalculate for the initial items
331 // recalculate for the initial items
309 if l.direction == DirectionBackward {
332 if l.direction == DirectionBackward {
310 l.recalculateItemPositions()
333 l.recalculateItemPositions()
312 renderCmd := func() tea.Msg {
335 renderCmd := func() tea.Msg {
313 // render the rest
337 // render the rest
314− _ = l.renderIterator(finishIndex, false)
338+ l.rendered, _ = l.renderIterator(finishIndex, false, l.rendered)
315 // needed for backwards
339 // needed for backwards
316 if l.direction == DirectionBackward {
340 if l.direction == DirectionBackward {
317 l.recalculateItemPositions()
341 l.recalculateItemPositions()
@@ -321,7 +345,7 @@ func (l *list[T]) render() tea.Cmd {
321 l.scrollToSelection()
345 l.scrollToSelection()
324− return renderedMsg{}
348+ return nil
326 return tea.Batch(focusChangeCmd, renderCmd)
350 return tea.Batch(focusChangeCmd, renderCmd)
@@ -568,13 +592,14 @@ func (l *list[T]) blurSelectedItem() tea.Cmd {
570// render iterator renders items starting from the specific index and limits hight if limitHeight != -1
594// render iterator renders items starting from the specific index and limits hight if limitHeight != -1
571−// returns the last index
595+// returns the last index and the rendered content so far
596+// we pass the rendered content around and don't use l.rendered to prevent jumping of the content
572−func (l *list[T]) renderIterator(startInx int, limitHeight bool) int {
597+func (l *list[T]) renderIterator(startInx int, limitHeight bool, rendered string) (string, int) {
573− currentContentHeight := lipgloss.Height(l.rendered) - 1
598+ currentContentHeight := lipgloss.Height(rendered) - 1
574 itemsLen := l.items.Len()
599 itemsLen := l.items.Len()
575 for i := startInx; i < itemsLen; i++ {
600 for i := startInx; i < itemsLen; i++ {
576 if currentContentHeight >= l.height && limitHeight {
601 if currentContentHeight >= l.height && limitHeight {
577− return i
602+ return rendered, i
579 // cool way to go through the list in both directions
604 // cool way to go through the list in both directions
@@ -602,13 +627,13 @@ func (l *list[T]) renderIterator(startInx int, limitHeight bool) int {
604 if l.direction == DirectionForward {
629 if l.direction == DirectionForward {
605− l.rendered += rItem.view + strings.Repeat("\n", gap)
630+ rendered += rItem.view + strings.Repeat("\n", gap)
607− l.rendered = rItem.view + strings.Repeat("\n", gap) + l.rendered
632+ rendered = rItem.view + strings.Repeat("\n", gap) + rendered
609 currentContentHeight = rItem.end + 1 + l.gap
634 currentContentHeight = rItem.end + 1 + l.gap
611− return itemsLen
636+ return rendered, itemsLen
614func (l *list[T]) renderItem(item Item) renderedItem {
639func (l *list[T]) renderItem(item Item) renderedItem {