) => {
+ const { value } = currentTarget;
+ if (this.state.error) {
+ this.setState(
+ {
+ value,
+ },
+ this.checkValidity,
+ );
+ } else {
+ this.setState({
+ value,
+ });
+ }
+ };
+
+ onValidityStateUpdateHandler = (error: ValidityState | TextAreaError) => {
+ if ((error as ValidityState).valid !== undefined) {
+ this.setErrorFromValidityState(error as ValidityState);
+ } else {
+ this.setState({
+ error: error as TextAreaError,
+ });
+ }
+ };
+
+ setErrorFromValidityState(validityState: ValidityState) {
+ const { badInput, customError, tooLong, valid, valueMissing } = validityState;
+
+ const { isRequired, maxLength, validation } = this.props;
+
+ const { value } = this.state;
+
+ let error;
+
+ if (valid) {
+ error = null;
+ } else if (badInput) {
+ error = messages.badInput();
+ } else if (tooLong && typeof maxLength !== 'undefined') {
+ error = messages.tooLong(maxLength);
+ } else if (valueMissing) {
+ error = messages.valueMissing();
+ } else if (customError && (isRequired || value.trim().length) && validation) {
+ error = validation(value);
+ }
+
+ this.setState({
+ error,
+ });
+ }
+
+ textarea: HTMLTextAreaElement | null | undefined;
+
+ // Updates component value and validity state
+ checkValidity = () => {
+ const { isRequired, validation } = this.props;
+ const { textarea } = this;
+
+ if (!textarea) {
+ return;
+ }
+
+ if (validation && (isRequired || textarea.value.trim().length)) {
+ const error = validation(textarea.value);
+ this.setState({
+ error,
+ value: textarea.value,
+ });
+
+ if (error) {
+ textarea.setCustomValidity(error.code);
+ } else {
+ textarea.setCustomValidity('');
+ }
+ } else {
+ this.setErrorFromValidityState(textarea.validity);
+ }
+ };
+
+ render() {
+ const {
+ autoFocus,
+ className = '',
+ isDisabled,
+ isReadOnly,
+ isRequired,
+ isResizable,
+ label,
+ name,
+ placeholder,
+ } = this.props;
+
+ const { error, value } = this.state;
+
+ return (
+
+
+ {
+ this.textarea = textarea;
+ }}
+ value={value}
+ />
+
+
+ );
+ }
+}
+
+export default TextArea;
diff --git a/src/components/form-elements/text-area/__tests__/TextArea.test.js b/src/components/form-elements/text-area/__tests__/TextArea.test.tsx
similarity index 81%
rename from src/components/form-elements/text-area/__tests__/TextArea.test.js
rename to src/components/form-elements/text-area/__tests__/TextArea.test.tsx
index 1cf4c30a93..0b3ad17d53 100644
--- a/src/components/form-elements/text-area/__tests__/TextArea.test.js
+++ b/src/components/form-elements/text-area/__tests__/TextArea.test.tsx
@@ -15,7 +15,7 @@ describe('components/form-elements/text-area/TextArea', () => {
test('should correctly render default component', () => {
const wrapper = shallow();
- expect(wrapper.find('TextArea').length).toEqual(1);
+ expect(wrapper.find('TextArea')).toHaveLength(1);
});
test('should update state if value prop changes', () => {
@@ -59,13 +59,15 @@ describe('components/form-elements/text-area/TextArea', () => {
});
test('should set an textarea as valid when the validityFn returns an void', () => {
- function validityFn() {}
+ function validityFn() {
+ return undefined;
+ }
- const wrapper = mount();
+ const wrapper = mount();
const textarea = wrapper.find('textarea');
textarea.simulate('blur');
- expect(textarea.instance().validity.valid).toBeTruthy();
+ expect((textarea.instance() as unknown as HTMLTextAreaElement).validity.valid).toBeTruthy();
});
test('should set an textarea as invalid when the validityFn returns an error string and input is not empty', () => {
@@ -76,13 +78,11 @@ describe('components/form-elements/text-area/TextArea', () => {
};
}
- const wrapper = mount(
- ,
- );
+ const wrapper = mount();
const textarea = wrapper.find('textarea');
textarea.simulate('blur');
- expect(textarea.instance().validity.valid).toBeFalsy();
+ expect((textarea.instance() as unknown as HTMLTextAreaElement).validity.valid).toBeFalsy();
});
test('should set an textarea as valid when intially then fixed when using validityFn', () => {
@@ -93,19 +93,19 @@ describe('components/form-elements/text-area/TextArea', () => {
message: 'errMessage',
});
- stub.onCall(1).returns();
+ stub.onCall(1).returns(undefined);
- const wrapper = mount();
+ const wrapper = mount();
let textarea = wrapper.find('textarea');
textarea.simulate('blur');
- expect(textarea.instance().validity.valid).toBeFalsy();
+ expect((textarea.instance() as unknown as HTMLTextAreaElement).validity.valid).toBeFalsy();
// Get the re-rendered textarea again
textarea = wrapper.find('textarea');
textarea.simulate('blur');
- expect(textarea.instance().validity.valid).toBeTruthy();
+ expect((textarea.instance() as unknown as HTMLTextAreaElement).validity.valid).toBeTruthy();
});
test('should not set textarea invalid when the validityFn returns an error string and textarea is empty and not required', () => {
@@ -116,11 +116,11 @@ describe('components/form-elements/text-area/TextArea', () => {
};
}
- const wrapper = mount();
+ const wrapper = mount();
const textarea = wrapper.find('textarea');
textarea.simulate('blur');
- expect(textarea.instance().validity.valid).toBeTruthy();
+ expect((textarea.instance() as unknown as HTMLTextAreaElement).validity.valid).toBeTruthy();
});
test('should set textarea invalid when the validityFn returns an error string, textarea is empty and is required', () => {
@@ -131,13 +131,11 @@ describe('components/form-elements/text-area/TextArea', () => {
};
}
- const wrapper = mount(
- ,
- );
+ const wrapper = mount();
const textarea = wrapper.find('textarea');
textarea.simulate('blur');
- expect(textarea.instance().validity.valid).toBeFalsy();
+ expect((textarea.instance() as unknown as HTMLTextAreaElement).validity.valid).toBeFalsy();
});
test('should re-validate when textarea is set via props programaticallly', () => {
@@ -150,7 +148,7 @@ describe('components/form-elements/text-area/TextArea', () => {
wrapper.setProps({ value: 'abba' });
textarea.simulate('blur');
- const textareaEl = textarea.getDOMNode();
+ const textareaEl = textarea.getDOMNode() as HTMLTextAreaElement;
textareaEl.value = 'a';
textarea.simulate('change', {
currentTarget: textareaEl,
@@ -167,7 +165,7 @@ describe('components/form-elements/text-area/TextArea', () => {
expect(wrapper.find('.text-area-container').hasClass('show-error')).toBeTruthy();
- const textareaEl = textarea.getDOMNode();
+ const textareaEl = textarea.getDOMNode() as HTMLTextAreaElement;
textareaEl.value = 'a';
textarea.simulate('change', {
currentTarget: textareaEl,
@@ -198,7 +196,9 @@ describe('components/form-elements/text-area/TextArea', () => {
validityStateHandlerSpy.callArgWith(1, error);
});
- expect(component.find('TextArea').first().instance().state.error).toEqual(error);
+ expect((component.find('TextArea').first().instance() as InstanceType).state.error).toEqual(
+ error,
+ );
});
test('should set validity state when set validity state handler is called with ValidityState object', () => {
@@ -221,6 +221,8 @@ describe('components/form-elements/text-area/TextArea', () => {
act(() => {
validityStateHandlerSpy.callArgWith(1, error);
});
- expect(component.find('TextArea').first().instance().state.error.code).toEqual('badInput');
+ expect(
+ (component.find('TextArea').first().instance() as InstanceType).state.error.code,
+ ).toEqual('badInput');
});
});
diff --git a/src/components/form-elements/text-area/index.js b/src/components/form-elements/text-area/index.js.flow
similarity index 100%
rename from src/components/form-elements/text-area/index.js
rename to src/components/form-elements/text-area/index.js.flow
diff --git a/src/components/form-elements/text-area/index.ts b/src/components/form-elements/text-area/index.ts
new file mode 100644
index 0000000000..79beb776de
--- /dev/null
+++ b/src/components/form-elements/text-area/index.ts
@@ -0,0 +1,2 @@
+export { default } from './TextArea';
+export type { TextAreaProps } from './TextArea';