OTP: fix click-to-focus and overwrite-on-retype#42524
Conversation
The single-input rewrite left two interaction gaps: clicking a slot didn't position the caret there (slots had pointer-events: none and focus always jumped to the end), and retyping inserted instead of overwriting, so preceding digits shifted along. Keep the single accessible input but make its interaction faithful to the input-otp model: - Represent the active slot as a selection range so the next keystroke overwrites a filled slot or appends to an empty one - Intercept single-char typing and backspace via beforeinput for overwrite semantics; paste/autofill/IME still flow through input - Make slots clickable (pointerdown) to position the caret, clamped to the first empty slot - Land focus on the first empty slot instead of the end; track the caret with a document selectionchange listener
c1fa103 to
0586af7
Compare
|
PREVIEW: https://deploy-preview-42524--twbs-bootstrap.netlify.app/docs/6.0/forms/otp-input/ Serious bug introduced in this PR on iPadOS 26 and iPadOS 27 (Developer Beta). Tapping in the input initiates the keyboard but auto-dismisses within a split-second. Also tested this on Edge and Firefox (Windows 11) and all working well there. |
# Conflicts: # .bundlewatch.config.json
The slots overlaid the input with pointer-events: none and the JS called input.focus() programmatically after preventDefault()-ing the tap. On iPadOS that raises the on-screen keyboard then dismisses it instantly, because the keyboard must be raised by a genuine, un-prevented gesture on the input itself. Let the input receive taps (pointer-events: auto) so focus — and the keyboard — come from the native gesture. Map the tap's x-coordinate to a slot and set the caret in the focus handler once focus settles; when already focused, reposition immediately (preventDefault is safe then). Reported on iPadOS 26/27 by @coliff.
|
@coliff thanks for catching this — pushed a fix. The cause: the visual slots overlaid the real The fix lets the input receive the tap natively ( Could you re-test on iPadOS 26/27 (Developer Beta) once the preview rebuilds? I don't have a device to confirm the keyboard now stays up. Desktop click-to-slot, overwrite-on-retype, and the full JS suite all still pass. |
Summary
The single-input OTP rewrite (#42500) improved accessibility but left two interaction gaps reported after merge:
pointer-events: noneand focus always jumped to the end of the value, so the click position was ignored.<input>inserts, so editing mid-value pushed the remaining digits down. OTP entry expects overwrite.Both stem from the same thing: a single, full-width, centered input can't map a click to a slot, and native editing inserts rather than overwrites.
Approach
Keep the single accessible
<input>(preserving the one-announced-field,autocomplete="one-time-code", SMS autofill, password-manager, and formatted-paste wins) and make its interaction faithful to the input-otp model:[i, i+1]on a filled slot so the next keystroke overwrites it,[i, i]on an empty one so it appends.beforeinputhandler intercepts single-character typing (overwrite + advance) and backspace (clear / step back). Paste, SMS autofill, and IME composition still fall through to the existing bulkinputpath.pointerdownhandler focuses the input and positions the caret on the clicked slot, clamped to the first empty slot.focus()land on the first empty slot instead of the absolute end; a documentselectionchangelistener keeps the active-slot highlight in sync with the caret.