slate/vendor/react-textarea-autosize.js
2022-01-05 14:07:45 +01:00

154 lines
3.4 KiB
JavaScript

import * as React from "react";
import calculateNodeHeight, { purgeCache } from "~/vendor/calculate-node-height";
import { FocusRing } from "~/components/core/FocusRing";
import { css } from "@emotion/react";
const noop = () => {};
const STYLES_TEXTAREA = css`
outline: 0;
`;
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,
css,
...props
} = this.props;
props.style = { ...props.style, height: this.state.height };
const maxHeight = Math.max(props.style.maxHeight || Infinity, this.state.maxHeight);
if (maxHeight < this.state.height) {
props.style.overflow = "hidden";
}
return (
<FocusRing>
<textarea
{...props}
css={[STYLES_TEXTAREA, css]}
onChange={this._onChange}
ref={this._onRef}
/>
</FocusRing>
);
}
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;
}
const { height, minHeight, maxHeight, rowCount, valueRowCount } = nodeHeight;
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();
};
}