import React from 'react'; import { Component, cloneElement } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; import calculateNodeHeight from './calculateNodeHeight'; import assign from 'object-assign'; import omit from 'omit.js'; function fixControlledValue(value) { if (typeof value === 'undefined' || value === null) { return ''; } return value; } function onNextFrame(cb) { if (window.requestAnimationFrame) { return window.requestAnimationFrame(cb); } return window.setTimeout(cb, 1); } function clearNextFrameAction(nextFrameId) { if (window.cancelAnimationFrame) { window.cancelAnimationFrame(nextFrameId); } else { window.clearTimeout(nextFrameId); } } export interface AutoSizeType { minRows?: number; maxRows?: number; } export interface InputProps { prefixCls?: string; className?: string; type?: string; id?: number | string; name?: string; value?: any; defaultValue?: any; placeholder?: string; size?: 'large' | 'default' | 'small'; disabled?: boolean; readOnly?: boolean; addonBefore?: React.ReactNode; addonAfter?: React.ReactNode; onKeyDown?: React.FormEventHandler; onChange?: React.FormEventHandler; onPressEnter?: React.FormEventHandler; onClick?: React.FormEventHandler; onFocus?: React.FormEventHandler; onBlur?: React.FormEventHandler; autosize?: boolean | AutoSizeType; autoComplete?: 'on' | 'off'; style?: React.CSSProperties; prefix?: React.ReactNode; suffix?: React.ReactNode; spellCheck?: boolean; autoFocus?: boolean; } export default class Input extends Component { static Group: any; static Search: any; static defaultProps = { disabled: false, prefixCls: 'ant-input', type: 'text', autosize: false, }; static propTypes = { type: PropTypes.string, id: PropTypes.oneOfType([ PropTypes.string, PropTypes.number, ]), size: PropTypes.oneOf(['small', 'default', 'large']), disabled: PropTypes.bool, value: PropTypes.any, defaultValue: PropTypes.any, className: PropTypes.string, addonBefore: PropTypes.node, addonAfter: PropTypes.node, prefixCls: PropTypes.string, autosize: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]), onPressEnter: PropTypes.func, onKeyDown: PropTypes.func, onFocus: PropTypes.func, onBlur: PropTypes.func, prefix: PropTypes.node, suffix: PropTypes.node, }; nextFrameActionId: number; refs: { input: any; }; state = { textareaStyles: null, isFocus: false, }; componentDidMount() { this.resizeTextarea(); } componentWillReceiveProps(nextProps) { // Re-render with the new content then recalculate the height as required. if (this.props.value !== nextProps.value) { if (this.nextFrameActionId) { clearNextFrameAction(this.nextFrameActionId); } this.nextFrameActionId = onNextFrame(this.resizeTextarea); } } handleKeyDown = (e) => { const { onPressEnter, onKeyDown } = this.props; if (e.keyCode === 13 && onPressEnter) { onPressEnter(e); } if (onKeyDown) { onKeyDown(e); } } handleTextareaChange = (e) => { if (!('value' in this.props)) { this.resizeTextarea(); } const onChange = this.props.onChange; if (onChange) { onChange(e); } } resizeTextarea = () => { const { type, autosize } = this.props; if (type !== 'textarea' || !autosize || !this.refs.input) { return; } const minRows = autosize ? (autosize as AutoSizeType).minRows : null; const maxRows = autosize ? (autosize as AutoSizeType).maxRows : null; const textareaStyles = calculateNodeHeight(this.refs.input, false, minRows, maxRows); this.setState({ textareaStyles }); } focus() { this.refs.input.focus(); } renderLabeledInput(children) { const props = this.props; // Not wrap when there is not addons if (props.type === 'textarea' || (!props.addonBefore && !props.addonAfter)) { return children; } const wrapperClassName = `${props.prefixCls}-group`; const addonClassName = `${wrapperClassName}-addon`; const addonBefore = props.addonBefore ? ( {props.addonBefore} ) : null; const addonAfter = props.addonAfter ? ( {props.addonAfter} ) : null; const className = classNames(`${props.prefixCls}-wrapper`, { [wrapperClassName]: (addonBefore || addonAfter), }); const node = ( {addonBefore} {children} {addonAfter} ); // Need another wrapper for changing display:table to display:inline-block if (addonBefore || addonAfter) { return {node}; } return node; } renderLabeledIcon(children) { const { props } = this; if (props.type === 'textarea' || !('prefix' in props || 'suffix' in props)) { return children; } const prefix = props.prefix ? ( {props.prefix} ) : null; const suffix = props.suffix ? ( {props.suffix} ) : null; return ( {prefix} {cloneElement(children, { style: null })} {suffix} ); } renderInput() { const props = assign({}, this.props); // Fix https://fb.me/react-unknown-prop const otherProps = omit(this.props, [ 'prefixCls', 'onPressEnter', 'autosize', 'addonBefore', 'addonAfter', 'prefix', 'suffix', ]); const prefixCls = props.prefixCls; if (!props.type) { return props.children; } const inputClassName = classNames(prefixCls, { [`${prefixCls}-sm`]: props.size === 'small', [`${prefixCls}-lg`]: props.size === 'large', }, props.className); if ('value' in props) { otherProps.value = fixControlledValue(props.value); // Input elements must be either controlled or uncontrolled, // specify either the value prop, or the defaultValue prop, but not both. delete otherProps.defaultValue; } switch (props.type) { case 'textarea': return (