UNPKG

7.52 kBJavaScriptView Raw
1'use client';
2
3import * as React from 'react';
4import PropTypes from 'prop-types';
5import { unstable_debounce as debounce, unstable_useForkRef as useForkRef, unstable_useEnhancedEffect as useEnhancedEffect, unstable_ownerWindow as ownerWindow } from '@mui/utils';
6import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7function getStyleValue(value) {
8 return parseInt(value, 10) || 0;
9}
10const styles = {
11 shadow: {
12 // Visibility needed to hide the extra text area on iPads
13 visibility: 'hidden',
14 // Remove from the content flow
15 position: 'absolute',
16 // Ignore the scrollbar width
17 overflow: 'hidden',
18 height: 0,
19 top: 0,
20 left: 0,
21 // Create a new layer, increase the isolation of the computed values
22 transform: 'translateZ(0)'
23 }
24};
25function isEmpty(obj) {
26 return obj === undefined || obj === null || Object.keys(obj).length === 0 || obj.outerHeightStyle === 0 && !obj.overflowing;
27}
28
29/**
30 *
31 * Demos:
32 *
33 * - [Textarea Autosize](https://mui.com/material-ui/react-textarea-autosize/)
34 *
35 * API:
36 *
37 * - [TextareaAutosize API](https://mui.com/material-ui/api/textarea-autosize/)
38 */
39const TextareaAutosize = /*#__PURE__*/React.forwardRef(function TextareaAutosize(props, forwardedRef) {
40 const {
41 onChange,
42 maxRows,
43 minRows = 1,
44 style,
45 value,
46 ...other
47 } = props;
48 const {
49 current: isControlled
50 } = React.useRef(value != null);
51 const inputRef = React.useRef(null);
52 const handleRef = useForkRef(forwardedRef, inputRef);
53 const heightRef = React.useRef(null);
54 const shadowRef = React.useRef(null);
55 const calculateTextareaStyles = React.useCallback(() => {
56 const input = inputRef.current;
57 const containerWindow = ownerWindow(input);
58 const computedStyle = containerWindow.getComputedStyle(input);
59
60 // If input's width is shrunk and it's not visible, don't sync height.
61 if (computedStyle.width === '0px') {
62 return {
63 outerHeightStyle: 0,
64 overflowing: false
65 };
66 }
67 const inputShallow = shadowRef.current;
68 inputShallow.style.width = computedStyle.width;
69 inputShallow.value = input.value || props.placeholder || 'x';
70 if (inputShallow.value.slice(-1) === '\n') {
71 // Certain fonts which overflow the line height will cause the textarea
72 // to report a different scrollHeight depending on whether the last line
73 // is empty. Make it non-empty to avoid this issue.
74 inputShallow.value += ' ';
75 }
76 const boxSizing = computedStyle.boxSizing;
77 const padding = getStyleValue(computedStyle.paddingBottom) + getStyleValue(computedStyle.paddingTop);
78 const border = getStyleValue(computedStyle.borderBottomWidth) + getStyleValue(computedStyle.borderTopWidth);
79
80 // The height of the inner content
81 const innerHeight = inputShallow.scrollHeight;
82
83 // Measure height of a textarea with a single row
84 inputShallow.value = 'x';
85 const singleRowHeight = inputShallow.scrollHeight;
86
87 // The height of the outer content
88 let outerHeight = innerHeight;
89 if (minRows) {
90 outerHeight = Math.max(Number(minRows) * singleRowHeight, outerHeight);
91 }
92 if (maxRows) {
93 outerHeight = Math.min(Number(maxRows) * singleRowHeight, outerHeight);
94 }
95 outerHeight = Math.max(outerHeight, singleRowHeight);
96
97 // Take the box sizing into account for applying this value as a style.
98 const outerHeightStyle = outerHeight + (boxSizing === 'border-box' ? padding + border : 0);
99 const overflowing = Math.abs(outerHeight - innerHeight) <= 1;
100 return {
101 outerHeightStyle,
102 overflowing
103 };
104 }, [maxRows, minRows, props.placeholder]);
105 const syncHeight = React.useCallback(() => {
106 const textareaStyles = calculateTextareaStyles();
107 if (isEmpty(textareaStyles)) {
108 return;
109 }
110 const outerHeightStyle = textareaStyles.outerHeightStyle;
111 const input = inputRef.current;
112 if (heightRef.current !== outerHeightStyle) {
113 heightRef.current = outerHeightStyle;
114 input.style.height = `${outerHeightStyle}px`;
115 }
116 input.style.overflow = textareaStyles.overflowing ? 'hidden' : '';
117 }, [calculateTextareaStyles]);
118 useEnhancedEffect(() => {
119 const handleResize = () => {
120 syncHeight();
121 };
122 // Workaround a "ResizeObserver loop completed with undelivered notifications" error
123 // in test.
124 // Note that we might need to use this logic in production per https://github.com/WICG/resize-observer/issues/38
125 // Also see https://github.com/mui/mui-x/issues/8733
126 let rAF;
127 const rAFHandleResize = () => {
128 cancelAnimationFrame(rAF);
129 rAF = requestAnimationFrame(() => {
130 handleResize();
131 });
132 };
133 const debounceHandleResize = debounce(handleResize);
134 const input = inputRef.current;
135 const containerWindow = ownerWindow(input);
136 containerWindow.addEventListener('resize', debounceHandleResize);
137 let resizeObserver;
138 if (typeof ResizeObserver !== 'undefined') {
139 resizeObserver = new ResizeObserver(process.env.NODE_ENV === 'test' ? rAFHandleResize : handleResize);
140 resizeObserver.observe(input);
141 }
142 return () => {
143 debounceHandleResize.clear();
144 cancelAnimationFrame(rAF);
145 containerWindow.removeEventListener('resize', debounceHandleResize);
146 if (resizeObserver) {
147 resizeObserver.disconnect();
148 }
149 };
150 }, [calculateTextareaStyles, syncHeight]);
151 useEnhancedEffect(() => {
152 syncHeight();
153 });
154 const handleChange = event => {
155 if (!isControlled) {
156 syncHeight();
157 }
158 if (onChange) {
159 onChange(event);
160 }
161 };
162 return /*#__PURE__*/_jsxs(React.Fragment, {
163 children: [/*#__PURE__*/_jsx("textarea", {
164 value: value,
165 onChange: handleChange,
166 ref: handleRef
167 // Apply the rows prop to get a "correct" first SSR paint
168 ,
169 rows: minRows,
170 style: style,
171 ...other
172 }), /*#__PURE__*/_jsx("textarea", {
173 "aria-hidden": true,
174 className: props.className,
175 readOnly: true,
176 ref: shadowRef,
177 tabIndex: -1,
178 style: {
179 ...styles.shadow,
180 ...style,
181 paddingTop: 0,
182 paddingBottom: 0
183 }
184 })]
185 });
186});
187process.env.NODE_ENV !== "production" ? TextareaAutosize.propTypes /* remove-proptypes */ = {
188 // ┌────────────────────────────── Warning ──────────────────────────────┐
189 // │ These PropTypes are generated from the TypeScript type definitions. │
190 // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
191 // └─────────────────────────────────────────────────────────────────────┘
192 /**
193 * @ignore
194 */
195 className: PropTypes.string,
196 /**
197 * Maximum number of rows to display.
198 */
199 maxRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
200 /**
201 * Minimum number of rows to display.
202 * @default 1
203 */
204 minRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
205 /**
206 * @ignore
207 */
208 onChange: PropTypes.func,
209 /**
210 * @ignore
211 */
212 placeholder: PropTypes.string,
213 /**
214 * @ignore
215 */
216 style: PropTypes.object,
217 /**
218 * @ignore
219 */
220 value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.number, PropTypes.string])
221} : void 0;
222export default TextareaAutosize;
\No newline at end of file