From 55f4fa27aa09493ee41feb8c3ee2616269e9c6ae Mon Sep 17 00:00:00 2001
From: sarankumar1325 <120080287+sarankumar1325@users.noreply.github.com>
Date: Sat, 24 Oct 2026 23:02:40 +0530
Subject: [PATCH] fix(input-otp): clear the previous box when backspace is
pressed in an empty box
---
core/src/components/input-otp/input-otp.tsx | 15 +++++-
.../input-otp/test/input-otp.spec.ts | 48 +++++++++++++++++++
2 files changed, 61 insertions(+), 2 deletions(-)
create mode 100644 core/src/components/input-otp/test/input-otp.spec.ts
diff --git a/core/src/components/input-otp/input-otp.tsx b/core/src/components/input-otp/input-otp.tsx
index 1c84b3c9a42..4c51e004f2c 100644
--- a/core/src/components/input-otp/input-otp.tsx
+++ b/core/src/components/input-otp/input-otp.tsx
@@ -548,7 +548,8 @@ export class InputOTP implements ComponentInterface {
* Handles keyboard navigation for the OTP component.
*
* Navigation:
- * - Backspace: Clears current input and moves to previous box if empty
+ * - Backspace: Clears current input, or clears the previous box and moves
+ * focus to it if the current input is empty
* - Arrow Left/Right: Moves focus between input boxes
* - Tab: Allows normal tab navigation between components
*/
@@ -597,8 +598,18 @@ export class InputOTP implements ComponentInterface {
this.updateValue(event);
event.preventDefault();
} else if (!this.inputValues[index] && index > 0) {
- // If current input is empty, move to previous input
+ // If current input is empty, clear the previous input and move
+ // focus to it. The default action is prevented because browsers do
+ // not agree on which box it applies to: desktop browsers delete from
+ // the newly focused box, but the iOS keyboard deletes from the box
+ // that had focus when the key was pressed, which is empty.
+ const previousIndex = index - 1;
+ this.inputValues[previousIndex] = '';
+ this.inputRefs[previousIndex].value = '';
+ this.previousInputValues = [...this.inputValues];
+ this.updateValue(event);
this.focusPrevious(index);
+ event.preventDefault();
}
} else if (event.key === 'ArrowLeft' || event.key === 'ArrowRight') {
this.isKeyboardNavigation = true;
diff --git a/core/src/components/input-otp/test/input-otp.spec.ts b/core/src/components/input-otp/test/input-otp.spec.ts
new file mode 100644
index 00000000000..d13e3715c24
--- /dev/null
+++ b/core/src/components/input-otp/test/input-otp.spec.ts
@@ -0,0 +1,48 @@
+import { newSpecPage } from '@stencil/core/testing';
+
+import { InputOTP } from '../input-otp';
+
+describe('input-otp: backspace', () => {
+ it('should clear the previous box when Backspace is pressed in an empty box', async () => {
+ const page = await newSpecPage({
+ components: [InputOTP],
+ html: `Description`,
+ });
+
+ const inputOtp = page.body.querySelector('ion-input-otp')!;
+ const inputs = inputOtp.querySelectorAll('input');
+ // The mock document fires focus events even for an element that already
+ // has focus, which makes onFocus recurse, so stub focus() instead.
+ inputs.forEach((input) => (input.focus = jest.fn()));
+ const ionInput = jest.fn();
+ inputOtp.addEventListener('ionInput', (ev) => ionInput((ev as CustomEvent).detail.value));
+
+ // Box 4 is empty. The test environment does not apply the browser's default
+ // Backspace action, like the iOS keyboard, so the component has to clear box 3 itself.
+ const event = new KeyboardEvent('keydown', { key: 'Backspace', bubbles: true, cancelable: true });
+ inputs[3].dispatchEvent(event);
+ await page.waitForChanges();
+
+ expect(inputs[2].value).toBe('');
+ expect(inputOtp.value).toBe('78');
+ expect(ionInput).toHaveBeenCalledWith('78');
+ expect(event.defaultPrevented).toBe(true);
+ expect(inputs[2].focus).toHaveBeenCalled();
+ });
+
+ it('should not change the value when Backspace is pressed in the first, empty box', async () => {
+ const page = await newSpecPage({
+ components: [InputOTP],
+ html: `Description`,
+ });
+
+ const inputOtp = page.body.querySelector('ion-input-otp')!;
+ const inputs = inputOtp.querySelectorAll('input');
+ inputs.forEach((input) => (input.focus = jest.fn()));
+
+ inputs[0].dispatchEvent(new KeyboardEvent('keydown', { key: 'Backspace', bubbles: true, cancelable: true }));
+ await page.waitForChanges();
+
+ expect(inputOtp.value).toBe('');
+ });
+});