Skip to content

Instantly share code, notes, and snippets.

@eugene1g
Last active January 12, 2017 02:29
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save eugene1g/5dbaa7d35d0c7d5c2c56 to your computer and use it in GitHub Desktop.
Save eugene1g/5dbaa7d35d0c7d5c2c56 to your computer and use it in GitHub Desktop.
React component for auto-expanding textareas
"use strict";
import React, {Component} from 'react';
import calcContentHeight from './dom-textarea-measure';
class AutogrowingTextarea extends Component {
constructor(props) {
this.props = props;
this.state = {};
this.textareaProps = sanitizeChildProps(props);
}
render() {
this.textareaProps.style.height = this.state.height;
return <textarea {...this.textareaProps} onChange={this.onChange.bind(this)}/>
}
componentDidMount() {
this.resizeComponent();
}
/**
* Update textrarea props in case this component is updated from the outside
*/
componentWillReceiveProps(nextProps) {
this.textareaProps = sanitizeChildProps(nextProps);
this.resizeComponent();
}
onChange(e) {
this.resizeComponent();
var changeCallback = this.props.onChange,
valueLink = this.props.valueLink;
if (typeof valueLink == 'object' && typeof valueLink.requestChange == 'function') {
changeCallback = ev => valueLink.requestChange(ev.target.value);
}
if (changeCallback) changeCallback(e);
}
resizeComponent() {
var height = calcContentHeight(React.findDOMNode(this), this.textareaProps.value || this.textareaProps.defaultValue);
if (this.props.minHeight && this.props.minHeight > height) {
height = this.props.minHeight;
}
if (this.props.maxHeight && this.props.maxHeight < height) {
height = this.props.maxHeight;
}
this.setState({height});
}
getValue() {
return React.findDOMNode(this).value;
}
focus() {
React.findDOMNode(this).focus();
}
}
export default AutogrowingTextarea;
AutogrowingTextarea.displayName = 'TextareaAutosize';
AutogrowingTextarea.propTypes = {
minHeight: React.PropTypes.number,
maxHeight: React.PropTypes.number
};
AutogrowingTextarea.defaultProps = {
minHeight: 40
};
/**
* Removes propertie that are relevant only for this wrapper and should not be passed down to the actual textarea
*/
function sanitizeChildProps(allProps) {
var childProps = objectWithout(allProps, ['valueLink', 'onChange', 'minHeight', 'maxHeight']);
if (typeof allProps.valueLink == 'object') childProps.value = allProps.valueLink.value;
if (typeof childProps.style != 'object') childProps.style = {};
//required to hide overflow so that there is no scrollbar
childProps.style.overflow = 'hidden';
return childProps;
}
function objectWithout(object, fieldNames) {
var copy = {};
for (let field in object) {
if (fieldNames.indexOf(field) === -1) copy[field] = object[field];
}
return copy;
}
"use strict";
var hiddenTextarea,
computedStyleCache = {},
hiddenTextareaStyle = "height:0;visibility:hidden;overflow:hidden;position:absolute;z-index:-1000;top:0;right:0";
export default function (uiTextNode, newContent) {
if (!hiddenTextarea) {
hiddenTextarea = document.createElement('textarea');
document.body.appendChild(hiddenTextarea);
}
var {baseUiStyling, sumVerticalPaddings} = calcNodeStyling(uiTextNode);
hiddenTextarea.setAttribute('style', baseUiStyling + ";" + hiddenTextareaStyle);
hiddenTextarea.value = typeof newContent == "string" ? newContent : uiTextNode.value;
return (hiddenTextarea.scrollHeight - sumVerticalPaddings);
}
function calcNodeStyling(node) {
const nodeRef = node.getAttribute('data-reactid') || node.id || node.name;
if (!computedStyleCache[nodeRef]) {
let compStyle = window.getComputedStyle(node),
sumPaddings = 0,
stylesToCopy = ['line-height', 'padding-top', 'padding-bottom', 'font-size', 'width', 'padding-left', 'padding-right'];
// If the textarea is set to border-box, it's not necessary to
// subtract the padding.
if (compStyle.getPropertyValue('box-sizing') !== "border-box" &&
compStyle.getPropertyValue('-moz-box-sizing') !== "border-box" &&
compStyle.getPropertyValue('-webkit-box-sizing') !== "border-box") {
sumPaddings = (
parseFloat(compStyle.getPropertyValue('padding-bottom')) +
parseFloat(compStyle.getPropertyValue('padding-top'))
);
}
computedStyleCache[nodeRef] = {
baseUiStyling: stylesToCopy.map(styleName => styleName + ':' + compStyle[styleName]).join(';'),
sumVerticalPaddings: sumPaddings
}
}
return computedStyleCache[nodeRef];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment