mirror of
https://github.com/zoriya/bubbles.git
synced 2026-08-05 04:36:07 +00:00
feat(textinput): do not block input on validation (#185)
* feat(textinput): do not block input on validation This PR builds upon the excellent work in #167 and #114 and makes a breaking change to the validation API. Currently, validation will completely block text input if the Validate function returns an error. This is now changed so the function no longer blocks input if this is the case, thus handing this responsibility to the clients. This is helpful for cases where the user is requested to type an existing system path, and the Validate function keeps asserting the existence of the path. With the current implementation such a validation is not possible. For example: > / Err: nil > /t Err: /t: No such file or directory > /tm Err: /tm: No such file or directory > /tmp Err: nil * fix: change name --------- Co-authored-by: Maas Lalani <maas@lalani.dev>
This commit is contained in:
co-authored by
Maas Lalani
parent
2b22d5293f
commit
9030d22eec
+19
-19
@@ -181,19 +181,14 @@ func (m *Model) SetValue(s string) {
|
||||
// Clean up any special characters in the input provided by the
|
||||
// caller. This avoids bugs due to e.g. tab characters and whatnot.
|
||||
runes := m.san().Sanitize([]rune(s))
|
||||
m.setValueInternal(runes)
|
||||
err := m.validate(runes)
|
||||
m.setValueInternal(runes, err)
|
||||
}
|
||||
|
||||
func (m *Model) setValueInternal(runes []rune) {
|
||||
if m.Validate != nil {
|
||||
if err := m.Validate(string(runes)); err != nil {
|
||||
m.Err = err
|
||||
return
|
||||
}
|
||||
}
|
||||
func (m *Model) setValueInternal(runes []rune, err error) {
|
||||
m.Err = err
|
||||
|
||||
empty := len(m.value) == 0
|
||||
m.Err = nil
|
||||
|
||||
if m.CharLimit > 0 && len(runes) > m.CharLimit {
|
||||
m.value = runes[:m.CharLimit]
|
||||
@@ -307,8 +302,6 @@ func (m *Model) insertRunesFromUserInput(v []rune) {
|
||||
tail := make([]rune, len(tailSrc))
|
||||
copy(tail, tailSrc)
|
||||
|
||||
oldPos := m.pos
|
||||
|
||||
// Insert pasted runes
|
||||
for _, r := range paste {
|
||||
head = append(head, r)
|
||||
@@ -323,11 +316,8 @@ func (m *Model) insertRunesFromUserInput(v []rune) {
|
||||
|
||||
// Put it all back together
|
||||
value := append(head, tail...)
|
||||
m.setValueInternal(value)
|
||||
|
||||
if m.Err != nil {
|
||||
m.pos = oldPos
|
||||
}
|
||||
inputErr := m.validate(value)
|
||||
m.setValueInternal(value, inputErr)
|
||||
}
|
||||
|
||||
// If a max width is defined, perform some logic to treat the visible area
|
||||
@@ -378,6 +368,7 @@ func (m *Model) handleOverflow() {
|
||||
// deleteBeforeCursor deletes all text before the cursor.
|
||||
func (m *Model) deleteBeforeCursor() {
|
||||
m.value = m.value[m.pos:]
|
||||
m.Err = m.validate(m.value)
|
||||
m.offset = 0
|
||||
m.SetCursor(0)
|
||||
}
|
||||
@@ -387,6 +378,7 @@ func (m *Model) deleteBeforeCursor() {
|
||||
// masked input.
|
||||
func (m *Model) deleteAfterCursor() {
|
||||
m.value = m.value[:m.pos]
|
||||
m.Err = m.validate(m.value)
|
||||
m.SetCursor(len(m.value))
|
||||
}
|
||||
|
||||
@@ -432,6 +424,7 @@ func (m *Model) deleteWordBackward() {
|
||||
} else {
|
||||
m.value = append(m.value[:m.pos], m.value[oldPos:]...)
|
||||
}
|
||||
m.Err = m.validate(m.value)
|
||||
}
|
||||
|
||||
// deleteWordForward deletes the word right to the cursor. If input is masked
|
||||
@@ -471,6 +464,7 @@ func (m *Model) deleteWordForward() {
|
||||
} else {
|
||||
m.value = append(m.value[:oldPos], m.value[m.pos:]...)
|
||||
}
|
||||
m.Err = m.validate(m.value)
|
||||
|
||||
m.SetCursor(oldPos)
|
||||
}
|
||||
@@ -575,12 +569,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
case tea.KeyMsg:
|
||||
switch {
|
||||
case key.Matches(msg, m.KeyMap.DeleteWordBackward):
|
||||
m.Err = nil
|
||||
m.deleteWordBackward()
|
||||
case key.Matches(msg, m.KeyMap.DeleteCharacterBackward):
|
||||
m.Err = nil
|
||||
if len(m.value) > 0 {
|
||||
m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...)
|
||||
m.Err = m.validate(m.value)
|
||||
if m.pos > 0 {
|
||||
m.SetCursor(m.pos - 1)
|
||||
}
|
||||
@@ -597,13 +591,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
|
||||
if m.pos < len(m.value) {
|
||||
m.SetCursor(m.pos + 1)
|
||||
}
|
||||
case key.Matches(msg, m.KeyMap.DeleteWordBackward):
|
||||
m.deleteWordBackward()
|
||||
case key.Matches(msg, m.KeyMap.LineStart):
|
||||
m.CursorStart()
|
||||
case key.Matches(msg, m.KeyMap.DeleteCharacterForward):
|
||||
if len(m.value) > 0 && m.pos < len(m.value) {
|
||||
m.value = append(m.value[:m.pos], m.value[m.pos+1:]...)
|
||||
m.Err = m.validate(m.value)
|
||||
}
|
||||
case key.Matches(msg, m.KeyMap.LineEnd):
|
||||
m.CursorEnd()
|
||||
@@ -884,3 +877,10 @@ func (m *Model) previousSuggestion() {
|
||||
m.currentSuggestionIndex = len(m.matchedSuggestions) - 1
|
||||
}
|
||||
}
|
||||
|
||||
func (m Model) validate(v []rune) error {
|
||||
if m.Validate != nil {
|
||||
return m.Validate(string(v))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user