diff --git a/textinput/textinput.go b/textinput/textinput.go index 9692ed7c..98af83d4 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -81,6 +81,15 @@ const ( CursorHide ) +// ValidateAction describes the behavior on input validation. +type ValidateAction int + +// Available validate actions. +const ( + BlockInput ValidateAction = iota + AllowInput +) + // String returns a the cursor mode in a human-readable format. This method is // provisional and for informational purposes only. func (c CursorMode) String() string { @@ -159,6 +168,10 @@ type Model struct { // error returned by the function. If the function is not defined, all // input is considered valid. Validate ValidateFunc + + // ValidateAction determines what action to take if the last input was + // invalid. + ValidateAction ValidateAction } // New creates a new model with default settings. @@ -191,14 +204,12 @@ var NewModel = New // SetValue sets the value of the text input. func (m *Model) SetValue(s string) { if m.Validate != nil { - if err := m.Validate(s); err != nil { - m.Err = err + m.Err = m.Validate(s) + if m.Err != nil && m.ValidateAction == BlockInput { return } } - m.Err = nil - runes := []rune(s) if m.CharLimit > 0 && len(runes) > m.CharLimit { m.value = runes[:m.CharLimit] @@ -362,7 +373,9 @@ func (m *Model) handlePaste(v string) bool { value := append(head, tail...) m.SetValue(string(value)) - if m.Err != nil { + // If validation failed and we're blocking input, revert to the previous + // cursor position + if m.Validate != nil && m.ValidateAction == BlockInput && m.Err != nil { m.pos = oldPos } @@ -612,8 +625,6 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { case tea.KeyMsg: switch msg.Type { case tea.KeyBackspace, tea.KeyCtrlH: // delete character before cursor - m.Err = nil - if msg.Alt { resetBlink = m.deleteWordLeft() } else { @@ -624,6 +635,13 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { } } } + + if m.Validate != nil { + m.Err = nil + if m.ValidateAction == AllowInput { + m.Err = m.Validate(string(m.value)) + } + } case tea.KeyLeft, tea.KeyCtrlB: if msg.Alt { // alt+left arrow, back one word resetBlink = m.wordLeft() @@ -680,9 +698,13 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { copy(value, m.value) value = append(value[:m.pos], append(runes, value[m.pos:]...)...) m.SetValue(string(value)) - if m.Err == nil { - resetBlink = m.setCursor(m.pos + len(runes)) + + // If validation failed and we don't allow input, avoid + // resetting the cursor blink. + if m.Validate != nil && m.ValidateAction == BlockInput && m.Err != nil { + break } + resetBlink = m.setCursor(m.pos + len(runes)) } }