Skip to content

Commit 36d954d

Browse files
[OneTimePasswordField] Fix iOS Chrome autocomplete (#3654)
Fix iOS Chrome autocomplete - iOS chrome autocomplete only fires an `input` event up to the `maxLength`, so we need the input that “supports autocomplete” to accept the full OTP length. Later, in the `input` handler, the PASTE action will be dispatched. - iOS Chrome fires onChange for the first input after firing `onInput` with the full code - If unchecked, the onChange handler will result in the _second_ input being pressed, after the PASTE action handler already focused the _final_ input
1 parent 08ff067 commit 36d954d

File tree

2 files changed

+24
-8
lines changed

2 files changed

+24
-8
lines changed

.changeset/shaky-carrots-invite.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@radix-ui/react-one-time-password-field': patch
3+
---
4+
5+
Fix iOS Chrome autocomplete (#3641)

packages/react/one-time-password-field/src/one-time-password-field.tsx

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,9 @@ const OneTimePasswordFieldInput = React.forwardRef<
612612
const keyboardActionTimeoutRef = React.useRef<number | null>(null);
613613
React.useEffect(() => {
614614
return () => {
615-
window.clearTimeout(keyboardActionTimeoutRef.current!);
615+
if (keyboardActionTimeoutRef.current) {
616+
window.clearTimeout(keyboardActionTimeoutRef.current);
617+
}
616618
};
617619
}, []);
618620

@@ -647,7 +649,7 @@ const OneTimePasswordFieldInput = React.forwardRef<
647649
data-protonpass-ignore={supportsAutoComplete ? undefined : 'true'}
648650
data-bwignore={supportsAutoComplete ? undefined : 'true'}
649651
inputMode={validation?.inputMode}
650-
maxLength={1}
652+
maxLength={supportsAutoComplete ? collection.size : 1}
651653
pattern={validation?.pattern}
652654
readOnly={context.readOnly}
653655
value={char}
@@ -664,11 +666,9 @@ const OneTimePasswordFieldInput = React.forwardRef<
664666
// In this case the value will be cleared, but we don't want to
665667
// set it directly because the user may want to prevent default
666668
// behavior in the onChange handler. The userActionRef will
667-
// is set temporarily so the change handler can behave correctly
669+
// be set temporarily so the change handler can behave correctly
668670
// in response to the action.
669-
userActionRef.current = {
670-
type: 'cut',
671-
};
671+
userActionRef.current = { type: 'cut' };
672672
// Set a short timeout to clear the action tracker after the change
673673
// handler has had time to complete.
674674
keyboardActionTimeoutRef.current = window.setTimeout(() => {
@@ -684,7 +684,11 @@ const OneTimePasswordFieldInput = React.forwardRef<
684684
// additional input. Handle this the same as if a user were
685685
// pasting a value.
686686
event.preventDefault();
687+
userActionRef.current = { type: 'autocomplete-paste' };
687688
dispatch({ type: 'PASTE', value });
689+
keyboardActionTimeoutRef.current = window.setTimeout(() => {
690+
userActionRef.current = null;
691+
}, 10);
688692
}
689693
})}
690694
onChange={composeEventHandlers(props.onChange, (event) => {
@@ -696,11 +700,16 @@ const OneTimePasswordFieldInput = React.forwardRef<
696700
if (action) {
697701
switch (action.type) {
698702
case 'cut':
699-
// TODO: do we want to assume the user wantt to clear the
703+
// TODO: do we want to assume the user wants to clear the
700704
// entire value here and copy the code to the clipboard instead
701705
// of just the value of the given input?
702706
dispatch({ type: 'CLEAR_CHAR', index, reason: 'Cut' });
703707
return;
708+
case 'autocomplete-paste':
709+
// the PASTE handler will already set the value and focus the final
710+
// input; we want to skip focusing the wrong element if the browser fires
711+
// onChange for the first input. This sometimes happens during autocomplete.
712+
return;
704713
case 'keydown': {
705714
if (action.key === 'Char') {
706715
// update resulting from a keydown event that set a value
@@ -718,6 +727,7 @@ const OneTimePasswordFieldInput = React.forwardRef<
718727
return;
719728
}
720729
default:
730+
action satisfies never;
721731
return;
722732
}
723733
}
@@ -929,7 +939,8 @@ type KeyboardActionDetails =
929939
metaKey: boolean;
930940
ctrlKey: boolean;
931941
}
932-
| { type: 'cut' };
942+
| { type: 'cut' }
943+
| { type: 'autocomplete-paste' };
933944

934945
type UpdateAction =
935946
| {

0 commit comments

Comments
 (0)