ANTD 2.6.3 Input源码分析
Input.tsx
1 import React from 'react'; 2 import { Component, PropTypes, cloneElement } from 'react'; 3 import classNames from 'classnames'; 4 import calculateNodeHeight from './calculateNodeHeight'; 5 import assign from 'object-assign'; 6 import omit from 'omit.js'; 7 8 function fixControlledValue(value) { 9 if (typeof value === 'undefined' || value === null) { 10 return ''; 11 } 12 return value; 13 } 14 15 function onNextFrame(cb) { 16 if (window.requestAnimationFrame) { 17 return window.requestAnimationFrame(cb); 18 } 19 return window.setTimeout(cb, 1); 20 } 21 22 function clearNextFrameAction(nextFrameId) { 23 if (window.cancelAnimationFrame) { 24 window.cancelAnimationFrame(nextFrameId); 25 } else { 26 window.clearTimeout(nextFrameId); 27 } 28 } 29 30 export interface AutoSizeType { 31 minRows?: number; 32 maxRows?: number; 33 }; 34 35 export interface InputProps { 36 prefixCls?: string; 37 className?: string; 38 type?: string; 39 id?: number | string; 40 name?: string; 41 value?: any; 42 defaultValue?: any; 43 placeholder?: string; 44 size?: 'large' | 'default' | 'small'; 45 disabled?: boolean; 46 readOnly?: boolean; 47 addonBefore?: React.ReactNode; 48 addonAfter?: React.ReactNode; 49 onPressEnter?: React.FormEventHandler<any>; 50 onKeyDown?: React.FormEventHandler<any>; 51 onChange?: React.FormEventHandler<any>; 52 onClick?: React.FormEventHandler<any>; 53 onFocus?: React.FormEventHandler<any>; 54 onBlur?: React.FormEventHandler<any>; 55 autosize?: boolean | AutoSizeType; 56 autoComplete?: 'on' | 'off'; 57 style?: React.CSSProperties; 58 prefix?: React.ReactNode; 59 suffix?: React.ReactNode; 60 } 61 62 export default class Input extends Component<InputProps, any> { 63 static Group: any; 64 static Search: any; 65 static defaultProps = { 66 disabled: false, 67 prefixCls: 'ant-input', 68 type: 'text', 69 autosize: false, 70 }; 71 72 static propTypes = { 73 type: PropTypes.string, 74 id: PropTypes.oneOfType([ 75 PropTypes.string, 76 PropTypes.number, 77 ]), 78 size: PropTypes.oneOf(['small', 'default', 'large']), 79 disabled: PropTypes.bool, 80 value: PropTypes.any, 81 defaultValue: PropTypes.any, 82 className: PropTypes.string, 83 addonBefore: PropTypes.node, 84 addonAfter: PropTypes.node, 85 prefixCls: PropTypes.string, 86 autosize: PropTypes.oneOfType([PropTypes.bool, PropTypes.object]), 87 onPressEnter: PropTypes.func, 88 onKeyDown: PropTypes.func, 89 onFocus: PropTypes.func, 90 onBlur: PropTypes.func, 91 prefix: PropTypes.node, 92 suffix: PropTypes.node, 93 }; 94 95 nextFrameActionId: number; 96 refs: { 97 input: any; 98 }; 99 100 state = { 101 textareaStyles: null, 102 isFocus: false, 103 }; 104 105 componentDidMount() { 106 this.resizeTextarea(); 107 } 108 109 componentWillReceiveProps(nextProps) { 110 // Re-render with the new content then recalculate the height as required. 111 if (this.props.value !== nextProps.value) { 112 if (this.nextFrameActionId) { 113 clearNextFrameAction(this.nextFrameActionId); 114 } 115 this.nextFrameActionId = onNextFrame(this.resizeTextarea); 116 } 117 } 118 119 componentDidUpdate(prevProps) { 120 const { props, state, refs } = this; 121 const preHasPresuffix = prevProps.prefix || prevProps.suffix; 122 const curHasPresuffix = props.prefix || props.suffix; 123 if (state.isFocus && (preHasPresuffix !== curHasPresuffix)) { 124 refs.input.focus(); 125 } 126 } 127 128 handleFocus = (e) => { 129 const { onFocus } = this.props; 130 this.setState({ 131 isFocus: true, 132 }); 133 if (onFocus) { 134 onFocus(e); 135 } 136 } 137 138 handleBlur = (e) => { 139 const { onBlur } = this.props; 140 this.setState({ 141 isFocus: false, 142 }); 143 if (onBlur) { 144 onBlur(e); 145 } 146 } 147 148 handleKeyDown = (e) => { 149 const { onPressEnter, onKeyDown } = this.props; 150 if (e.keyCode === 13 && onPressEnter) { 151 onPressEnter(e); 152 } 153 if (onKeyDown) { 154 onKeyDown(e); 155 } 156 } 157 158 handleTextareaChange = (e) => { 159 if (!('value' in this.props)) { 160 this.resizeTextarea(); 161 } 162 const onChange = this.props.onChange; 163 if (onChange) { 164 onChange(e); 165 } 166 } 167 168 resizeTextarea = () => { 169 const { type, autosize } = this.props; 170 if (type !== 'textarea' || !autosize || !this.refs.input) { 171 return; 172 } 173 const minRows = autosize ? (autosize as AutoSizeType).minRows : null; 174 const maxRows = autosize ? (autosize as AutoSizeType).maxRows : null; 175 const textareaStyles = calculateNodeHeight(this.refs.input, false, minRows, maxRows); 176 this.setState({ textareaStyles }); 177 } 178 179 focus() { 180 this.refs.input.focus(); 181 } 182 183 renderLabeledInput(children) { 184 const props = this.props; 185 186 // Not wrap when there is not addons 187 if (props.type === 'textarea' || (!props.addonBefore && !props.addonAfter)) { 188 return children; 189 } 190 191 const wrapperClassName = `${props.prefixCls}-group`; 192 const addonClassName = `${wrapperClassName}-addon`; 193 const addonBefore = props.addonBefore ? ( 194 <span className={addonClassName}> 195 {props.addonBefore} 196 </span> 197 ) : null; 198 199 const addonAfter = props.addonAfter ? ( 200 <span className={addonClassName}> 201 {props.addonAfter} 202 </span> 203 ) : null; 204 205 const className = classNames({ 206 [`${props.prefixCls}-wrapper`]: true, 207 [wrapperClassName]: (addonBefore || addonAfter), 208 }); 209 210 return ( 211 <span className={className}> 212 {addonBefore} 213 {children} 214 {addonAfter} 215 </span> 216 ); 217 } 218 219 renderLabeledIcon(children) { 220 const { props } = this; 221 222 if (props.type === 'textarea' || (!props.prefix && !props.suffix)) { 223 return children; 224 } 225 226 const prefix = props.prefix ? ( 227 <span className={`${props.prefixCls}-prefix`}> 228 {props.prefix} 229 </span> 230 ) : null; 231 232 const suffix = props.suffix ? ( 233 <span className={`${props.prefixCls}-suffix`}> 234 {props.suffix} 235 </span> 236 ) : null; 237 238 return ( 239 <span className={`${props.prefixCls}-preSuffix-wrapper`} style={props.style}> 240 {prefix} 241 {cloneElement(children, { style: null })} 242 {suffix} 243 </span> 244 ); 245 } 246 247 renderInput() { 248 const props = assign({}, this.props); 249 // Fix https://fb.me/react-unknown-prop 250 const otherProps = omit(this.props, [ 251 'prefixCls', 252 'onPressEnter', 253 'autosize', 254 'addonBefore', 255 'addonAfter', 256 'prefix', 257 'suffix', 258 ]); 259 260 const prefixCls = props.prefixCls; 261 if (!props.type) { 262 return props.children; 263 } 264 265 const inputClassName = classNames(prefixCls, { 266 [`${prefixCls}-sm`]: props.size === 'small', 267 [`${prefixCls}-lg`]: props.size === 'large', 268 }, props.className); 269 270 if ('value' in props) { 271 otherProps.value = fixControlledValue(props.value); 272 // Input elements must be either controlled or uncontrolled, 273 // specify either the value prop, or the defaultValue prop, but not both. 274 delete otherProps.defaultValue; 275 } 276 277 switch (props.type) { 278 case 'textarea': 279 return ( 280 <textarea 281 {...otherProps} 282 style={assign({}, props.style, this.state.textareaStyles)} 283 className={inputClassName} 284 onKeyDown={this.handleKeyDown} 285 onChange={this.handleTextareaChange} 286 ref="input" 287 /> 288 ); 289 default: 290 return this.renderLabeledIcon( 291 <input 292 {...otherProps} 293 className={inputClassName} 294 onKeyDown={this.handleKeyDown} 295 onFocus={this.handleFocus} 296 onBlur={this.handleBlur} 297 ref="input" 298 /> 299 ); 300 } 301 } 302 303 render() { 304 return this.renderLabeledInput(this.renderInput()); 305 } 306 }
calculateNodeHeight.tsx
1 // Thanks to https://github.com/andreypopp/react-textarea-autosize/ 2 3 /** 4 * calculateNodeHeight(uiTextNode, useCache = false) 5 */ 6 7 const HIDDEN_TEXTAREA_STYLE = ` 8 min-height:0 !important; 9 max-height:none !important; 10 height:0 !important; 11 visibility:hidden !important; 12 overflow:hidden !important; 13 position:absolute !important; 14 z-index:-1000 !important; 15 top:0 !important; 16 right:0 !important 17 `; 18 19 const SIZING_STYLE = [ 20 'letter-spacing', 21 'line-height', 22 'padding-top', 23 'padding-bottom', 24 'font-family', 25 'font-weight', 26 'font-size', 27 'text-rendering', 28 'text-transform', 29 'width', 30 'text-indent', 31 'padding-left', 32 'padding-right', 33 'border-width', 34 'box-sizing', 35 ]; 36 37 let computedStyleCache = {}; 38 let hiddenTextarea; 39 40 function calculateNodeStyling(node, useCache = false) { 41 const nodeRef = ( 42 node.getAttribute('id') || 43 node.getAttribute('data-reactid') || 44 node.getAttribute('name') 45 ); 46 47 if (useCache && computedStyleCache[nodeRef]) { 48 return computedStyleCache[nodeRef]; 49 } 50 51 const style = window.getComputedStyle(node); 52 53 const boxSizing = ( 54 style.getPropertyValue('box-sizing') || 55 style.getPropertyValue('-moz-box-sizing') || 56 style.getPropertyValue('-webkit-box-sizing') 57 ); 58 59 const paddingSize = ( 60 parseFloat(style.getPropertyValue('padding-bottom')) + 61 parseFloat(style.getPropertyValue('padding-top')) 62 ); 63 64 const borderSize = ( 65 parseFloat(style.getPropertyValue('border-bottom-width')) + 66 parseFloat(style.getPropertyValue('border-top-width')) 67 ); 68 69 const sizingStyle = SIZING_STYLE 70 .map(name => `${name}:${style.getPropertyValue(name)}`) 71 .join(';'); 72 73 const nodeInfo = { 74 sizingStyle, 75 paddingSize, 76 borderSize, 77 boxSizing, 78 }; 79 80 if (useCache && nodeRef) { 81 computedStyleCache[nodeRef] = nodeInfo; 82 } 83 84 return nodeInfo; 85 } 86 87 export default function calculateNodeHeight( 88 uiTextNode, 89 useCache = false, 90 minRows: number | null = null, 91 maxRows: number | null = null 92 ) { 93 if (!hiddenTextarea) { 94 hiddenTextarea = document.createElement('textarea'); 95 document.body.appendChild(hiddenTextarea); 96 } 97 98 // Copy all CSS properties that have an impact on the height of the content in 99 // the textbox 100 let { 101 paddingSize, borderSize, 102 boxSizing, sizingStyle, 103 } = calculateNodeStyling(uiTextNode, useCache); 104 105 // Need to have the overflow attribute to hide the scrollbar otherwise 106 // text-lines will not calculated properly as the shadow will technically be 107 // narrower for content 108 hiddenTextarea.setAttribute('style', `${sizingStyle};${HIDDEN_TEXTAREA_STYLE}`); 109 hiddenTextarea.value = uiTextNode.value || uiTextNode.placeholder || ''; 110 111 let minHeight = -Infinity; 112 let maxHeight = Infinity; 113 let height = hiddenTextarea.scrollHeight; 114 115 if (boxSizing === 'border-box') { 116 // border-box: add border, since height = content + padding + border 117 height = height + borderSize; 118 } else if (boxSizing === 'content-box') { 119 // remove padding, since height = content 120 height = height - paddingSize; 121 } 122 123 if (minRows !== null || maxRows !== null) { 124 // measure height of a textarea with a single row 125 hiddenTextarea.value = ''; 126 let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize; 127 if (minRows !== null) { 128 minHeight = singleRowHeight * minRows; 129 if (boxSizing === 'border-box') { 130 minHeight = minHeight + paddingSize + borderSize; 131 } 132 height = Math.max(minHeight, height); 133 } 134 if (maxRows !== null) { 135 maxHeight = singleRowHeight * maxRows; 136 if (boxSizing === 'border-box') { 137 maxHeight = maxHeight + paddingSize + borderSize; 138 } 139 height = Math.min(maxHeight, height); 140 } 141 } 142 return { height, minHeight, maxHeight }; 143 }
Group.tsx
1 import React from 'react'; 2 import classNames from 'classnames'; 3 4 export interface GroupProps { 5 className?: string; 6 size?: 'large' | 'small' | 'default'; 7 children?: any; 8 style?: React.CSSProperties; 9 prefixCls?: string; 10 compact?: boolean; 11 } 12 13 const Group: React.StatelessComponent<GroupProps> = (props) => { 14 const { prefixCls = 'ant-input-group', className = '' } = props; 15 const cls = classNames(prefixCls, { 16 [`${prefixCls}-lg`]: props.size === 'large', 17 [`${prefixCls}-sm`]: props.size === 'small', 18 [`${prefixCls}-compact`]: props.compact, 19 }, className); 20 return ( 21 <span className={cls} style={props.style}> 22 {props.children} 23 </span> 24 ); 25 }; 26 27 export default Group;
Search.tsx
1 import React from 'react'; 2 import classNames from 'classnames'; 3 import Input from './Input'; 4 import Icon from '../icon'; 5 6 export interface SearchProps { 7 className?: string; 8 placeholder?: string; 9 prefixCls?: string; 10 style?: React.CSSProperties; 11 defaultValue?: any; 12 value?: any; 13 onChange?: React.FormEventHandler<any>; 14 onSearch?: (value: string) => any; 15 size?: 'large' | 'default' | 'small'; 16 disabled?: boolean; 17 readOnly?: boolean; 18 } 19 20 export default class Search extends React.Component<SearchProps, any> { 21 static defaultProps = { 22 prefixCls: 'ant-input-search', 23 onSearch() {}, 24 }; 25 input: any; 26 onSearch = () => { 27 const { onSearch } = this.props; 28 if (onSearch) { 29 onSearch(this.input.refs.input.value); 30 } 31 this.input.refs.input.focus(); 32 } 33 render() { 34 const { className, prefixCls, ...others } = this.props; 35 delete others.onSearch; 36 const searchSuffix = ( 37 <Icon 38 className={`${prefixCls}-icon`} 39 onClick={this.onSearch} 40 type="search" 41 /> 42 ); 43 return ( 44 <Input 45 className={classNames(prefixCls, className)} 46 onPressEnter={this.onSearch} 47 ref={node => this.input = node} 48 suffix={searchSuffix} 49 {...others} 50 /> 51 ); 52 } 53 }

浙公网安备 33010602011771号