charmbracelet / huh

Build terminal forms and prompts 🤷🏻‍♀️
MIT License
4.16k stars 111 forks source link

Focus not given to first field when redirected to a huh.Form from a tea.Model #421

Open keyneston opened 1 week ago

keyneston commented 1 week ago

Describe the bug

When directed to a huh model from another bubbletea model, the initial input does not have focus. This is exacerbated by, but does not require, the use of a field validation. If a field validation is used it soft-locks the program. If a field validation is not used on the first field then one can simply tab and shift-tab back.

To Reproduce

  1. Create some kind of navigation model that directs to a huh model. (Alternatively run the reproduction code below)
  2. Put a validation on the first field of the huh model in order to soft lock it.
  3. Run the code, proceeding through the navigation model to the huh model.
  4. Try and type into the Input
  5. Try and tab/shift-tab off the Input.
package main

import (
    "fmt"

    tea "github.com/charmbracelet/bubbletea"
    "github.com/charmbracelet/huh"
)

func main() {
    tea.NewProgram(NavModel{}).Run()
}

type NavModel struct{}

func (n NavModel) Init() tea.Cmd {
    return nil
}

func (n NavModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    if msg, ok := msg.(tea.KeyMsg); ok {
        if msg.String() == "ctrl+c" {
            return n, tea.Quit
        }
        if msg.String() == "enter" {
                        // Hardcoding 80,80 because I'm being lazy in this reproduction code and not saving them after we receive them. 
                        // Sending this windowSizeMsg is necessary to get it to properly render the NewModel at all.
            return NewModel(), func() tea.Msg { return tea.WindowSizeMsg{Height: 80, Width: 80} }
        }
    }

    return n, nil
}

func (n NavModel) View() string {
    return "Press enter to proceed."
}

type Model struct {
    form *huh.Form // huh.Form is just a tea.Model
}

func NewModel() Model {
    return Model{
        form: huh.NewForm(
            huh.NewGroup(
                huh.NewInput().
                    Validate(func(in string) error {
                        if in == "" {
                            return fmt.Errorf("Class must be set.")
                        }

                        return nil
                    }).
                    Key("class").
                    Title("Choose your class"),

                huh.NewSelect[int]().
                    Key("level").
                    Options(huh.NewOptions(1, 20, 9999)...).
                    Title("Choose your level"),
            ),
        ),
    }
}

func (m Model) Init() tea.Cmd {
    return m.form.Init()
}

func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
    if msg, ok := msg.(tea.KeyMsg); ok {
        if msg.String() == "ctrl+c" {
            return m, tea.Quit
        }
    }

    form, cmd := m.form.Update(msg)
    if f, ok := form.(*huh.Form); ok {
        m.form = f
    }

    return m, cmd
}

func (m Model) View() string {
    if m.form.State == huh.StateCompleted {
        class := m.form.GetString("class")
        level := m.form.GetString("level")
        return fmt.Sprintf("You selected: %s, Lvl. %d", class, level)
    }
    return m.form.View()
}

Expected behavior

When the huh model is rendered the initial input field should have focus and you should be able to type into it.

Screenshots

What isn't visible is I'm trying to type into the Input field.

CleanShot 2024-09-20 at 12 17 25

Desktop:

go.mod

module github.com/keyneston/bubbletea-huh-test

go 1.22.4

require (
    github.com/atotto/clipboard v0.1.4 // indirect
    github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
    github.com/catppuccin/go v0.2.0 // indirect
    github.com/charmbracelet/bubbles v0.20.0 // indirect
    github.com/charmbracelet/bubbletea v1.1.1 // indirect
    github.com/charmbracelet/huh v0.6.0 // indirect
    github.com/charmbracelet/lipgloss v0.13.0 // indirect
    github.com/charmbracelet/x/ansi v0.2.3 // indirect
    github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0 // indirect
    github.com/charmbracelet/x/term v0.2.0 // indirect
    github.com/dustin/go-humanize v1.0.1 // indirect
    github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
    github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
    github.com/mattn/go-isatty v0.0.20 // indirect
    github.com/mattn/go-localereader v0.0.1 // indirect
    github.com/mattn/go-runewidth v0.0.16 // indirect
    github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
    github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
    github.com/muesli/cancelreader v0.2.2 // indirect
    github.com/muesli/termenv v0.15.3-0.20240618155329-98d742f6907a // indirect
    github.com/rivo/uniseg v0.4.7 // indirect
    golang.org/x/sync v0.8.0 // indirect
    golang.org/x/sys v0.25.0 // indirect
    golang.org/x/text v0.18.0 // indirect
)
keyneston commented 1 week ago

I was able to work around this by calling:

    m.form.NextField()
    m.form.PrevField()

After initialising my struct.

cloverLynn commented 1 week ago

Seconding This issue, thanks @keyneston for a workaround :black_heart: