2017-01-26 20:05:32 +03:00
|
|
|
import React, { PropTypes } from 'react';
|
2016-05-21 19:57:49 +03:00
|
|
|
import classnames from 'classnames';
|
|
|
|
import { themr } from 'react-css-themr';
|
2017-01-26 20:05:32 +03:00
|
|
|
import { INPUT } from '../identifiers';
|
|
|
|
import InjectedFontIcon from '../font_icon/FontIcon';
|
2015-09-20 23:21:11 +03:00
|
|
|
|
2016-05-28 18:44:29 +03:00
|
|
|
const factory = (FontIcon) => {
|
|
|
|
class Input extends React.Component {
|
|
|
|
static propTypes = {
|
2017-01-26 20:05:32 +03:00
|
|
|
children: PropTypes.node,
|
|
|
|
className: PropTypes.string,
|
|
|
|
disabled: PropTypes.bool,
|
|
|
|
error: PropTypes.oneOfType([
|
|
|
|
PropTypes.string,
|
|
|
|
PropTypes.node,
|
2016-12-14 05:42:24 +03:00
|
|
|
]),
|
2017-01-26 20:05:32 +03:00
|
|
|
floating: PropTypes.bool,
|
|
|
|
hint: PropTypes.oneOfType([
|
|
|
|
PropTypes.string,
|
|
|
|
PropTypes.node,
|
2016-12-14 05:42:24 +03:00
|
|
|
]),
|
2017-01-26 20:05:32 +03:00
|
|
|
icon: PropTypes.oneOfType([
|
|
|
|
PropTypes.string,
|
|
|
|
PropTypes.element,
|
2016-05-28 18:44:29 +03:00
|
|
|
]),
|
2017-01-26 20:05:32 +03:00
|
|
|
label: PropTypes.oneOfType([
|
|
|
|
PropTypes.string,
|
|
|
|
PropTypes.node,
|
2016-12-14 05:42:24 +03:00
|
|
|
]),
|
2017-01-26 20:05:32 +03:00
|
|
|
maxLength: PropTypes.number,
|
|
|
|
multiline: PropTypes.bool,
|
|
|
|
name: PropTypes.string,
|
|
|
|
onBlur: PropTypes.func,
|
|
|
|
onChange: PropTypes.func,
|
|
|
|
onFocus: PropTypes.func,
|
|
|
|
onKeyPress: PropTypes.func,
|
|
|
|
required: PropTypes.bool,
|
|
|
|
rows: PropTypes.number,
|
|
|
|
theme: PropTypes.shape({
|
|
|
|
bar: PropTypes.string,
|
|
|
|
counter: PropTypes.string,
|
|
|
|
disabled: PropTypes.string,
|
|
|
|
error: PropTypes.string,
|
|
|
|
errored: PropTypes.string,
|
|
|
|
hidden: PropTypes.string,
|
|
|
|
hint: PropTypes.string,
|
|
|
|
icon: PropTypes.string,
|
|
|
|
input: PropTypes.string,
|
|
|
|
inputElement: PropTypes.string,
|
|
|
|
required: PropTypes.string,
|
|
|
|
withIcon: PropTypes.string,
|
2016-05-28 18:44:29 +03:00
|
|
|
}),
|
2017-01-26 20:05:32 +03:00
|
|
|
type: PropTypes.string,
|
|
|
|
value: PropTypes.oneOfType([
|
|
|
|
PropTypes.object,
|
|
|
|
PropTypes.string,
|
|
|
|
]),
|
2016-05-28 18:44:29 +03:00
|
|
|
};
|
2015-09-20 23:21:11 +03:00
|
|
|
|
2016-05-28 18:44:29 +03:00
|
|
|
static defaultProps = {
|
|
|
|
className: '',
|
|
|
|
hint: '',
|
|
|
|
disabled: false,
|
|
|
|
floating: true,
|
|
|
|
multiline: false,
|
|
|
|
required: false,
|
2017-01-26 20:05:32 +03:00
|
|
|
type: 'text',
|
2016-05-28 18:44:29 +03:00
|
|
|
};
|
2015-09-20 23:21:11 +03:00
|
|
|
|
2017-01-26 20:05:32 +03:00
|
|
|
componentDidMount() {
|
2016-07-21 16:15:35 +03:00
|
|
|
if (this.props.multiline) {
|
|
|
|
window.addEventListener('resize', this.handleAutoresize);
|
2016-08-04 04:34:28 +03:00
|
|
|
this.handleAutoresize();
|
2016-07-21 16:15:35 +03:00
|
|
|
}
|
2016-06-24 04:10:24 +03:00
|
|
|
}
|
|
|
|
|
2017-01-26 20:05:32 +03:00
|
|
|
componentWillReceiveProps(nextProps) {
|
2016-06-24 04:10:24 +03:00
|
|
|
if (!this.props.multiline && nextProps.multiline) {
|
|
|
|
window.addEventListener('resize', this.handleAutoresize);
|
|
|
|
} else if (this.props.multiline && !nextProps.multiline) {
|
|
|
|
window.removeEventListener('resize', this.handleAutoresize);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-26 20:05:32 +03:00
|
|
|
componentDidUpdate() {
|
2016-08-04 04:41:24 +03:00
|
|
|
// resize the textarea, if nessesary
|
2016-08-04 22:52:54 +03:00
|
|
|
if (this.props.multiline) this.handleAutoresize();
|
2016-08-04 04:41:24 +03:00
|
|
|
}
|
|
|
|
|
2017-01-26 20:05:32 +03:00
|
|
|
componentWillUnmount() {
|
2016-08-04 04:40:10 +03:00
|
|
|
if (this.props.multiline) window.removeEventListener('resize', this.handleAutoresize);
|
2016-06-24 04:10:24 +03:00
|
|
|
}
|
|
|
|
|
2016-05-28 18:44:29 +03:00
|
|
|
handleChange = (event) => {
|
2016-08-04 04:45:38 +03:00
|
|
|
const { onChange, multiline, maxLength } = this.props;
|
|
|
|
const valueFromEvent = event.target.value;
|
|
|
|
|
2016-08-04 23:02:03 +03:00
|
|
|
// Trim value to maxLength if that exists (only on multiline inputs).
|
|
|
|
// Note that this is still required even tho we have the onKeyPress filter
|
|
|
|
// because the user could paste smt in the textarea.
|
2016-08-04 04:45:38 +03:00
|
|
|
const haveToTrim = (multiline && maxLength && event.target.value.length > maxLength);
|
|
|
|
const value = haveToTrim ? valueFromEvent.substr(0, maxLength) : valueFromEvent;
|
|
|
|
|
|
|
|
// propagate to to store and therefore to the input
|
|
|
|
if (onChange) onChange(value, event);
|
2016-05-28 18:44:29 +03:00
|
|
|
};
|
2016-06-24 04:10:24 +03:00
|
|
|
|
|
|
|
handleAutoresize = () => {
|
2017-01-26 20:05:32 +03:00
|
|
|
const element = this.inputNode;
|
2016-10-13 03:15:33 +03:00
|
|
|
const rows = this.props.rows;
|
|
|
|
|
|
|
|
if (typeof rows === 'number' && !isNaN(rows)) {
|
|
|
|
element.style.height = null;
|
|
|
|
} else {
|
|
|
|
// compute the height difference between inner height and outer height
|
|
|
|
const style = getComputedStyle(element, null);
|
|
|
|
const heightOffset = style.boxSizing === 'content-box'
|
|
|
|
? -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom))
|
|
|
|
: parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
|
|
|
|
|
|
|
|
// resize the input to its content size
|
|
|
|
element.style.height = 'auto';
|
|
|
|
element.style.height = `${element.scrollHeight + heightOffset}px`;
|
|
|
|
}
|
2016-06-24 04:10:24 +03:00
|
|
|
}
|
2015-11-25 13:24:06 +03:00
|
|
|
|
2016-08-06 19:30:08 +03:00
|
|
|
handleKeyPress = (event) => {
|
2016-08-04 22:56:36 +03:00
|
|
|
// prevent insertion of more characters if we're a multiline input
|
|
|
|
// and maxLength exists
|
|
|
|
const { multiline, maxLength, onKeyPress } = this.props;
|
|
|
|
if (multiline && maxLength) {
|
|
|
|
// check if smt is selected, in which case the newly added charcter would
|
|
|
|
// replace the selected characters, so the length of value doesn't actually
|
|
|
|
// increase.
|
|
|
|
const isReplacing = event.target.selectionEnd - event.target.selectionStart;
|
|
|
|
const value = event.target.value;
|
|
|
|
|
|
|
|
if (!isReplacing && value.length === maxLength) {
|
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
2017-01-26 20:05:32 +03:00
|
|
|
return undefined;
|
2016-08-04 22:56:36 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (onKeyPress) onKeyPress(event);
|
2017-01-26 20:05:32 +03:00
|
|
|
return undefined;
|
2016-08-06 19:30:08 +03:00
|
|
|
};
|
|
|
|
|
2017-01-26 20:05:32 +03:00
|
|
|
blur() {
|
|
|
|
this.inputNode.blur();
|
2016-08-06 19:30:08 +03:00
|
|
|
}
|
|
|
|
|
2017-01-26 20:05:32 +03:00
|
|
|
focus() {
|
|
|
|
this.inputNode.focus();
|
2016-08-04 22:56:36 +03:00
|
|
|
}
|
|
|
|
|
2017-01-26 20:05:32 +03:00
|
|
|
render() {
|
2016-05-28 18:44:29 +03:00
|
|
|
const { children, disabled, error, floating, hint, icon,
|
2016-06-06 20:37:51 +03:00
|
|
|
name, label: labelText, maxLength, multiline, required,
|
2017-01-26 20:05:32 +03:00
|
|
|
theme, type, value, onKeyPress, rows = 1, ...others } = this.props;
|
2016-05-28 18:44:29 +03:00
|
|
|
const length = maxLength && value ? value.length : 0;
|
2017-01-26 20:05:32 +03:00
|
|
|
const labelClassName = classnames(theme.label, { [theme.fixed]: !floating });
|
2015-11-29 14:39:55 +03:00
|
|
|
|
2016-05-28 18:44:29 +03:00
|
|
|
const className = classnames(theme.input, {
|
|
|
|
[theme.disabled]: disabled,
|
|
|
|
[theme.errored]: error,
|
|
|
|
[theme.hidden]: type === 'hidden',
|
2017-01-26 20:05:32 +03:00
|
|
|
[theme.withIcon]: icon,
|
2016-05-28 18:44:29 +03:00
|
|
|
}, this.props.className);
|
2015-09-20 23:21:11 +03:00
|
|
|
|
2016-07-10 14:42:35 +03:00
|
|
|
const valuePresent = value !== null
|
|
|
|
&& value !== undefined
|
|
|
|
&& value !== ''
|
2017-01-26 20:05:32 +03:00
|
|
|
&& !(typeof value === Number && isNaN(value)); // eslint-disable-line
|
2016-03-06 15:56:35 +03:00
|
|
|
|
2016-08-04 04:46:20 +03:00
|
|
|
const inputElementProps = {
|
2016-05-28 18:44:29 +03:00
|
|
|
...others,
|
2017-01-26 20:05:32 +03:00
|
|
|
className: classnames(theme.inputElement, { [theme.filled]: valuePresent }),
|
2016-05-28 18:44:29 +03:00
|
|
|
onChange: this.handleChange,
|
2017-01-26 20:05:32 +03:00
|
|
|
ref: (node) => { this.inputNode = node; },
|
2016-05-28 18:44:29 +03:00
|
|
|
role: 'input',
|
2016-06-06 20:37:51 +03:00
|
|
|
name,
|
2016-05-28 18:44:29 +03:00
|
|
|
disabled,
|
|
|
|
required,
|
|
|
|
type,
|
2017-01-26 20:05:32 +03:00
|
|
|
value,
|
2016-08-04 04:46:20 +03:00
|
|
|
};
|
2016-08-04 22:56:36 +03:00
|
|
|
if (!multiline) {
|
2016-08-04 22:58:41 +03:00
|
|
|
inputElementProps.maxLength = maxLength;
|
2016-08-04 22:56:36 +03:00
|
|
|
inputElementProps.onKeyPress = onKeyPress;
|
|
|
|
} else {
|
2016-10-13 03:15:33 +03:00
|
|
|
inputElementProps.rows = rows;
|
2016-08-06 19:30:08 +03:00
|
|
|
inputElementProps.onKeyPress = this.handleKeyPress;
|
2016-08-04 22:56:36 +03:00
|
|
|
}
|
|
|
|
|
2016-05-28 18:44:29 +03:00
|
|
|
return (
|
2017-01-26 20:05:32 +03:00
|
|
|
<div data-react-toolbox="input" className={className}>
|
2016-08-06 19:30:08 +03:00
|
|
|
{React.createElement(multiline ? 'textarea' : 'input', inputElementProps)}
|
2016-05-28 18:44:29 +03:00
|
|
|
{icon ? <FontIcon className={theme.icon} value={icon} /> : null}
|
2016-08-07 22:23:14 +03:00
|
|
|
<span className={theme.bar} />
|
2016-05-28 18:44:29 +03:00
|
|
|
{labelText
|
2017-01-26 20:05:32 +03:00
|
|
|
? <label className={labelClassName} htmlFor={name}>
|
|
|
|
{labelText}
|
|
|
|
{required ? <span className={theme.required}> * </span> : null}
|
|
|
|
</label>
|
2016-05-28 18:44:29 +03:00
|
|
|
: null}
|
2016-10-09 14:37:43 +03:00
|
|
|
{hint ? <span hidden={labelText} className={theme.hint}>{hint}</span> : null}
|
2016-05-28 18:44:29 +03:00
|
|
|
{error ? <span className={theme.error}>{error}</span> : null}
|
|
|
|
{maxLength ? <span className={theme.counter}>{length}/{maxLength}</span> : null}
|
|
|
|
{children}
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
2015-10-20 08:40:51 +03:00
|
|
|
}
|
2015-10-22 02:31:17 +03:00
|
|
|
|
2016-05-28 18:44:29 +03:00
|
|
|
return Input;
|
|
|
|
};
|
|
|
|
|
|
|
|
const Input = factory(InjectedFontIcon);
|
2016-08-02 22:51:13 +03:00
|
|
|
export default themr(INPUT, null, { withRef: true })(Input);
|
2016-05-28 18:44:29 +03:00
|
|
|
export { factory as inputFactory };
|
2016-05-25 01:25:43 +03:00
|
|
|
export { Input };
|