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 }
View Code

 

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 }
View Code

 

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;
View Code

 

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 }
View Code

 

posted @ 2017-02-03 14:23  馋虫大盗  阅读(251)  评论(0)    收藏  举报