From 55b7aa977eb4eb52d04a4fc0cfd908dd6bba2176 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aur=C3=A9lien?=
<2128970+GoldAndLink@users.noreply.github.com>
Date: Thu, 30 Nov 2023 07:21:05 +0100
Subject: [PATCH 1/4] feat: add focus + autofocus
---
dist/src/OtpInput/OtpInput.js | 4 ++--
dist/src/OtpInput/OtpInput.types.d.ts | 2 ++
dist/src/OtpInput/useOtpInput.js | 5 ++++-
src/OtpInput/OtpInput.tsx | 7 ++++---
src/OtpInput/useOtpInput.ts | 6 +++++-
src/index.d.ts | 10 ++++++++++
6 files changed, 27 insertions(+), 7 deletions(-)
diff --git a/dist/src/OtpInput/OtpInput.js b/dist/src/OtpInput/OtpInput.js
index 37fdbc1..2272b11 100644
--- a/dist/src/OtpInput/OtpInput.js
+++ b/dist/src/OtpInput/OtpInput.js
@@ -8,7 +8,7 @@ const VerticalStick_1 = require("./VerticalStick");
const useOtpInput_1 = require("./useOtpInput");
exports.OtpInput = (0, react_1.forwardRef)((props, ref) => {
const { models: { text, inputRef, focusedInputIndex }, actions: { clear, handlePress, handleTextChange }, forms: { setTextWithRef }, } = (0, useOtpInput_1.useOtpInput)(props);
- const { numberOfDigits, hideStick, focusColor = "#A4D0A4", focusStickBlinkingDuration, secureTextEntry = false, theme = {}, } = props;
+ const { autoFocus, numberOfDigits, hideStick, focusColor = "#A4D0A4", focusStickBlinkingDuration, secureTextEntry = false, theme = {}, } = props;
const { containerStyle, inputsContainerStyle, pinCodeContainerStyle, pinCodeTextStyle, focusStickStyle, focusedPinCodeContainerStyle, } = theme;
(0, react_1.useImperativeHandle)(ref, () => ({ clear, setValue: setTextWithRef }));
return (
@@ -32,6 +32,6 @@ exports.OtpInput = (0, react_1.forwardRef)((props, ref) => {
);
})}
-
+
);
});
diff --git a/dist/src/OtpInput/OtpInput.types.d.ts b/dist/src/OtpInput/OtpInput.types.d.ts
index 9ffe92c..b642c73 100644
--- a/dist/src/OtpInput/OtpInput.types.d.ts
+++ b/dist/src/OtpInput/OtpInput.types.d.ts
@@ -1,5 +1,6 @@
import { ColorValue, TextStyle, ViewStyle } from "react-native";
export interface OtpInputProps {
+ autoFocus?: boolean;
numberOfDigits: number;
focusColor?: ColorValue;
onTextChange?: (text: string) => void;
@@ -11,6 +12,7 @@ export interface OtpInputProps {
}
export interface OtpInputRef {
clear: () => void;
+ focus: () => void;
setValue: (value: string) => void;
}
export interface Theme {
diff --git a/dist/src/OtpInput/useOtpInput.js b/dist/src/OtpInput/useOtpInput.js
index 233162b..8eb9af8 100644
--- a/dist/src/OtpInput/useOtpInput.js
+++ b/dist/src/OtpInput/useOtpInput.js
@@ -28,9 +28,12 @@ const useOtpInput = ({ onTextChange, onFilled, numberOfDigits }) => {
const clear = () => {
setText("");
};
+ const focus = () => {
+ inputRef.current?.focus();
+ };
return {
models: { text, inputRef, focusedInputIndex },
- actions: { handlePress, handleTextChange, clear },
+ actions: { handlePress, handleTextChange, clear, focus },
forms: { setText, setTextWithRef },
};
};
diff --git a/src/OtpInput/OtpInput.tsx b/src/OtpInput/OtpInput.tsx
index f073f55..e10817e 100644
--- a/src/OtpInput/OtpInput.tsx
+++ b/src/OtpInput/OtpInput.tsx
@@ -8,11 +8,12 @@ import { useOtpInput } from "./useOtpInput";
export const OtpInput = forwardRef((props, ref) => {
const {
models: { text, inputRef, focusedInputIndex },
- actions: { clear, handlePress, handleTextChange },
+ actions: { clear, handlePress, handleTextChange, focus },
forms: { setTextWithRef },
} = useOtpInput(props);
const {
numberOfDigits,
+ autoFocus,
hideStick,
focusColor = "#A4D0A4",
focusStickBlinkingDuration,
@@ -28,7 +29,7 @@ export const OtpInput = forwardRef((props, ref) => {
focusedPinCodeContainerStyle,
} = theme;
- useImperativeHandle(ref, () => ({ clear, setValue: setTextWithRef }));
+ useImperativeHandle(ref, () => ({ clear, focus, setValue: setTextWithRef }));
return (
@@ -74,7 +75,7 @@ export const OtpInput = forwardRef((props, ref) => {
maxLength={numberOfDigits}
inputMode="numeric"
ref={inputRef}
- autoFocus
+ autoFocus={autoFocus}
style={styles.hiddenInput}
secureTextEntry={secureTextEntry}
testID="otp-input-hidden"
diff --git a/src/OtpInput/useOtpInput.ts b/src/OtpInput/useOtpInput.ts
index 7679657..1e35405 100644
--- a/src/OtpInput/useOtpInput.ts
+++ b/src/OtpInput/useOtpInput.ts
@@ -32,9 +32,13 @@ export const useOtpInput = ({ onTextChange, onFilled, numberOfDigits }: OtpInput
setText("");
};
+ const focus = () => {
+ inputRef.current?.focus();
+ };
+
return {
models: { text, inputRef, focusedInputIndex },
- actions: { handlePress, handleTextChange, clear },
+ actions: { handlePress, handleTextChange, clear, focus },
forms: { setText, setTextWithRef },
};
};
diff --git a/src/index.d.ts b/src/index.d.ts
index 3f3c23a..31053b3 100644
--- a/src/index.d.ts
+++ b/src/index.d.ts
@@ -2,6 +2,11 @@ declare module "OTPInput" {
import { ColorValue, TextStyle, ViewStyle } from "react-native";
export interface OtpEntryProps {
+ /**
+ * Autofocus.
+ */
+ autoFocus: boolean;
+
/**
* The number of digits to be displayed in the OTP entry.
*/
@@ -35,6 +40,11 @@ declare module "OTPInput" {
*/
clear: () => void;
+ /**
+ * Focus of the OTP input.
+ */
+ focus: () => void;
+
/**
* Sets the value of the OTP input.
* @param value - The value to be set.
From b2d14dee3ae8aacd8087352086b2f4cde413af19 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aur=C3=A9lien?=
<2128970+GoldAndLink@users.noreply.github.com>
Date: Thu, 30 Nov 2023 07:21:16 +0100
Subject: [PATCH 2/4] docs: update doc
---
README.MD | 2 ++
1 file changed, 2 insertions(+)
diff --git a/README.MD b/README.MD
index 97cdeb5..70052cc 100644
--- a/README.MD
+++ b/README.MD
@@ -71,6 +71,7 @@ The `react-native-otp-entry` component accepts the following props:
| Prop | Type | Description |
| ---------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------- |
| `numberOfDigits` | number | The number of digits to be displayed in the OTP entry. |
+| `autoFocus` | boolean | Set autofocus. |
| `focusColor` | ColorValue | The color of the input field border and stick when it is focused. |
| `onTextChange` | (text: string) => void | A callback function is invoked when the OTP text changes. It receives the updated text as an argument. |
| `onFilled` | (text: string) => void | A callback function is invoked when the OTP input is fully filled. It receives a full otp code as an argument. |
@@ -96,6 +97,7 @@ The `react-native-otp-entry` component exposes these functions with `ref`:
| Prop | Type | Description |
| ---------- | ------------------------ | ---------------------------------- |
| `clear` | () => void; | Clears the value of the OTP input. |
+| `focus` | () => void; | Focus of the OTP input. |
| `setValue` | (value: string) => void; | Sets the value of the OTP input. |
## License
From 9b2bd73f62b34ed0a4e54efba9f0ee4e14681224 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Aur=C3=A9lien?=
<2128970+GoldAndLink@users.noreply.github.com>
Date: Thu, 30 Nov 2023 23:10:53 +0100
Subject: [PATCH 3/4] fix: Property 'autoFocus' does not exist on type
'OtpInputProps'
---
src/OtpInput/OtpInput.types.ts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/OtpInput/OtpInput.types.ts b/src/OtpInput/OtpInput.types.ts
index 5714565..5c95cc5 100644
--- a/src/OtpInput/OtpInput.types.ts
+++ b/src/OtpInput/OtpInput.types.ts
@@ -2,6 +2,7 @@ import { ColorValue, TextStyle, ViewStyle } from "react-native";
export interface OtpInputProps {
numberOfDigits: number;
+ autoFocus?: boolean;
focusColor?: ColorValue;
onTextChange?: (text: string) => void;
onFilled?: (text: string) => void;
@@ -13,6 +14,7 @@ export interface OtpInputProps {
export interface OtpInputRef {
clear: () => void;
+ focus: () => void;
setValue: (value: string) => void;
}
From 195831f2b858ae8d9e2a60edcc0df6b7603f531b Mon Sep 17 00:00:00 2001
From: anday013
Date: Fri, 15 Dec 2023 21:38:05 +0100
Subject: [PATCH 4/4] test: add tests for focus and autofocus
---
dist/src/OtpInput/OtpInput.js | 6 +++---
dist/src/OtpInput/OtpInput.test.js | 10 ++++++++++
dist/src/OtpInput/OtpInput.types.d.ts | 2 +-
dist/src/OtpInput/useOtpInput.d.ts | 1 +
dist/src/OtpInput/useOtpInput.test.js | 8 ++++++++
package-lock.json | 4 ++--
src/OtpInput/OtpInput.test.tsx | 16 ++++++++++++++++
src/OtpInput/OtpInput.tsx | 2 +-
src/OtpInput/useOtpInput.test.ts | 11 +++++++++++
9 files changed, 53 insertions(+), 7 deletions(-)
diff --git a/dist/src/OtpInput/OtpInput.js b/dist/src/OtpInput/OtpInput.js
index 2272b11..d8bc5fd 100644
--- a/dist/src/OtpInput/OtpInput.js
+++ b/dist/src/OtpInput/OtpInput.js
@@ -7,10 +7,10 @@ const OtpInput_styles_1 = require("./OtpInput.styles");
const VerticalStick_1 = require("./VerticalStick");
const useOtpInput_1 = require("./useOtpInput");
exports.OtpInput = (0, react_1.forwardRef)((props, ref) => {
- const { models: { text, inputRef, focusedInputIndex }, actions: { clear, handlePress, handleTextChange }, forms: { setTextWithRef }, } = (0, useOtpInput_1.useOtpInput)(props);
- const { autoFocus, numberOfDigits, hideStick, focusColor = "#A4D0A4", focusStickBlinkingDuration, secureTextEntry = false, theme = {}, } = props;
+ const { models: { text, inputRef, focusedInputIndex }, actions: { clear, handlePress, handleTextChange, focus }, forms: { setTextWithRef }, } = (0, useOtpInput_1.useOtpInput)(props);
+ const { numberOfDigits, autoFocus = true, hideStick, focusColor = "#A4D0A4", focusStickBlinkingDuration, secureTextEntry = false, theme = {}, } = props;
const { containerStyle, inputsContainerStyle, pinCodeContainerStyle, pinCodeTextStyle, focusStickStyle, focusedPinCodeContainerStyle, } = theme;
- (0, react_1.useImperativeHandle)(ref, () => ({ clear, setValue: setTextWithRef }));
+ (0, react_1.useImperativeHandle)(ref, () => ({ clear, focus, setValue: setTextWithRef }));
return (
{Array(numberOfDigits)
diff --git a/dist/src/OtpInput/OtpInput.test.js b/dist/src/OtpInput/OtpInput.test.js
index 70024a9..78e7bd5 100644
--- a/dist/src/OtpInput/OtpInput.test.js
+++ b/dist/src/OtpInput/OtpInput.test.js
@@ -24,6 +24,16 @@ describe("OtpInput", () => {
expect(input).toHaveTextContent("•");
});
});
+ test("should autoFocused by default", () => {
+ renderOtpInput();
+ const input = react_native_1.screen.getByTestId("otp-input-hidden");
+ expect(input.props.autoFocus).toBe(true);
+ });
+ test('should not focus if "autoFocus" is false', () => {
+ renderOtpInput({ autoFocus: false });
+ const input = react_native_1.screen.getByTestId("otp-input-hidden");
+ expect(input.props.autoFocus).toBe(false);
+ });
test("focusColor should not be overridden by theme", () => {
renderOtpInput({
focusColor: "#000",
diff --git a/dist/src/OtpInput/OtpInput.types.d.ts b/dist/src/OtpInput/OtpInput.types.d.ts
index b642c73..36641ab 100644
--- a/dist/src/OtpInput/OtpInput.types.d.ts
+++ b/dist/src/OtpInput/OtpInput.types.d.ts
@@ -1,7 +1,7 @@
import { ColorValue, TextStyle, ViewStyle } from "react-native";
export interface OtpInputProps {
- autoFocus?: boolean;
numberOfDigits: number;
+ autoFocus?: boolean;
focusColor?: ColorValue;
onTextChange?: (text: string) => void;
onFilled?: (text: string) => void;
diff --git a/dist/src/OtpInput/useOtpInput.d.ts b/dist/src/OtpInput/useOtpInput.d.ts
index f505a8d..24001b4 100644
--- a/dist/src/OtpInput/useOtpInput.d.ts
+++ b/dist/src/OtpInput/useOtpInput.d.ts
@@ -11,6 +11,7 @@ export declare const useOtpInput: ({ onTextChange, onFilled, numberOfDigits }: O
handlePress: () => void;
handleTextChange: (value: string) => void;
clear: () => void;
+ focus: () => void;
};
forms: {
setText: import("react").Dispatch>;
diff --git a/dist/src/OtpInput/useOtpInput.test.js b/dist/src/OtpInput/useOtpInput.test.js
index f6446df..0f7f026 100644
--- a/dist/src/OtpInput/useOtpInput.test.js
+++ b/dist/src/OtpInput/useOtpInput.test.js
@@ -23,6 +23,14 @@ describe("useOtpInput", () => {
expect(result.current.forms.setText).toHaveBeenCalledWith("");
});
});
+ test("focus() should focus on input", () => {
+ jest.spyOn(React, "useRef").mockReturnValue({ current: { focus: jest.fn() } });
+ const { result } = renderUseOtInput();
+ result.current.actions.focus();
+ (0, react_native_1.act)(() => {
+ expect(result.current.models.inputRef.current?.focus).toHaveBeenCalled();
+ });
+ });
test("setTextWithRef() should only call setText the first 'numberOfDigits' characters", () => {
jest.spyOn(React, "useState").mockImplementation(() => ["", jest.fn()]);
const { result } = renderUseOtInput();
diff --git a/package-lock.json b/package-lock.json
index 51306ca..b07f720 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "react-native-otp-entry",
- "version": "1.2.0",
+ "version": "1.3.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "react-native-otp-entry",
- "version": "1.2.0",
+ "version": "1.3.0",
"license": "MIT",
"devDependencies": {
"@testing-library/jest-native": "^5.4.3",
diff --git a/src/OtpInput/OtpInput.test.tsx b/src/OtpInput/OtpInput.test.tsx
index 6f75d87..9d922e1 100644
--- a/src/OtpInput/OtpInput.test.tsx
+++ b/src/OtpInput/OtpInput.test.tsx
@@ -34,6 +34,22 @@ describe("OtpInput", () => {
});
});
+ test("should autoFocused by default", () => {
+ renderOtpInput();
+
+ const input = screen.getByTestId("otp-input-hidden");
+
+ expect(input.props.autoFocus).toBe(true);
+ });
+
+ test('should not focus if "autoFocus" is false', () => {
+ renderOtpInput({ autoFocus: false });
+
+ const input = screen.getByTestId("otp-input-hidden");
+
+ expect(input.props.autoFocus).toBe(false);
+ });
+
test("focusColor should not be overridden by theme", () => {
renderOtpInput({
focusColor: "#000",
diff --git a/src/OtpInput/OtpInput.tsx b/src/OtpInput/OtpInput.tsx
index e10817e..1fc960e 100644
--- a/src/OtpInput/OtpInput.tsx
+++ b/src/OtpInput/OtpInput.tsx
@@ -13,7 +13,7 @@ export const OtpInput = forwardRef((props, ref) => {
} = useOtpInput(props);
const {
numberOfDigits,
- autoFocus,
+ autoFocus = true,
hideStick,
focusColor = "#A4D0A4",
focusStickBlinkingDuration,
diff --git a/src/OtpInput/useOtpInput.test.ts b/src/OtpInput/useOtpInput.test.ts
index e78d919..1c45d43 100644
--- a/src/OtpInput/useOtpInput.test.ts
+++ b/src/OtpInput/useOtpInput.test.ts
@@ -36,6 +36,17 @@ describe("useOtpInput", () => {
});
});
+ test("focus() should focus on input", () => {
+ jest.spyOn(React, "useRef").mockReturnValue({ current: { focus: jest.fn() } } as any);
+
+ const { result } = renderUseOtInput();
+ result.current.actions.focus();
+
+ act(() => {
+ expect(result.current.models.inputRef.current?.focus).toHaveBeenCalled();
+ });
+ });
+
test("setTextWithRef() should only call setText the first 'numberOfDigits' characters", () => {
jest.spyOn(React, "useState").mockImplementation(() => ["", jest.fn()]);
const { result } = renderUseOtInput();