1 | import * as React from "react";
|
2 | import * as PropTypes from "prop-types";
|
3 | import * as autosize from "autosize";
|
4 | import * as _getLineHeight from "line-height";
|
5 |
|
6 | const getLineHeight = _getLineHeight as (element: HTMLElement) => number | null;
|
7 |
|
8 | export namespace TextareaAutosize {
|
9 | export type RequiredProps = Pick<
|
10 | React.HTMLProps<HTMLTextAreaElement>,
|
11 | Exclude<keyof React.HTMLProps<HTMLTextAreaElement>, "ref">
|
12 | > & {
|
13 |
|
14 | onResize?: (e: Event) => void;
|
15 |
|
16 | rows?: React.HTMLProps<HTMLTextAreaElement>["rows"];
|
17 |
|
18 | maxRows?: number;
|
19 | |
20 |
|
21 |
|
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 |
|
35 | const RESIZED = "autosize:resized";
|
36 |
|
37 | type InnerProps = TextareaAutosize.Props & {
|
38 | innerRef: React.Ref<HTMLTextAreaElement> | null;
|
39 | };
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 | class 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 |
|
87 |
|
88 |
|
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 |
|
163 | export const TextareaAutosize = React.forwardRef(
|
164 | (
|
165 | props: TextareaAutosize.Props,
|
166 | ref: React.Ref<HTMLTextAreaElement> | null
|
167 | ) => {
|
168 | return <TextareaAutosizeClass {...props} innerRef={ref} />;
|
169 | }
|
170 | );
|