UNPKG

7.26 kBJavaScriptView Raw
1import _extends from "@babel/runtime/helpers/esm/extends";
2import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
3import * as React from 'react';
4import PropTypes from 'prop-types';
5import debounce from '../utils/debounce';
6import useForkRef from '../utils/useForkRef';
7import deprecatedPropType from '../utils/deprecatedPropType';
8
9function getStyleValue(computedStyle, property) {
10 return parseInt(computedStyle[property], 10) || 0;
11}
12
13const useEnhancedEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
14const styles = {
15 /* Styles applied to the shadow textarea element. */
16 shadow: {
17 // Visibility needed to hide the extra text area on iPads
18 visibility: 'hidden',
19 // Remove from the content flow
20 position: 'absolute',
21 // Ignore the scrollbar width
22 overflow: 'hidden',
23 height: 0,
24 top: 0,
25 left: 0,
26 // Create a new layer, increase the isolation of the computed values
27 transform: 'translateZ(0)'
28 }
29};
30const TextareaAutosize = /*#__PURE__*/React.forwardRef(function TextareaAutosize(props, ref) {
31 const {
32 onChange,
33 rows,
34 rowsMax,
35 rowsMin: rowsMinProp,
36 maxRows: maxRowsProp,
37 minRows: minRowsProp = 1,
38 style,
39 value
40 } = props,
41 other = _objectWithoutPropertiesLoose(props, ["onChange", "rows", "rowsMax", "rowsMin", "maxRows", "minRows", "style", "value"]);
42
43 const maxRows = maxRowsProp || rowsMax;
44 const minRows = rows || rowsMinProp || minRowsProp;
45 const {
46 current: isControlled
47 } = React.useRef(value != null);
48 const inputRef = React.useRef(null);
49 const handleRef = useForkRef(ref, inputRef);
50 const shadowRef = React.useRef(null);
51 const renders = React.useRef(0);
52 const [state, setState] = React.useState({});
53 const syncHeight = React.useCallback(() => {
54 const input = inputRef.current;
55 const computedStyle = window.getComputedStyle(input);
56 const inputShallow = shadowRef.current;
57 inputShallow.style.width = computedStyle.width;
58 inputShallow.value = input.value || props.placeholder || 'x';
59
60 if (inputShallow.value.slice(-1) === '\n') {
61 // Certain fonts which overflow the line height will cause the textarea
62 // to report a different scrollHeight depending on whether the last line
63 // is empty. Make it non-empty to avoid this issue.
64 inputShallow.value += ' ';
65 }
66
67 const boxSizing = computedStyle['box-sizing'];
68 const padding = getStyleValue(computedStyle, 'padding-bottom') + getStyleValue(computedStyle, 'padding-top');
69 const border = getStyleValue(computedStyle, 'border-bottom-width') + getStyleValue(computedStyle, 'border-top-width'); // The height of the inner content
70
71 const innerHeight = inputShallow.scrollHeight - padding; // Measure height of a textarea with a single row
72
73 inputShallow.value = 'x';
74 const singleRowHeight = inputShallow.scrollHeight - padding; // The height of the outer content
75
76 let outerHeight = innerHeight;
77
78 if (minRows) {
79 outerHeight = Math.max(Number(minRows) * singleRowHeight, outerHeight);
80 }
81
82 if (maxRows) {
83 outerHeight = Math.min(Number(maxRows) * singleRowHeight, outerHeight);
84 }
85
86 outerHeight = Math.max(outerHeight, singleRowHeight); // Take the box sizing into account for applying this value as a style.
87
88 const outerHeightStyle = outerHeight + (boxSizing === 'border-box' ? padding + border : 0);
89 const overflow = Math.abs(outerHeight - innerHeight) <= 1;
90 setState(prevState => {
91 // Need a large enough difference to update the height.
92 // This prevents infinite rendering loop.
93 if (renders.current < 20 && (outerHeightStyle > 0 && Math.abs((prevState.outerHeightStyle || 0) - outerHeightStyle) > 1 || prevState.overflow !== overflow)) {
94 renders.current += 1;
95 return {
96 overflow,
97 outerHeightStyle
98 };
99 }
100
101 if (process.env.NODE_ENV !== 'production') {
102 if (renders.current === 20) {
103 console.error(['Material-UI: Too many re-renders. The layout is unstable.', 'TextareaAutosize limits the number of renders to prevent an infinite loop.'].join('\n'));
104 }
105 }
106
107 return prevState;
108 });
109 }, [maxRows, minRows, props.placeholder]);
110 React.useEffect(() => {
111 const handleResize = debounce(() => {
112 renders.current = 0;
113 syncHeight();
114 });
115 window.addEventListener('resize', handleResize);
116 return () => {
117 handleResize.clear();
118 window.removeEventListener('resize', handleResize);
119 };
120 }, [syncHeight]);
121 useEnhancedEffect(() => {
122 syncHeight();
123 });
124 React.useEffect(() => {
125 renders.current = 0;
126 }, [value]);
127
128 const handleChange = event => {
129 renders.current = 0;
130
131 if (!isControlled) {
132 syncHeight();
133 }
134
135 if (onChange) {
136 onChange(event);
137 }
138 };
139
140 return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement("textarea", _extends({
141 value: value,
142 onChange: handleChange,
143 ref: handleRef // Apply the rows prop to get a "correct" first SSR paint
144 ,
145 rows: minRows,
146 style: _extends({
147 height: state.outerHeightStyle,
148 // Need a large enough difference to allow scrolling.
149 // This prevents infinite rendering loop.
150 overflow: state.overflow ? 'hidden' : null
151 }, style)
152 }, other)), /*#__PURE__*/React.createElement("textarea", {
153 "aria-hidden": true,
154 className: props.className,
155 readOnly: true,
156 ref: shadowRef,
157 tabIndex: -1,
158 style: _extends({}, styles.shadow, style)
159 }));
160});
161process.env.NODE_ENV !== "production" ? TextareaAutosize.propTypes = {
162 // ----------------------------- Warning --------------------------------
163 // | These PropTypes are generated from the TypeScript type definitions |
164 // | To update them edit the d.ts file and run "yarn proptypes" |
165 // ----------------------------------------------------------------------
166
167 /**
168 * @ignore
169 */
170 className: PropTypes.string,
171
172 /**
173 * Maximum number of rows to display.
174 */
175 maxRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
176
177 /**
178 * Minimum number of rows to display.
179 */
180 minRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
181
182 /**
183 * @ignore
184 */
185 onChange: PropTypes.func,
186
187 /**
188 * @ignore
189 */
190 placeholder: PropTypes.string,
191
192 /**
193 * Minimum number of rows to display.
194 * @deprecated Use `minRows` instead.
195 */
196 rows: deprecatedPropType(PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 'Use `minRows` instead.'),
197
198 /**
199 * Maximum number of rows to display.
200 * @deprecated Use `maxRows` instead.
201 */
202 rowsMax: deprecatedPropType(PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 'Use `maxRows` instead.'),
203
204 /**
205 * Minimum number of rows to display.
206 * @deprecated Use `minRows` instead.
207 */
208 rowsMin: deprecatedPropType(PropTypes.oneOfType([PropTypes.number, PropTypes.string]), 'Use `minRows` instead.'),
209
210 /**
211 * @ignore
212 */
213 style: PropTypes.object,
214
215 /**
216 * @ignore
217 */
218 value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.number, PropTypes.string])
219} : void 0;
220export default TextareaAutosize;
\No newline at end of file