weex-nuke
Version:
基于 Rax 、Weex 的高性能组件体系 ~~
367 lines (351 loc) • 9.14 kB
JSX
'use strict';
/** @jsx createElement */
import { createElement, Component, PropTypes } from 'rax';
import View from 'nuke-view';
import Text from 'nuke-text';
import Image from 'nuke-image';
import Icon from 'nuke-icon';
import BaseInput from 'nuke-base-input';
import { connectStyle } from 'nuke-theme-provider';
import { isWeex } from 'nuke-env';
import { textKeys } from 'nuke-helper';
import stylesProvider from '../styles';
class NormalInput extends Component {
constructor(props, context) {
super(props);
let value = null;
if ('value' in props) {
value = props.value;
} else if ('defaultValue' in props) {
value = props.defaultValue;
}
const count = (value && value.length) || 0;
this.state = {
value,
inputValue: value,
count: (value && value.length) || 0,
maxLengthError: count > props.maxLength,
};
this.fixedFont = context.commonConfigs && context.commonConfigs.fixedFont;
if ('fixedFont' in props) {
this.fixedFont = props.fixedFont;
}
[
'inputHandler',
'changeHander',
'clearHandler',
'focusHandler',
'blurHandler',
'getRef',
'getValue',
'focus',
'blur',
'clear',
].forEach((m) => {
this[m] = this[m].bind(this);
});
}
componentWillReceiveProps(nextProps) {
if (
'value' in nextProps &&
(typeof nextProps.value === 'string' ||
typeof nextProps.value === 'number') &&
nextProps.value !== this.state.inputValue
) {
this.setState({
value: nextProps.value,
inputValue: nextProps.value,
count: nextProps.value.length,
maxLengthError: nextProps.value.length > this.props.maxLength,
});
}
}
getInput() {
const {
readOnly,
disabled,
onInput,
onChange,
inputStyle = {},
hasClear,
rows,
multiple,
...others
} = this.props;
const { value } = this.state;
const styles = this.props.themeStyle;
const inputAttrs = {
...others,
value: typeof value !== null ? value : null,
readOnly,
multiple,
style: Object.assign(
{},
styles['input-ele'],
hasClear ? styles['input-has-clear'] : null,
multiple ? styles['multiple-input'] : styles['single-input'],
inputStyle
),
onInput: this.inputHandler,
onChange: this.changeHander,
onFocus: this.focusHandler,
onBlur: this.blurHandler,
disabled,
};
if (isWeex) {
// 如果是多行文本,添加 returnKeyType 会导致无法换行
if (multiple && !this.props.onReturn) {
delete inputAttrs.onReturn;
delete inputAttrs.returnKeyType;
}
} else if (!disabled) {
delete inputAttrs.disabled;
}
if (multiple) {
inputAttrs.rows = rows || 3;
}
textKeys.map((item) => {
if (styles[item]) {
inputAttrs.style[item] = styles[item];
}
});
return (
<BaseInput
fixedFont={this.fixedFont}
{...inputAttrs}
ref={(n) => {
this.input = n;
}}
/>
);
}
clear() {
this.setState({
value: '',
count: 0,
});
}
clearHandler(e) {
this.setState({
value: this.state.inputValue,
});
this.setState({
value: '',
inputValue: '',
count: 0,
});
this.focus();
this.trigger('onClear', '');
}
focusHandler(e) {
this.setState({
focus: true,
});
this.trigger('onFocus', e);
}
trigger(fn, ...attrs) {
if (typeof fn === 'string') fn = this.props[fn];
if (!(typeof fn === 'function')) return;
return fn.apply(this, attrs);
}
blurHandler(e) {
this.setState({
focus: false,
});
this.trigger('onBlur', e);
}
focus() {
this.input.focus();
}
getRef() {
return this.input.getRef();
}
blur() {
this.input.blur();
}
getValue() {
return this.input.getValue();
}
changeHander(text, eventObj) {
const { maxLength } = this.props;
this.setState({
value: text,
inputValue: text,
count: text.length,
maxLengthError: text.length > maxLength,
});
this.trigger('onChange', text, eventObj);
}
inputHandler(text, eventObj) {
const { maxLength } = this.props;
this.setState({
inputValue: text,
count: text.length,
maxLengthError: text.length > maxLength,
});
this.trigger('onInput', eventObj);
}
renderClear(styles) {
const { count } = this.state;
const { clearIconStyle = {} } = this.props;
return count ? (
<Icon
name="delete-fill"
onClick={this.clearHandler}
style={[styles.clear, clearIconStyle]}
/>
) : null;
}
renderCustomIcon(styles) {
const { icon = {} } = this.props;
const { uri, onPress = () => {}, style = {} } = icon;
if (!uri) return null;
return (
<View style={[styles.icon, style]} onClick={onPress}>
<Image source={{ uri }} style={[styles['icon-image'], style]} />
</View>
);
}
renderCount(styles) {
const { maxLength, multiple, renderCount } = this.props;
const { maxLengthError } = this.state;
if (!maxLength || !renderCount) return null;
const { count } = this.state;
return (
<View
data-role="count"
style={
styles[`${multiple ? 'multiple-count-wrap' : 'single-count-wrap'}`]
}
>
<Text
fixedFont={this.fixedFont}
style={[
styles[`${multiple ? 'multiple-count-text' : 'single-count-text'}`],
maxLengthError ? styles['count-error'] : {},
]}
>
{count} / {maxLength}
</Text>
</View>
);
}
calcInputStyle() {
const {
readOnly,
disabled,
style = {},
hideErrorWhenFocus,
multiple,
status,
themeStyle: styles,
} = this.props;
const { focus, maxLengthError } = this.state;
let inputWrapperStyle = Object.assign(
{},
styles['input-wrap'],
styles[`${multiple ? 'multiple-wrap' : 'single-wrap'}`],
readOnly ? styles.readonly : {},
status === 'error' ? styles['error-input-wrap'] : {},
focus ? styles['focus-input-wrap'] : {},
disabled ? styles[`${multiple ? 'multiple' : 'single'}-disabled`] : {}
);
if ((status === 'error' && !hideErrorWhenFocus) || maxLengthError) {
inputWrapperStyle = Object.assign(
inputWrapperStyle,
styles['error-input-wrap']
);
}
if (disabled) {
inputWrapperStyle = Object.assign(
inputWrapperStyle,
styles[`${multiple ? 'multiple' : 'single'}-disabled`]
);
}
return { ...inputWrapperStyle, ...style };
}
render() {
const { renderCount, hasClear, status, errorMessage } = this.props;
const styles = this.props.themeStyle;
const { focus } = this.state;
const inputWrapperStyle = this.calcInputStyle();
return (
<View data-role="normal-wrap" style={inputWrapperStyle}>
{this.getInput()}
{hasClear ? this.renderClear(styles) : null}
{!hasClear ? this.renderCustomIcon(styles) : null}
{(status === 'error' && errorMessage) || renderCount ? (
<View data-role="help" style={styles.help}>
{!focus && status === 'error' && errorMessage ? (
<Text fixedFont={this.fixedFont} style={styles['error-text']}>
{errorMessage}
</Text>
) : null}
{this.renderCount(styles)}
</View>
) : null}
</View>
);
}
}
NormalInput.propTypes = {
defaultValue: PropTypes.any,
onFocus: PropTypes.func,
onInput: PropTypes.func,
onBlur: PropTypes.func,
onReturn: PropTypes.func,
placeholder: PropTypes.string,
readOnly: PropTypes.boolean,
disabled: PropTypes.boolean,
style: PropTypes.any,
maxLength: PropTypes.number,
multiple: PropTypes.boolean,
inputStyle: PropTypes.any,
returnKeyType: PropTypes.any,
hasClear: PropTypes.boolean,
rows: PropTypes.number,
maxRows: PropTypes.number,
type: PropTypes.oneOf([
'text',
'url',
'password',
'tel',
'date',
'time',
'email',
]),
status: PropTypes.oneOf(['success', 'error']),
errorMessage: PropTypes.string,
renderCount: PropTypes.boolean,
value: PropTypes.string,
themeStyle: PropTypes.any,
hideErrorWhenFocus: PropTypes.boolean,
};
NormalInput.defaultProps = {
onFocus: () => {},
onInput: () => {},
onBlur: () => {},
onReturn: () => {},
placeholder: '',
readOnly: false,
disabled: false,
style: {},
multiple: false,
inputStyle: {},
hasClear: false,
type: 'text',
status: 'success',
themeStyle: {},
renderCount: false,
placeholderColor: '#999999',
hideErrorWhenFocus: true,
};
NormalInput.contextTypes = {
androidConfigs: PropTypes.any,
commonConfigs: PropTypes.any,
};
NormalInput.displayName = 'Input';
const StyledInput = connectStyle(stylesProvider, { withRef: true })(
NormalInput
);
export default StyledInput;