2019-03-18 22:41:46 +08:00
|
|
|
import React, { PureComponent, ChangeEvent } from 'react';
|
2019-03-18 17:41:28 +08:00
|
|
|
import classNames from 'classnames';
|
2019-03-18 22:41:46 +08:00
|
|
|
import { validate, EventsWithValidation, hasValidationEvent } from '../../utils';
|
|
|
|
|
import { ValidationEvents, ValidationRule } from '../../types';
|
2019-03-18 17:41:28 +08:00
|
|
|
|
|
|
|
|
export enum InputStatus {
|
|
|
|
|
Invalid = 'invalid',
|
|
|
|
|
Valid = 'valid',
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface Props extends React.HTMLProps<HTMLInputElement> {
|
|
|
|
|
validationEvents?: ValidationEvents;
|
|
|
|
|
hideErrorMessage?: boolean;
|
|
|
|
|
|
|
|
|
|
// Override event props and append status as argument
|
|
|
|
|
onBlur?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
|
|
|
|
|
onFocus?: (event: React.FocusEvent<HTMLInputElement>, status?: InputStatus) => void;
|
2019-03-18 22:41:46 +08:00
|
|
|
onChange?: (event: React.ChangeEvent<HTMLInputElement>, status?: InputStatus) => void;
|
2019-03-18 17:41:28 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export class Input extends PureComponent<Props> {
|
|
|
|
|
static defaultProps = {
|
|
|
|
|
className: '',
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
state = {
|
|
|
|
|
error: null,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
get status() {
|
|
|
|
|
return this.state.error ? InputStatus.Invalid : InputStatus.Valid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
get isInvalid() {
|
|
|
|
|
return this.status === InputStatus.Invalid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
validatorAsync = (validationRules: ValidationRule[]) => {
|
2019-03-18 22:41:46 +08:00
|
|
|
return (evt: ChangeEvent<HTMLInputElement>) => {
|
2019-03-18 17:41:28 +08:00
|
|
|
const errors = validate(evt.target.value, validationRules);
|
|
|
|
|
this.setState(prevState => {
|
2019-03-18 22:41:46 +08:00
|
|
|
return { ...prevState, error: errors ? errors[0] : null };
|
2019-03-18 17:41:28 +08:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2019-03-18 22:41:46 +08:00
|
|
|
populateEventPropsWithStatus = (restProps: any, validationEvents: ValidationEvents | undefined) => {
|
2019-03-18 17:41:28 +08:00
|
|
|
const inputElementProps = { ...restProps };
|
2019-03-18 22:41:46 +08:00
|
|
|
if (!validationEvents) {
|
|
|
|
|
return inputElementProps;
|
|
|
|
|
}
|
|
|
|
|
Object.keys(EventsWithValidation).forEach(eventName => {
|
|
|
|
|
if (hasValidationEvent(eventName as EventsWithValidation, validationEvents) || restProps[eventName]) {
|
|
|
|
|
inputElementProps[eventName] = async (evt: ChangeEvent<HTMLInputElement>) => {
|
2019-03-18 17:41:28 +08:00
|
|
|
evt.persist(); // Needed for async. https://reactjs.org/docs/events.html#event-pooling
|
2019-03-18 22:41:46 +08:00
|
|
|
if (hasValidationEvent(eventName as EventsWithValidation, validationEvents)) {
|
2019-03-18 17:41:28 +08:00
|
|
|
await this.validatorAsync(validationEvents[eventName]).apply(this, [evt]);
|
|
|
|
|
}
|
|
|
|
|
if (restProps[eventName]) {
|
|
|
|
|
restProps[eventName].apply(null, [evt, this.status]);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return inputElementProps;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
|
const { validationEvents, className, hideErrorMessage, ...restProps } = this.props;
|
|
|
|
|
const { error } = this.state;
|
|
|
|
|
const inputClassName = classNames('gf-form-input', { invalid: this.isInvalid }, className);
|
|
|
|
|
const inputElementProps = this.populateEventPropsWithStatus(restProps, validationEvents);
|
|
|
|
|
|
|
|
|
|
return (
|
2019-03-25 22:53:05 +08:00
|
|
|
<div>
|
2019-03-18 17:41:28 +08:00
|
|
|
<input {...inputElementProps} className={inputClassName} />
|
|
|
|
|
{error && !hideErrorMessage && <span>{error}</span>}
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|