fix: render full placeholder text in form inputs
bubbles/v2 textinput truncates the placeholder to its first rune when the input width is 0 (the default): it sizes the placeholder buffer to Width()+1 and early-returns after the first character. Set a non-zero width on every text input (defaultInputWidth) and resize inputs responsively to the terminal on WindowSizeMsg via FormStep.SetWidth, so placeholders render in full and inputs fill the available width. Adds a regression test asserting the placeholder renders past its first rune. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,11 @@ const (
|
|||||||
FieldTypeNumber
|
FieldTypeNumber
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// defaultInputWidth is the visible width of text inputs before the first window-size
|
||||||
|
// message arrives. It must be > 0 (and ideally >= the longest placeholder) so the
|
||||||
|
// placeholder renders in full; it is overridden responsively via FormStep.SetWidth.
|
||||||
|
const defaultInputWidth = 50
|
||||||
|
|
||||||
type FormField struct {
|
type FormField struct {
|
||||||
Id string
|
Id string
|
||||||
Label string
|
Label string
|
||||||
@@ -51,6 +56,9 @@ func NewFormStep(title string, fields []FormField) FormStep {
|
|||||||
|
|
||||||
ti := textinput.New()
|
ti := textinput.New()
|
||||||
ti.Placeholder = f.Fields[i].Placeholder
|
ti.Placeholder = f.Fields[i].Placeholder
|
||||||
|
// A non-zero width is required for the placeholder to render in full: with the
|
||||||
|
// default width of 0 the textinput truncates the placeholder to its first rune.
|
||||||
|
ti.SetWidth(defaultInputWidth)
|
||||||
if f.Fields[i].Default != "" {
|
if f.Fields[i].Default != "" {
|
||||||
ti.SetValue(f.Fields[i].Default)
|
ti.SetValue(f.Fields[i].Default)
|
||||||
}
|
}
|
||||||
@@ -129,6 +137,17 @@ func (f *FormStep) Values() map[string]string {
|
|||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetWidth resizes every text input to w (selects are unaffected). Called on window
|
||||||
|
// resize so inputs fill the available terminal width and placeholders render in full.
|
||||||
|
func (f *FormStep) SetWidth(w int) {
|
||||||
|
for i := range f.Fields {
|
||||||
|
if f.Fields[i].Type == FieldTypeSelect {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f.Fields[i].input.SetWidth(w)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// View
|
// View
|
||||||
func (f FormStep) View() string {
|
func (f FormStep) View() string {
|
||||||
pad := strings.Repeat(" ", padding)
|
pad := strings.Repeat(" ", padding)
|
||||||
|
|||||||
@@ -1,6 +1,23 @@
|
|||||||
package tui
|
package tui
|
||||||
|
|
||||||
import "testing"
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Guards against the bubbles bug where a text input with width 0 renders only the
|
||||||
|
// first rune of its placeholder. NewFormStep must set a non-zero width.
|
||||||
|
func TestPlaceholderRendersBeyondFirstRune(t *testing.T) {
|
||||||
|
const placeholder = "postgres://user@host/db"
|
||||||
|
f := NewFormStep("t", []FormField{
|
||||||
|
{Id: "url", Label: "URL", Placeholder: placeholder, Type: FieldTypeText},
|
||||||
|
})
|
||||||
|
|
||||||
|
view := f.Fields[0].input.View()
|
||||||
|
if !strings.Contains(view, "ostgres://user@host/db") {
|
||||||
|
t.Errorf("placeholder appears truncated; rendered view = %q", view)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFormStepValues(t *testing.T) {
|
func TestFormStepValues(t *testing.T) {
|
||||||
f := NewFormStep("Test", []FormField{
|
f := NewFormStep("Test", []FormField{
|
||||||
|
|||||||
@@ -33,6 +33,19 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
case tea.WindowSizeMsg:
|
case tea.WindowSizeMsg:
|
||||||
m.width = msg.Width
|
m.width = msg.Width
|
||||||
m.height = msg.Height
|
m.height = msg.Height
|
||||||
|
|
||||||
|
// Fit inputs to the terminal: subtract the left padding, the "> " prompt and
|
||||||
|
// the cursor cell, with a sensible floor for very narrow terminals.
|
||||||
|
inputWidth := msg.Width - (padding * 2) - 4
|
||||||
|
if inputWidth < 20 {
|
||||||
|
inputWidth = 20
|
||||||
|
}
|
||||||
|
for _, f := range []*FormStep{
|
||||||
|
&m.loginForm, &m.wireguardForm, &m.appForm,
|
||||||
|
&m.serverForm, &m.dbForm, &m.certForm,
|
||||||
|
} {
|
||||||
|
f.SetWidth(inputWidth)
|
||||||
|
}
|
||||||
case spinner.TickMsg:
|
case spinner.TickMsg:
|
||||||
var cmd tea.Cmd
|
var cmd tea.Cmd
|
||||||
m.spinner, cmd = m.spinner.Update(msg)
|
m.spinner, cmd = m.spinner.Update(msg)
|
||||||
|
|||||||
Reference in New Issue
Block a user