mirror of
https://github.com/zoriya/bubbles.git
synced 2026-08-14 17:03:25 +00:00
fix(textinput): improve placeholder (#768)
similar to #767 but for textinputs
This commit is contained in:
+11
-19
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/charmbracelet/bubbles/runeutil"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"github.com/charmbracelet/x/ansi"
|
||||
rw "github.com/mattn/go-runewidth"
|
||||
"github.com/rivo/uniseg"
|
||||
)
|
||||
@@ -703,40 +704,31 @@ func (m Model) placeholderView() string {
|
||||
var (
|
||||
v string
|
||||
style = m.PlaceholderStyle.Inline(true).Render
|
||||
p = m.PromptStyle.Render(m.Prompt)
|
||||
)
|
||||
|
||||
p := make([]rune, m.Width+1)
|
||||
copy(p, []rune(m.Placeholder))
|
||||
|
||||
m.Cursor.TextStyle = m.PlaceholderStyle
|
||||
m.Cursor.SetChar(string(p[:1]))
|
||||
first, rest, _, _ := uniseg.FirstGraphemeClusterInString(m.Placeholder, 0)
|
||||
m.Cursor.SetChar(first)
|
||||
v += m.Cursor.View()
|
||||
|
||||
// If the entire placeholder is already set and no padding is needed, finish
|
||||
if m.Width < 1 && len(p) <= 1 {
|
||||
if m.Width < 1 && uniseg.StringWidth(rest) <= 1 {
|
||||
return m.PromptStyle.Render(m.Prompt) + v
|
||||
}
|
||||
|
||||
// If Width is set then size placeholder accordingly
|
||||
if m.Width > 0 {
|
||||
// available width is width - len + cursor offset of 1
|
||||
minWidth := lipgloss.Width(m.Placeholder)
|
||||
availWidth := m.Width - minWidth + 1
|
||||
|
||||
// if width < len, 'subtract'(add) number to len and dont add padding
|
||||
if availWidth < 0 {
|
||||
minWidth += availWidth
|
||||
availWidth = 0
|
||||
}
|
||||
// append placeholder[len] - cursor, append padding
|
||||
v += style(string(p[1:minWidth]))
|
||||
v += style(strings.Repeat(" ", availWidth))
|
||||
width := m.Width - lipgloss.Width(p) - lipgloss.Width(v)
|
||||
placeholderRest := ansi.Truncate(rest, width, "…")
|
||||
availWidth := max(0, width-lipgloss.Width(placeholderRest))
|
||||
v += style(placeholderRest) + strings.Repeat(" ", availWidth)
|
||||
} else {
|
||||
// if there is no width, the placeholder can be any length
|
||||
v += style(string(p[1:]))
|
||||
v += style(rest)
|
||||
}
|
||||
|
||||
return m.PromptStyle.Render(m.Prompt) + v
|
||||
return p + v
|
||||
}
|
||||
|
||||
// Blink is a command used to initialize cursor blinking.
|
||||
|
||||
@@ -5,6 +5,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
)
|
||||
|
||||
func Test_CurrentSuggestion(t *testing.T) {
|
||||
@@ -46,6 +48,30 @@ func Test_SlicingOutsideCap(t *testing.T) {
|
||||
textinput.View()
|
||||
}
|
||||
|
||||
func TestChinesePlaceholder(t *testing.T) {
|
||||
textinput := New()
|
||||
textinput.Placeholder = "输入消息..."
|
||||
textinput.Width = 20
|
||||
|
||||
got := textinput.View()
|
||||
expected := "> 输入消息... "
|
||||
if got != expected {
|
||||
t.Fatalf("expected %q but got %q", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPlaceholderTruncate(t *testing.T) {
|
||||
textinput := New()
|
||||
textinput.Placeholder = "A very long placeholder, or maybe not so much"
|
||||
textinput.Width = 10
|
||||
|
||||
got := textinput.View()
|
||||
expected := "> A very …"
|
||||
if got != expected {
|
||||
t.Fatalf("expected %q but got %q", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleValidateFunc() {
|
||||
creditCardNumber := New()
|
||||
creditCardNumber.Placeholder = "4505 **** **** 1234"
|
||||
@@ -78,3 +104,15 @@ func ExampleValidateFunc() {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func keyPress(key rune) tea.Msg {
|
||||
return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{key}, Alt: false}
|
||||
}
|
||||
|
||||
func sendString(m Model, str string) Model {
|
||||
for _, k := range str {
|
||||
m, _ = m.Update(keyPress(k))
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user