2020-04-09 00:29:13 +03:00
|
|
|
import * as React from "react";
|
|
|
|
|
2022-01-02 18:28:44 +03:00
|
|
|
import calculateNodeHeight, { purgeCache } from "~/vendor/calculate-node-height";
|
|
|
|
import { css } from "@emotion/react";
|
2020-04-09 00:29:13 +03:00
|
|
|
|
|
|
|
const noop = () => {};
|
|
|
|
|
2022-01-02 18:28:44 +03:00
|
|
|
const STYLES_TEXTAREA = css`
|
|
|
|
outline: 0;
|
|
|
|
`;
|
|
|
|
|
2020-04-09 00:29:13 +03:00
|
|
|
let uid = 0;
|
|
|
|
|
|
|
|
export default class TextareaAutosize extends React.Component {
|
|
|
|
static defaultProps = {
|
|
|
|
inputRef: noop,
|
|
|
|
onChange: noop,
|
|
|
|
onHeightChange: noop,
|
|
|
|
useCacheForDOMMeasurements: false,
|
|
|
|
};
|
|
|
|
|
|
|
|
constructor(props) {
|
|
|
|
super(props);
|
|
|
|
this.state = {
|
|
|
|
height: (props.style && props.style.height) || 0,
|
|
|
|
minHeight: -Infinity,
|
|
|
|
maxHeight: Infinity,
|
|
|
|
};
|
|
|
|
|
|
|
|
this._uid = uid++;
|
|
|
|
this._controlled = props.value !== undefined;
|
|
|
|
this._resizeLock = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
render() {
|
|
|
|
const {
|
|
|
|
inputRef,
|
|
|
|
maxRows,
|
|
|
|
minRows,
|
|
|
|
onHeightChange,
|
|
|
|
useCacheForDOMMeasurements,
|
2022-01-02 18:28:44 +03:00
|
|
|
css,
|
2020-04-09 00:29:13 +03:00
|
|
|
...props
|
|
|
|
} = this.props;
|
|
|
|
|
|
|
|
props.style = { ...props.style, height: this.state.height };
|
|
|
|
|
2022-01-02 18:28:44 +03:00
|
|
|
const maxHeight = Math.max(props.style.maxHeight || Infinity, this.state.maxHeight);
|
2020-04-09 00:29:13 +03:00
|
|
|
|
|
|
|
if (maxHeight < this.state.height) {
|
|
|
|
props.style.overflow = "hidden";
|
|
|
|
}
|
|
|
|
|
2022-01-02 18:28:44 +03:00
|
|
|
return (
|
2022-01-20 19:48:09 +03:00
|
|
|
<textarea
|
|
|
|
{...props}
|
|
|
|
css={[STYLES_TEXTAREA, css]}
|
|
|
|
onChange={this._onChange}
|
|
|
|
ref={this._onRef}
|
|
|
|
/>
|
2022-01-02 18:28:44 +03:00
|
|
|
);
|
2020-04-09 00:29:13 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
this._resizeComponent();
|
|
|
|
// Working around Firefox bug which runs resize listeners even when other JS is running at the same moment
|
|
|
|
// causing competing rerenders (due to setState in the listener) in React.
|
|
|
|
// More can be found here - facebook/react#6324
|
|
|
|
this._resizeListener = () => {
|
|
|
|
if (this._resizeLock) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
this._resizeLock = true;
|
|
|
|
this._resizeComponent(() => {
|
|
|
|
this._resizeLock = false;
|
|
|
|
});
|
|
|
|
};
|
|
|
|
window.addEventListener("resize", this._resizeListener);
|
|
|
|
}
|
|
|
|
|
|
|
|
componentDidUpdate(prevProps, prevState) {
|
|
|
|
if (prevProps !== this.props) {
|
|
|
|
this._resizeComponent();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (this.state.height !== prevState.height) {
|
|
|
|
this.props.onHeightChange(this.state.height, this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
window.removeEventListener("resize", this._resizeListener);
|
|
|
|
purgeCache(this._uid);
|
|
|
|
}
|
|
|
|
|
|
|
|
_onRef = (node) => {
|
|
|
|
this._ref = node;
|
|
|
|
const { inputRef } = this.props;
|
|
|
|
|
|
|
|
if (typeof inputRef === "function") {
|
|
|
|
inputRef(node);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
inputRef.current = node;
|
|
|
|
};
|
|
|
|
|
|
|
|
_onChange = (event) => {
|
|
|
|
if (!this._controlled) {
|
|
|
|
this._resizeComponent();
|
|
|
|
}
|
|
|
|
this.props.onChange(event, this);
|
|
|
|
};
|
|
|
|
|
|
|
|
_resizeComponent = (callback = noop) => {
|
|
|
|
if (!process.env.BROWSER && !this._ref) {
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const nodeHeight = calculateNodeHeight(
|
|
|
|
this._ref,
|
|
|
|
this._uid,
|
|
|
|
this.props.useCacheForDOMMeasurements,
|
|
|
|
this.props.minRows,
|
|
|
|
this.props.maxRows
|
|
|
|
);
|
|
|
|
|
|
|
|
if (nodeHeight === null) {
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-01-02 18:28:44 +03:00
|
|
|
const { height, minHeight, maxHeight, rowCount, valueRowCount } = nodeHeight;
|
2020-04-09 00:29:13 +03:00
|
|
|
|
|
|
|
this.rowCount = rowCount;
|
|
|
|
this.valueRowCount = valueRowCount;
|
|
|
|
|
|
|
|
if (
|
|
|
|
this.state.height !== height ||
|
|
|
|
this.state.minHeight !== minHeight ||
|
|
|
|
this.state.maxHeight !== maxHeight
|
|
|
|
) {
|
|
|
|
this.setState({ height, minHeight, maxHeight }, callback);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
callback();
|
|
|
|
};
|
|
|
|
}
|