UNPKG

weex-nuke

Version:

基于 Rax 、Weex 的高性能组件体系 ~~

489 lines (463 loc) 13.1 kB
'use strict'; /** @jsx createElement */ import { createElement, Component, findDOMNode, cloneElement, PropTypes } from 'rax'; import View from 'nuke-view'; import Text from 'nuke-text'; import Image from 'nuke-image'; import { isWeb, isWeex } from 'nuke-env'; import { genEventObject } from '../mods/util'; import TEXTINHERITKEYS from '../mods/inherit-keys'; import BaseInput from 'nuke-base-input'; import IMAGE_URL from '../mods/image'; import PlaceHolder from '../mods/placeholder'; import Count from '../mods/count'; import { isValidInput, isValidNumber } from '../uitls'; const textlineheight = 36; class Input extends Component { constructor(props, context) { super(props); this.validInput = true; 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 = { focus: false, value, lines: 1, inputValue: value, count, maxLengthError: props.maxLength ? count > props.maxLength : false, }; this.materialDesign = true; this.fixedFont = context.commonConfigs && context.commonConfigs.fixedFont; if ('fixedFont' in props) { this.fixedFont = props.fixedFont; } this.inputHandler = this.inputHandler.bind(this); this.focusHandler = this.focusHandler.bind(this); this.blurHandler = this.blurHandler.bind(this); this.clearHandler = this.clearHandler.bind(this); this.changeHandler = this.changeHandler.bind(this); this.returnHandler = this.returnHandler.bind(this); this.getRef = this.getRef.bind(this); this.getValue = this.getValue.bind(this); this.focus = this.focus.bind(this); this.blur = this.blur.bind(this); this.clear = this.clear.bind(this); this.setNativeFormatRule = this.setNativeFormatRule.bind(this); } componentDidMount() { const { multiple } = this.props; if (isWeb && multiple) { const textarea = findDOMNode(this.refs.baseinput); this.minHeight = textarea.scrollHeight; // 18 } } componentWillReceiveProps(nextProps) { if ( 'value' in nextProps && (typeof nextProps.value === 'string' || typeof nextProps.value === 'number') && nextProps.value !== this.state.inputValue ) { const { type } = this.props; // if (type === 'number' && !isValidNumber(nextProps.value)) return; this.setState({ value: nextProps.value, inputValue: nextProps.value, count: nextProps.value.length, maxLengthError: nextProps.value.length > this.props.maxLength, }); } } getTextRect(ref) { return new Promise((resolve, reject) => { const domEl = findDOMNode(ref); if (domEl) { if (isWeex) { try { const dom = require('@weex-module/dom'); dom.getComponentRect(domEl.ref, (e) => { if (e && e.size && e.size.height) { resolve(e.size.height.toFixed()); } else { reject(0); } }); } catch (e) { reject(0); } } } else { reject(0); } }); } setTextareaHeight(lines) { this.setState({ lines, }); } getHideElement(hiddenStyle) { const { multiple, maxRows } = this.props; if (!multiple || isWeb) return null; return ( <Text fixedFont={this.fixedFont} ref={(n) => { this.multipleText = n; }} style={hiddenStyle} numberOfLines={maxRows} > {this.state.inputValue} </Text> ); } clearHandler(e) { this.setState({ value: this.state.inputValue, }); this.setState({ value: '', inputValue: '', count: 0, maxLengthError: false, }); this.focus(); this.trigger('onClear', ''); } blur() { this.refs.baseinput.blur(); } focus() { this.refs.baseinput.focus(); } getRef() { return this.refs.baseinput.getRef(); } setNativeFormatRule(rules) { return this.refs.baseinput.setNativeFormatRule(rules); } clear() { this.setState({ value: '', inputValue: '', count: 0, maxLengthError: false, }); this.trigger('onClear', ''); } trigger(fn, ...attrs) { if (typeof fn === 'string') fn = this.props[fn]; if (!(typeof fn === 'function')) return; return fn.apply(this, attrs); } focusHandler(e) { this.setState({ focus: true, }); this.trigger('onFocus', e); } blurHandler(e) { const { type } = this.props; // hack For type number useage invalid input can't get any value if (type === 'number' && isWeb) { this.validInput = isValidInput(e.srcElement); } this.setState({ focus: false, }); this.trigger('onBlur', e); } returnHandler(e) { this.trigger('onReturn', e); } inputHandler(text, eventObj) { const { multiple, maxLength } = this.props; this.setState({ inputValue: text, count: text.length, maxLengthError: maxLength ? text.length > maxLength : false, }); this.trigger('onInput', eventObj); } changeHandler(text, eventObj) { const { type, maxLength } = this.props; if (type === 'date' || type === 'time') { this.setState({ inputValue: text, }); } else { this.setState({ value: text, inputValue: text, count: text.length, maxLengthError: text.length > maxLength, }); } this.trigger('onChange', text, eventObj); } renderPlaceholder() { const { focus, inputValue, maxLengthError } = this.state; const { placeholder, placeholderColor, themeStyle, status, disabled, errorMessage, floatPlaceholder, hideErrorWhenFocus, } = this.props; const placeholderAttrs = { placeholder, placeholderColor, themeStyle, status, disabled, errorMessage, floatPlaceholder, hideErrorWhenFocus, focus, inputValue, validInput: this.validInput, maxLengthError, fixedFont: this.fixedFont, }; return <PlaceHolder {...placeholderAttrs} />; } renderCount() { const { maxLength, multiple, renderCount, themeStyle } = this.props; if (!maxLength || !renderCount) return null; const { count } = this.state; const countAttrs = { maxLength, multiple, renderCount, count, themeStyle, fixedFont: this.fixedFont, }; return <Count {...countAttrs} />; } renderClear(styles) { return <Image source={{ uri: IMAGE_URL.clear }} onClick={this.clearHandler} style={styles['md-clear']} />; } renderCustomIcon(styles) { const { icon = {} } = this.props; const { uri, onPress = () => {}, style = {} } = icon; if (!uri) return null; return ( <View style={[styles['md-icon'], style]} onClick={onPress}> <Image source={{ uri }} style={[styles['icon-image'], style]} /> </View> ); } renderHelpLine(styles) { const { status, errorMessage, autoAdjustHeight, hideErrorWhenFocus = false, renderCount } = this.props; const { focus, maxLengthError } = this.state; let renderErrorMessage; if (hideErrorWhenFocus) { renderErrorMessage = !focus && status === 'error' && errorMessage; } else { renderErrorMessage = status === 'error' && errorMessage; } if (!renderErrorMessage && !renderCount && autoAdjustHeight) return null; let errorMessageDom; if (errorMessage) { if (typeof errorMessage === 'string') { errorMessageDom = ( <Text fixedFont={this.fixedFont} style={styles['md-error-text']}> {errorMessage} </Text> ); } else { errorMessageDom = cloneElement(errorMessage, { style: errorMessage.props.style ? Object.assign({}, styles['md-error-text'], errorMessage.props.style) : styles['md-error-text'], }); } } return ( <View data-role="md-help" style={styles['md-help']}> {renderErrorMessage ? errorMessageDom : null} {this.renderCount()} </View> ); } getValue() { return this.refs.baseinput.getValue(); } render() { const { defaultValue, onChange, onFocus, onInput, onBlur, placeholder, renderCount, readOnly, disabled, style = {}, maxLength, multiple, inputStyle, returnKeyType, hasClear, rows, type, icon, autoAdjustHeight, status, floatPlaceholder, hideErrorWhenFocus, errorMessage, themeStyle, maxRows, ...others } = this.props; const styles = this.props.themeStyle; const { focus, value, lines, maxLengthError } = this.state; const mode = multiple ? 'multiple' : 'single'; const MDWrapperStyle = Object.assign( {}, styles['input-wrap'], styles[`${mode}-md-wrap`], readOnly ? styles.readonly : {}, floatPlaceholder ? {} : styles['static-placeholder'], disabled ? styles[`md-${mode}-disabled`] : {}, style ); let mdLineStyle = Object.assign( {}, styles['md-bottom-line'], status === 'error' ? styles['md-bottom-line-error'] : {}, focus ? styles['md-bottom-line-focus'] : {} ); if ((status === 'error' && !hideErrorWhenFocus) || maxLengthError) { mdLineStyle = Object.assign(mdLineStyle, styles['md-bottom-line-error']); } if (disabled) { mdLineStyle = Object.assign(mdLineStyle, styles['md-bottom-line-disabled']); } const inputElementAttrs = { onInput: this.inputHandler, onFocus: this.focusHandler, onBlur: this.blurHandler, onChange: this.changeHandler, onReturn: this.returnHandler, onResize: this.resizeHandler, multiple, disabled, readOnly, returnKeyType, maxLength, placeholder: '', value: typeof value !== null ? value : null, type, style: multiple ? styles['multiple-md-input-ele'] : styles['md-input-ele'], }; if (isWeb) { if ('readOnly' in inputElementAttrs && !inputElementAttrs.readOnly) { delete inputElementAttrs.readOnly; } } if (multiple) { if (isWeb) { inputElementAttrs.rows = 1; } else { // maxRows // inputElementAttrs.rows = lines; inputElementAttrs.style.height = maxRows * textlineheight; // not adjust maxRows inputElementAttrs.rows = maxRows; } } TEXTINHERITKEYS.map((item) => { if (styles[item]) { inputElementAttrs.style[item] = styles[item]; } }); const multipleTextStyle = Object.assign( {}, inputElementAttrs.style, styles['text-for-height'] // {lineHeight: textlineheight } ); if ('height' in multipleTextStyle) { delete multipleTextStyle.height; } const inputElestyle = inputElementAttrs.style; return ( <View data-role="md-wrap" style={MDWrapperStyle}> {this.renderPlaceholder()} {this.getHideElement(multipleTextStyle)} <BaseInput fixedFont={this.fixedFont} ref="baseinput" {...others} {...inputElementAttrs} /> <View data-role="md-line" style={mdLineStyle} /> {this.renderHelpLine(styles)} {hasClear ? this.renderClear(styles) : null} {!hasClear ? this.renderCustomIcon(styles) : null} </View> ); } } Input.propTypes = { defaultValue: PropTypes.any, onFocus: PropTypes.func, onInput: PropTypes.func, onBlur: 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, placeholderColor: PropTypes.string, value: PropTypes.string, themeStyle: PropTypes.any, renderCount: PropTypes.boolean, floatPlaceholder: PropTypes.boolean, hideErrorWhenFocus: PropTypes.boolean, fixedFont: PropTypes.boolean, }; Input.defaultProps = { onFocus: () => {}, onInput: () => {}, onBlur: () => {}, placeholder: '', readOnly: false, disabled: false, style: {}, multiple: false, inputStyle: {}, themeStyle: {}, hasClear: false, type: 'text', rows: 1, maxRows: 3, status: 'success', renderCount: false, placeholderColor: '#999999', floatPlaceholder: true, hideErrorWhenFocus: true, fixedFont: true, }; Input.contextTypes = { androidConfigs: PropTypes.any, commonConfigs: PropTypes.any, }; export default Input;