Fix .otp Delete key moving the caret to the last slot (#3190)

Co-authored-by: codecalm <codecalm@gmail.com>
This commit is contained in:
Bartosz-Doandcodecalm authored and GitHub committed 2026-09-25 12:04:40 +02:00
1 parent 70b8372c61
commit 3d10e24af3
3 files changed
+53 -1

No files matched your search

+5
View File
@@ -0,0 +1,5 @@
---
"@tabler/core": patch
---
Fixed the Delete key in `.otp` moving the caret to the last slot; it now removes the character under the caret and keeps the caret in place, like Backspace.
+21 -1
View File
@@ -292,7 +292,7 @@ class OtpInput extends BaseComponent {
this._afterValueChange() this._afterValueChange()
} }
// Intercepts single-character typing and backspace so each slot is // Intercepts single-character typing, Backspace and Delete so each slot is
// overwritten in place rather than inserting and shifting the value. // overwritten in place rather than inserting and shifting the value.
// Anything else (paste, autofill, IME) falls through to `_handleInput`. // Anything else (paste, autofill, IME) falls through to `_handleInput`.
_handleBeforeInput(event: InputEvent): void { _handleBeforeInput(event: InputEvent): void {
@@ -334,6 +334,26 @@ class OtpInput extends BaseComponent {
} }
this._afterValueChange() this._afterValueChange()
return
}
if (inputType === 'deleteContentForward') {
event.preventDefault()
const start = this._input.selectionStart ?? 0
const end = this._input.selectionEnd ?? start
const chars = [...this._input.value]
if (end > start) {
chars.splice(start, end - start)
this._input.value = chars.join('')
} else if (start < chars.length) {
chars.splice(start, 1)
this._input.value = chars.join('')
}
this._selectSlot(start)
this._afterValueChange()
} }
} }
+27
View File
@@ -210,6 +210,33 @@ describe('OtpInput', () => {
expect(input().value).toBe('') expect(input().value).toBe('')
}) })
it('should clear a selected slot on delete without shifting the caret', () => {
const instance = new OtpInput(otp())
instance.setValue('123456')
input().setSelectionRange(3, 4)
beforeInput(input(), { inputType: 'deleteContentForward' })
expect(input().value).toBe('12356')
expect(input().selectionStart).toBe(3)
})
it('should delete the next character on delete with a collapsed caret', () => {
const instance = new OtpInput(otp())
instance.setValue('123')
input().setSelectionRange(1, 1)
beforeInput(input(), { inputType: 'deleteContentForward' })
expect(input().value).toBe('13')
expect(input().selectionStart).toBe(1)
})
it('should do nothing on delete at the end of the value', () => {
const instance = new OtpInput(otp())
instance.setValue('123')
input().setSelectionRange(3, 3)
const event = beforeInput(input(), { inputType: 'deleteContentForward' })
expect(event.defaultPrevented).toBe(true)
expect(input().value).toBe('123')
})
it('should select the first empty slot on keyboard focus', () => { it('should select the first empty slot on keyboard focus', () => {
const instance = new OtpInput(otp()) const instance = new OtpInput(otp())
instance.setValue('12') instance.setValue('12')