OtpInput: fix click-to-focus and overwrite-on-retype#42524
Open
mdo wants to merge 1 commit into
Open
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.No public API or event changes.
Testing
npm run js-test-karma— 1097/1097 pass, including 5 new interaction tests (click-to-position, overwrite-not-insert, backspace, Tab → first-empty, disallowed-char swallowed).eslint+stylelintclean.Built
dist/is intentionally not included (CI builds it, consistent with other PRs on this branch).Note
Unit tests cover the core logic, but real cross-browser/mobile behavior (iOS Safari focus with
pointer-events: none, Android GBoardbeforeinput, IME composition) is worth a quick manual pass on theforms/otp-inputdocs page before merge. The code guards for these (single-charinsertTextonly; composition falls through).