1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
|
import React from 'react';
import autosize from 'autosize';
import { t, props } from 'tcomb-react';
const UPDATE = 'autosize:update',
DESTROY = 'autosize:destroy',
RESIZED = 'autosize:resized';
export const Props = {
rows: t.maybe(t.Integer),
maxRows: t.maybe(t.Integer),
onResize: t.maybe(t.Function)
};
/** A light replacement for built-in textarea component
* which automaticaly adjusts its height to match the content
* @param onResize - called whenever the textarea resizes
* @param rows - minimum number of visible rows
* @param maxRows - maximum number of visible rows
*/
@props(Props, { strict: false })
export default class TextareaAutosize extends React.Component {
static defaultProps = {
rows: 1,
maxRows: 5
};
state = {
maxHeight: null
}
componentDidMount() {
autosize(this.textarea);
if (this.props.onResize) {
this.textarea.addEventListener(RESIZED, this.props.onResize);
}
}
componentWillUnmount() {
if (this.props.onResize) {
this.textarea.removeEventListener(RESIZED, this.props.onResize);
}
this.dispatchEvent(DESTROY);
}
dispatchEvent = (EVENT_TYPE) => {
const event = document.createEvent('Event');
event.initEvent(EVENT_TYPE, true, false);
this.textarea.dispatchEvent(event);
};
getValue = ({ valueLink, value }) => valueLink ? valueLink.value : value;
onChange = e => {
const {
props: { maxRows, onChange },
state: { maxHeight }
} = this;
const numberOfRows = e.target.value.split('\n').length;
if (!maxHeight && numberOfRows >= maxRows) {
const computedStyle = window.getComputedStyle(this.textarea);
const paddingTop = parseFloat(computedStyle.getPropertyValue('padding-top'), 10);
const paddingBottom = parseFloat(computedStyle.getPropertyValue('padding-top'), 10);
const verticalPadding = (paddingTop || 0) + (paddingBottom || 0);
const borderTopWidth = parseInt(computedStyle.getPropertyValue('border-top-width'), 10);
const borderBottomWidth = parseInt(computedStyle.getPropertyValue('border-bottom-width'), 10);
const verticalBorderWidth = (borderTopWidth || 0) + (borderBottomWidth || 0);
this.setState({
maxHeight: this.textarea.offsetHeight - verticalPadding - verticalBorderWidth
});
} else if (maxHeight && numberOfRows < maxRows) {
this.setState({ maxHeight: null });
}
onChange && onChange(e);
}
getLocals = () => {
const {
props: { onResize, maxRows, onChange, style, ...props }, // eslint-disable-line no-unused-vars
state: { maxHeight }
} = this;
return {
...props,
style: maxHeight ? { ...style, maxHeight } : style,
onChange: this.onChange
};
}
render() {
const { children, ...locals } = this.getLocals();
return (
<textarea {...locals} ref={(ref) => { this.textarea = ref; }}>
{children}
</textarea>
);
}
componentDidUpdate(prevProps) {
if (this.getValue(prevProps) !== this.getValue(this.props)) {
this.dispatchEvent(UPDATE);
}
}
}
|