Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/thirty-snails-speak.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@razorpay/blade': minor
---

feat: add numeric validation for input and paste events on PhoneNumberInput
7 changes: 7 additions & 0 deletions packages/blade/src/components/Form/FormTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ export type FormInputOnClickEvent = {

export type FormInputHandleOnClickEvent = ({ name, value }: FormInputOnClickEvent) => void;

export type FormInputHandleOnPasteEvent = ({ name, value }: FormInputOnPasteEvent) => void;

export type FormInputOnPasteEvent = {
name?: string;
value?: React.ClipboardEvent<HTMLInputElement>;
};

export type FormInputValidationProps = {
/**
* Help text for the input
Expand Down
23 changes: 23 additions & 0 deletions packages/blade/src/components/Input/BaseInput/BaseInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import type { ActionStates } from '~utils/useInteraction';
import type {
FormInputHandleOnClickEvent,
FormInputHandleOnKeyDownEvent,
FormInputHandleOnPasteEvent,
} from '~components/Form/FormTypes';
import type {
BladeElementRef,
Expand Down Expand Up @@ -135,6 +136,10 @@ type BaseInputCommonProps = FormInputLabelProps &
* For React Native this will call `onEndEditing` event since we want to get the last value of the input field
*/
onBlur?: FormInputOnEvent;
/**
* The callback function to be invoked when value is pasted into the input field
*/
onPaste?: FormInputHandleOnPasteEvent;
/**
* Ignores the blur event animation (Used in Select to ignore blur animation when item in option is clicked)
*/
Expand Down Expand Up @@ -526,6 +531,7 @@ const useInput = ({
onSubmit,
onInput,
onKeyDown,
onPaste,
onInputKeydownTagHandler,
}: Pick<
BaseInputProps,
Expand All @@ -538,6 +544,7 @@ const useInput = ({
| 'onKeyDown'
| 'onClick'
| 'onSubmit'
| 'onPaste'
> & {
onInputKeydownTagHandler: OnInputKeydownTagHandlerType;
}): {
Expand All @@ -548,6 +555,7 @@ const useInput = ({
handleOnSubmit: FormInputHandleOnEvent;
handleOnInput: FormInputHandleOnEvent;
handleOnKeyDown: FormInputHandleOnKeyDownEvent;
handleOnPaste: FormInputHandleOnPasteEvent;
inputValue?: string;
} => {
if (__DEV__) {
Expand Down Expand Up @@ -639,6 +647,16 @@ const useInput = ({
[onBlur],
);

const handleOnPaste: FormInputHandleOnPasteEvent = React.useCallback(
({ name, value }) => {
onPaste?.({
name,
value,
});
},
[onPaste],
);

const handleOnChange: FormInputHandleOnEvent = React.useCallback(
({ name, value }) => {
let _value = '';
Expand Down Expand Up @@ -699,6 +717,7 @@ const useInput = ({
handleOnSubmit,
handleOnInput,
handleOnKeyDown,
handleOnPaste,
inputValue,
};
};
Expand Down Expand Up @@ -811,6 +830,7 @@ const _BaseInput: React.ForwardRefRenderFunction<BladeElementRef, BaseInputProps
onSubmit,
onClick,
onKeyDown,
onPaste,
isDisabled,
necessityIndicator,
validationState,
Expand Down Expand Up @@ -902,6 +922,7 @@ const _BaseInput: React.ForwardRefRenderFunction<BladeElementRef, BaseInputProps
handleOnSubmit,
handleOnInput,
handleOnKeyDown,
handleOnPaste,
inputValue,
} = useInput({
defaultValue,
Expand All @@ -913,6 +934,7 @@ const _BaseInput: React.ForwardRefRenderFunction<BladeElementRef, BaseInputProps
onSubmit,
onInput,
onKeyDown,
onPaste,
onInputKeydownTagHandler,
});
const { inputId, helpTextId, errorTextId, successTextId } = useFormId(id);
Expand Down Expand Up @@ -1082,6 +1104,7 @@ const _BaseInput: React.ForwardRefRenderFunction<BladeElementRef, BaseInputProps
handleOnInput={handleOnInput}
handleOnKeyDown={handleOnKeyDown}
handleOnClick={handleOnClick}
handleOnPaste={handleOnPaste}
leadingIcon={leadingIcon}
prefix={prefix}
trailingInteractionElement={trailingInteractionElement}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ const _StyledBaseInput: React.ForwardRefRenderFunction<
handleOnInput,
handleOnKeyDown,
handleOnClick,
handleOnPaste,
keyboardType,
keyboardReturnKeyType,
autoCompleteSuggestionType,
Expand Down Expand Up @@ -128,6 +129,9 @@ const _StyledBaseInput: React.ForwardRefRenderFunction<
onKeyDown: (event: React.KeyboardEvent<HTMLInputElement>) => {
handleOnKeyDown?.({ name, key: event.key, code: event.code, event });
},
onPaste: (event: React.ClipboardEvent<HTMLInputElement>): void => {
handleOnPaste?.({ name, value: event });
},
disabled: isDisabled,
enterKeyHint: keyboardReturnKeyType === 'default' ? 'enter' : keyboardReturnKeyType,
autoComplete: autoCompleteSuggestionType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import userEvent from '@testing-library/user-event';

import type { ReactElement } from 'react';
import { useState } from 'react';
import { fireEvent } from '@testing-library/react';
import { BaseInput } from '..';
import renderWithTheme from '~utils/testing/renderWithTheme.web';
import assertAccessible from '~utils/testing/assertAccessible.web';
Expand Down Expand Up @@ -355,4 +356,105 @@ describe('<BaseInput />', () => {
expect(container).toMatchSnapshot();
expect(getByLabelText('Enter name')).toHaveAttribute('data-analytics-name', 'base-input');
});

// Tests for new onPaste functionality
describe('onPaste functionality', () => {
it('should call onPaste prop when paste event occurs', () => {
const label = 'Enter name';
const onPaste = jest.fn();

const { getByLabelText } = renderWithTheme(
<BaseInput label={label} id="name" name="test-input" onPaste={onPaste} />,
);

const input = getByLabelText(label);

// Use fireEvent.paste to trigger the paste event
fireEvent.paste(input);

expect(onPaste).toHaveBeenCalledTimes(1);
expect(onPaste).toHaveBeenCalledWith({
name: 'test-input',
value: expect.any(Object), // ClipboardEvent
});
});

it('should pass clipboard data to onPaste handler', () => {
const label = 'Enter name';
const onPaste = jest.fn();

const { getByLabelText } = renderWithTheme(
<BaseInput label={label} id="name" name="clipboard-test" onPaste={onPaste} />,
);

const input = getByLabelText(label);

// Use fireEvent.paste with mock clipboard data
fireEvent.paste(input, {
clipboardData: {
getData: jest.fn(() => 'Hello World'),
},
});

expect(onPaste).toHaveBeenCalledTimes(1);
const callArgs = onPaste.mock.calls[0][0];
expect(callArgs.name).toBe('clipboard-test');
expect(callArgs.value).toEqual(expect.any(Object)); // ClipboardEvent object
});

it('should not call onPaste when prop is not provided', () => {
const label = 'Enter name';

const { getByLabelText } = renderWithTheme(<BaseInput label={label} id="name" />);

const input = getByLabelText(label);

// This should not throw an error even without onPaste prop
expect(() => fireEvent.paste(input)).not.toThrow();
});

it('should handle paste events with empty clipboard data', () => {
const label = 'Enter name';
const onPaste = jest.fn();

const { getByLabelText } = renderWithTheme(
<BaseInput label={label} id="name" name="empty-test" onPaste={onPaste} />,
);

const input = getByLabelText(label);

// Use fireEvent.paste with empty clipboardData
fireEvent.paste(input);

expect(onPaste).toHaveBeenCalledTimes(1);
expect(onPaste).toHaveBeenCalledWith({
name: 'empty-test',
value: expect.any(Object),
});
});

it('should handle paste events properly', () => {
const label = 'Enter name';
const onPaste = jest.fn();

const { getByLabelText } = renderWithTheme(
<BaseInput label={label} id="name" name="paste-test" onPaste={onPaste} />,
);

const input = getByLabelText(label);

// Use fireEvent.paste which creates the appropriate event
fireEvent.paste(input, {
clipboardData: {
getData: () => 'pasted content',
},
});

expect(onPaste).toHaveBeenCalledTimes(1);
expect(onPaste).toHaveBeenCalledWith({
name: 'paste-test',
value: expect.any(Object),
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,7 @@ exports[`<BaseInput /> should render 1`] = `
accessible={true}
data-blade-component="styled-base-input"
editable={true}
handleOnPaste={[Function]}
hasLeadingDropdown={false}
hasTags={false}
id="name-1-input-2"
Expand Down Expand Up @@ -552,6 +553,7 @@ exports[`<BaseInput /> should render input with no borders 1`] = `
accessible={true}
data-blade-component="styled-base-input"
editable={true}
handleOnPaste={[Function]}
hasLeadingDropdown={false}
hasTags={false}
id="name-49-input-50"
Expand Down Expand Up @@ -869,6 +871,7 @@ exports[`<BaseInput /> should render input with no borders in error state 1`] =
accessible={true}
data-blade-component="styled-base-input"
editable={true}
handleOnPaste={[Function]}
hasLeadingDropdown={false}
hasTags={false}
id="name-61-input-62"
Expand Down Expand Up @@ -1355,6 +1358,7 @@ exports[`<BaseInput /> should render input with no borders in success state 1`]
accessible={true}
data-blade-component="styled-base-input"
editable={true}
handleOnPaste={[Function]}
hasLeadingDropdown={false}
hasTags={false}
id="name-73-input-74"
Expand Down Expand Up @@ -1919,6 +1923,7 @@ exports[`<BaseInput /> should render with icons 1`] = `
accessible={true}
data-blade-component="styled-base-input"
editable={true}
handleOnPaste={[Function]}
hasLeadingDropdown={false}
hasTags={false}
id="name-85-input-86"
Expand Down Expand Up @@ -2440,6 +2445,7 @@ exports[`<BaseInput /> should render with large size input 1`] = `
accessible={true}
data-blade-component="styled-base-input"
editable={true}
handleOnPaste={[Function]}
hasLeadingDropdown={false}
hasTags={false}
id="name-97-input-98"
Expand Down Expand Up @@ -3014,6 +3020,7 @@ exports[`<BaseInput /> should render with trailingButton 1`] = `
accessible={true}
data-blade-component="styled-base-input"
editable={true}
handleOnPaste={[Function]}
hasLeadingDropdown={false}
hasTags={false}
id="coupon-109-input-110"
Expand Down
2 changes: 2 additions & 0 deletions packages/blade/src/components/Input/BaseInput/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { FormInputHandleOnEvent } from '~components/Form';
import type {
FormInputHandleOnClickEvent,
FormInputHandleOnKeyDownEvent,
FormInputHandleOnPasteEvent,
FormInputOnClickEvent,
} from '~components/Form/FormTypes';
import type { ContainerElementType } from '~utils/types';
Expand Down Expand Up @@ -56,6 +57,7 @@ export type StyledBaseInputProps = {
handleOnKeyDown?: FormInputHandleOnKeyDownEvent;
handleOnInput?: FormInputHandleOnEvent;
handleOnClick?: FormInputHandleOnClickEvent;
handleOnPaste?: FormInputHandleOnPasteEvent;
hasLeadingIcon?: boolean;
hasTrailingIcon?: boolean;
accessibilityProps: Record<string, unknown>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ exports[`<Dropdown /> with <AutoComplete /> should render AutoComplete 1`] = `
autoCompleteType="off"
data-blade-component="styled-base-input"
editable={true}
handleOnPaste={[Function]}
hasLeadingDropdown={false}
hasTags={false}
id="dropdown-7-trigger-1-input-2"
Expand Down
Loading
Loading