UNPKG

4.2 kBTypeScriptView Raw
1import * as React from "react";
2import * as PropTypes from "prop-types";
3import * as autosize from "autosize";
4import * as _getLineHeight from "line-height";
5
6const getLineHeight = _getLineHeight as (element: HTMLElement) => number | null;
7
8export namespace TextareaAutosize {
9 export type RequiredProps = Pick<
10 React.HTMLProps<HTMLTextAreaElement>,
11 Exclude<keyof React.HTMLProps<HTMLTextAreaElement>, "ref">
12 > & {
13 /** Called whenever the textarea resizes */
14 onResize?: (e: Event) => void;
15 /** Minimum number of visible rows */
16 rows?: React.HTMLProps<HTMLTextAreaElement>["rows"];
17 /** Maximum number of visible rows */
18 maxRows?: number;
19 /** Initialize `autosize` asynchronously.
20 * Enable it if you are using StyledComponents
21 * This is forced to true when `maxRows` is set.
22 */
23 async?: boolean;
24 };
25 export type DefaultProps = {
26 rows: number;
27 async: boolean;
28 };
29 export type Props = RequiredProps & Partial<DefaultProps>;
30 export type State = {
31 lineHeight: number | null;
32 };
33}
34
35const RESIZED = "autosize:resized";
36
37type InnerProps = TextareaAutosize.Props & {
38 innerRef: React.Ref<HTMLTextAreaElement> | null;
39};
40
41/**
42 * A light replacement for built-in textarea component
43 * which automaticaly adjusts its height to match the content
44 */
45class TextareaAutosizeClass extends React.Component<
46 InnerProps,
47 TextareaAutosize.State
48> {
49 static defaultProps: TextareaAutosize.DefaultProps = {
50 rows: 1,
51 async: false
52 };
53
54 static propTypes: {
55 [key in keyof InnerProps]: PropTypes.Requireable<any>
56 } = {
57 rows: PropTypes.number,
58 maxRows: PropTypes.number,
59 onResize: PropTypes.func,
60 innerRef: PropTypes.any,
61 async: PropTypes.bool
62 };
63
64 state: TextareaAutosize.State = {
65 lineHeight: null
66 };
67
68 textarea: HTMLTextAreaElement | null = null;
69 currentValue: InnerProps["value"];
70
71 onResize = (e: Event): void => {
72 if (this.props.onResize) {
73 this.props.onResize(e);
74 }
75 };
76
77 componentDidMount() {
78 const { maxRows, async } = this.props;
79
80 if (typeof maxRows === "number") {
81 this.updateLineHeight();
82 }
83
84 if (typeof maxRows === "number" || async) {
85 /*
86 the defer is needed to:
87 - force "autosize" to activate the scrollbar when this.props.maxRows is passed
88 - support StyledComponents (see #71)
89 */
90 setTimeout(
91 () => this.textarea && autosize(this.textarea)
92 );
93 } else {
94 this.textarea && autosize(this.textarea);
95 }
96
97 if (this.textarea) {
98 this.textarea.addEventListener(RESIZED, this.onResize);
99 }
100 }
101
102 componentWillUnmount() {
103 if (this.textarea) {
104 this.textarea.removeEventListener(RESIZED, this.onResize);
105 autosize.destroy(this.textarea);
106 }
107 }
108
109 updateLineHeight = () => {
110 if (this.textarea) {
111 this.setState({
112 lineHeight: getLineHeight(this.textarea)
113 });
114 }
115 };
116
117 onChange = (e: React.SyntheticEvent<HTMLTextAreaElement>) => {
118 const { onChange } = this.props;
119 this.currentValue = e.currentTarget.value;
120 onChange && onChange(e);
121 };
122
123 render() {
124 const {
125 props: {
126 onResize,
127 maxRows,
128 onChange,
129 style,
130 innerRef,
131 children,
132 ...props
133 },
134 state: { lineHeight }
135 } = this;
136
137 const maxHeight = maxRows && lineHeight ? lineHeight * maxRows : null;
138
139 return (
140 <textarea
141 {...props}
142 onChange={this.onChange}
143 style={maxHeight ? { ...style, maxHeight } : style}
144 ref={element => {
145 this.textarea = element
146 if (typeof this.props.innerRef === 'function') {
147 this.props.innerRef(element)
148 } else if (this.props.innerRef) {
149 (this.props.innerRef as any).current = element
150 }
151 }}
152 >
153 {children}
154 </textarea>
155 );
156 }
157
158 componentDidUpdate() {
159 this.textarea && autosize.update(this.textarea);
160 }
161}
162
163export const TextareaAutosize = React.forwardRef(
164 (
165 props: TextareaAutosize.Props,
166 ref: React.Ref<HTMLTextAreaElement> | null
167 ) => {
168 return <TextareaAutosizeClass {...props} innerRef={ref} />;
169 }
170);