UNPKG

7.76 kBJavaScriptView Raw
1'use client';
2
3import _extends from "@babel/runtime/helpers/esm/extends";
4import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
5const _excluded = ["onChange", "maxRows", "minRows", "style", "value"];
6import * as React from 'react';
7import PropTypes from 'prop-types';
8import { unstable_debounce as debounce, unstable_useForkRef as useForkRef, unstable_useEnhancedEffect as useEnhancedEffect, unstable_ownerWindow as ownerWindow } from '@mui/utils';
9import { jsx as _jsx } from "react/jsx-runtime";
10import { jsxs as _jsxs } from "react/jsx-runtime";
11function getStyleValue(value) {
12 return parseInt(value, 10) || 0;
13}
14const styles = {
15 shadow: {
16 // Visibility needed to hide the extra text area on iPads
17 visibility: 'hidden',
18 // Remove from the content flow
19 position: 'absolute',
20 // Ignore the scrollbar width
21 overflow: 'hidden',
22 height: 0,
23 top: 0,
24 left: 0,
25 // Create a new layer, increase the isolation of the computed values
26 transform: 'translateZ(0)'
27 }
28};
29function isEmpty(obj) {
30 return obj === undefined || obj === null || Object.keys(obj).length === 0 || obj.outerHeightStyle === 0 && !obj.overflowing;
31}
32
33/**
34 *
35 * Demos:
36 *
37 * - [Textarea Autosize](https://mui.com/base-ui/react-textarea-autosize/)
38 * - [Textarea Autosize](https://mui.com/material-ui/react-textarea-autosize/)
39 *
40 * API:
41 *
42 * - [TextareaAutosize API](https://mui.com/base-ui/react-textarea-autosize/components-api/#textarea-autosize)
43 */
44const TextareaAutosize = /*#__PURE__*/React.forwardRef(function TextareaAutosize(props, forwardedRef) {
45 const {
46 onChange,
47 maxRows,
48 minRows = 1,
49 style,
50 value
51 } = props,
52 other = _objectWithoutPropertiesLoose(props, _excluded);
53 const {
54 current: isControlled
55 } = React.useRef(value != null);
56 const inputRef = React.useRef(null);
57 const handleRef = useForkRef(forwardedRef, inputRef);
58 const shadowRef = React.useRef(null);
59 const calculateTextareaStyles = React.useCallback(() => {
60 const input = inputRef.current;
61 const containerWindow = ownerWindow(input);
62 const computedStyle = containerWindow.getComputedStyle(input);
63
64 // If input's width is shrunk and it's not visible, don't sync height.
65 if (computedStyle.width === '0px') {
66 return {
67 outerHeightStyle: 0,
68 overflowing: false
69 };
70 }
71 const inputShallow = shadowRef.current;
72 inputShallow.style.width = computedStyle.width;
73 inputShallow.value = input.value || props.placeholder || 'x';
74 if (inputShallow.value.slice(-1) === '\n') {
75 // Certain fonts which overflow the line height will cause the textarea
76 // to report a different scrollHeight depending on whether the last line
77 // is empty. Make it non-empty to avoid this issue.
78 inputShallow.value += ' ';
79 }
80 const boxSizing = computedStyle.boxSizing;
81 const padding = getStyleValue(computedStyle.paddingBottom) + getStyleValue(computedStyle.paddingTop);
82 const border = getStyleValue(computedStyle.borderBottomWidth) + getStyleValue(computedStyle.borderTopWidth);
83
84 // The height of the inner content
85 const innerHeight = inputShallow.scrollHeight;
86
87 // Measure height of a textarea with a single row
88 inputShallow.value = 'x';
89 const singleRowHeight = inputShallow.scrollHeight;
90
91 // The height of the outer content
92 let outerHeight = innerHeight;
93 if (minRows) {
94 outerHeight = Math.max(Number(minRows) * singleRowHeight, outerHeight);
95 }
96 if (maxRows) {
97 outerHeight = Math.min(Number(maxRows) * singleRowHeight, outerHeight);
98 }
99 outerHeight = Math.max(outerHeight, singleRowHeight);
100
101 // Take the box sizing into account for applying this value as a style.
102 const outerHeightStyle = outerHeight + (boxSizing === 'border-box' ? padding + border : 0);
103 const overflowing = Math.abs(outerHeight - innerHeight) <= 1;
104 return {
105 outerHeightStyle,
106 overflowing
107 };
108 }, [maxRows, minRows, props.placeholder]);
109 const syncHeight = React.useCallback(() => {
110 const textareaStyles = calculateTextareaStyles();
111 if (isEmpty(textareaStyles)) {
112 return;
113 }
114 const input = inputRef.current;
115 input.style.height = `${textareaStyles.outerHeightStyle}px`;
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", _extends({
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)), /*#__PURE__*/_jsx("textarea", {
172 "aria-hidden": true,
173 className: props.className,
174 readOnly: true,
175 ref: shadowRef,
176 tabIndex: -1,
177 style: _extends({}, styles.shadow, style, {
178 paddingTop: 0,
179 paddingBottom: 0
180 })
181 })]
182 });
183});
184process.env.NODE_ENV !== "production" ? TextareaAutosize.propTypes /* remove-proptypes */ = {
185 // ┌────────────────────────────── Warning ──────────────────────────────┐
186 // │ These PropTypes are generated from the TypeScript type definitions. │
187 // │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
188 // └─────────────────────────────────────────────────────────────────────┘
189 /**
190 * @ignore
191 */
192 className: PropTypes.string,
193 /**
194 * Maximum number of rows to display.
195 */
196 maxRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
197 /**
198 * Minimum number of rows to display.
199 * @default 1
200 */
201 minRows: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
202 /**
203 * @ignore
204 */
205 onChange: PropTypes.func,
206 /**
207 * @ignore
208 */
209 placeholder: PropTypes.string,
210 /**
211 * @ignore
212 */
213 style: PropTypes.object,
214 /**
215 * @ignore
216 */
217 value: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string), PropTypes.number, PropTypes.string])
218} : void 0;
219export { TextareaAutosize };
\No newline at end of file