diff --git a/Makefile b/Makefile index 8000fe2c..018f4677 100644 --- a/Makefile +++ b/Makefile @@ -7,7 +7,7 @@ TESTS = $(wildcard src/__tests__/*-test.js) BABEL_OPTS = \ --sourceMaps=inline -build: $(LIB) $(ES) +build: $(LIB) $(ES) build-min test:: @echo No tests... @@ -16,7 +16,7 @@ lint:: @$(BIN)/eslint $(SRC) clean: - @rm -f $(LIB) $(ES) + @rm -rf $(LIB) $(ES) sloc: @$(BIN)/sloc ./src @@ -36,6 +36,10 @@ publish: build @git push --tags origin HEAD:master @npm publish +build-min: $(LIB) + @echo "building dist/TextareaAutosize.min.js" + @$(BIN)/cross-env BABEL_ENV=es NODE_ENV=production $(BIN)/rollup -c + lib/%.js: src/%.js @echo "building cjs $@" @mkdir -p $(@D) diff --git a/dist/TextareaAutosize.min.js b/dist/TextareaAutosize.min.js new file mode 100644 index 00000000..2e53d188 --- /dev/null +++ b/dist/TextareaAutosize.min.js @@ -0,0 +1,550 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory(require('react'), require('prop-types')) : + typeof define === 'function' && define.amd ? define(['react', 'prop-types'], factory) : + (global.TextareaAutosize = factory(global.React,global.PropTypes)); +}(this, (function (React,PropTypes) { 'use strict'; + +React = 'default' in React ? React['default'] : React; +PropTypes = 'default' in PropTypes ? PropTypes['default'] : PropTypes; + +/** + * calculateNodeHeight(uiTextNode, useCache = false) + */ + +var HIDDEN_TEXTAREA_STYLE = { + 'min-height': '0', + 'max-height': 'none', + 'height': '0', + 'visibility': 'hidden', + 'overflow': 'hidden', + 'position': 'absolute', + 'z-index': '-1000', + 'top': '0', + 'right': '0' +}; + +var SIZING_STYLE = ['letter-spacing', 'line-height', 'padding-top', 'padding-bottom', 'font-family', 'font-weight', 'font-size', 'text-rendering', 'text-transform', 'width', 'text-indent', 'padding-left', 'padding-right', 'border-width', 'box-sizing']; + +var computedStyleCache = {}; +var hiddenTextarea = void 0; + +function calculateNodeHeight(uiTextNode) { + var useCache = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + var minRows = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null; + var maxRows = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null; + + if (!hiddenTextarea) { + hiddenTextarea = document.createElement('textarea'); + document.body.appendChild(hiddenTextarea); + } else if (hiddenTextarea.parentNode === null) { + document.body.appendChild(hiddenTextarea); + } + + // Copy all CSS properties that have an impact on the height of the content in + // the textbox + + var _calculateNodeStyling = calculateNodeStyling(uiTextNode, useCache), + paddingSize = _calculateNodeStyling.paddingSize, + borderSize = _calculateNodeStyling.borderSize, + boxSizing = _calculateNodeStyling.boxSizing, + sizingStyle = _calculateNodeStyling.sizingStyle; + + // Need to have the overflow attribute to hide the scrollbar otherwise + // text-lines will not calculated properly as the shadow will technically be + // narrower for content + + + Object.keys(sizingStyle).map(function (key) { + hiddenTextarea.style[key] = sizingStyle[key]; + }); + Object.keys(HIDDEN_TEXTAREA_STYLE).map(function (key) { + hiddenTextarea.style.setProperty(key, HIDDEN_TEXTAREA_STYLE[key], 'important'); + }); + hiddenTextarea.value = uiTextNode.value || uiTextNode.placeholder || 'x'; + + var minHeight = -Infinity; + var maxHeight = Infinity; + var height = hiddenTextarea.scrollHeight; + + if (boxSizing === 'border-box') { + // border-box: add border, since height = content + padding + border + height = height + borderSize; + } else if (boxSizing === 'content-box') { + // remove padding, since height = content + height = height - paddingSize; + } + + if (minRows !== null || maxRows !== null) { + // measure height of a textarea with a single row + hiddenTextarea.value = 'x'; + var singleRowHeight = hiddenTextarea.scrollHeight - paddingSize; + if (minRows !== null) { + minHeight = singleRowHeight * minRows; + if (boxSizing === 'border-box') { + minHeight = minHeight + paddingSize + borderSize; + } + height = Math.max(minHeight, height); + } + if (maxRows !== null) { + maxHeight = singleRowHeight * maxRows; + if (boxSizing === 'border-box') { + maxHeight = maxHeight + paddingSize + borderSize; + } + height = Math.min(maxHeight, height); + } + } + return { height: height, minHeight: minHeight, maxHeight: maxHeight }; +} + +function calculateNodeStyling(node) { + var useCache = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + var nodeRef = node.getAttribute('id') || node.getAttribute('data-reactid') || node.getAttribute('name'); + + if (useCache && computedStyleCache[nodeRef]) { + return computedStyleCache[nodeRef]; + } + + var style = window.getComputedStyle(node); + + var boxSizing = style.getPropertyValue('box-sizing') || style.getPropertyValue('-moz-box-sizing') || style.getPropertyValue('-webkit-box-sizing'); + + var paddingSize = parseFloat(style.getPropertyValue('padding-bottom')) + parseFloat(style.getPropertyValue('padding-top')); + + var borderSize = parseFloat(style.getPropertyValue('border-bottom-width')) + parseFloat(style.getPropertyValue('border-top-width')); + + var sizingStyle = SIZING_STYLE.reduce(function (obj, name) { + obj[name] = style.getPropertyValue(name); + return obj; + }, {}); + + var nodeInfo = { + sizingStyle: sizingStyle, + paddingSize: paddingSize, + borderSize: borderSize, + boxSizing: boxSizing + }; + + if (useCache && nodeRef) { + computedStyleCache[nodeRef] = nodeInfo; + } + + return nodeInfo; +} + +var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { + return typeof obj; +} : function (obj) { + return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; +}; + + + + + + + + + + + +var classCallCheck = function (instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } +}; + +var createClass = function () { + function defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, descriptor.key, descriptor); + } + } + + return function (Constructor, protoProps, staticProps) { + if (protoProps) defineProperties(Constructor.prototype, protoProps); + if (staticProps) defineProperties(Constructor, staticProps); + return Constructor; + }; +}(); + + + + + + + +var _extends = Object.assign || function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + + return target; +}; + +var get$1 = function get$1(object, property, receiver) { + if (object === null) object = Function.prototype; + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent === null) { + return undefined; + } else { + return get$1(parent, property, receiver); + } + } else if ("value" in desc) { + return desc.value; + } else { + var getter = desc.get; + + if (getter === undefined) { + return undefined; + } + + return getter.call(receiver); + } +}; + +var inherits = function (subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); + } + + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + enumerable: false, + writable: true, + configurable: true + } + }); + if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; +}; + + + + + + + + + +var objectWithoutProperties = function (obj, keys) { + var target = {}; + + for (var i in obj) { + if (keys.indexOf(i) >= 0) continue; + if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; + target[i] = obj[i]; + } + + return target; +}; + +var possibleConstructorReturn = function (self, call) { + if (!self) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + + return call && (typeof call === "object" || typeof call === "function") ? call : self; +}; + + + +var set$1 = function set$1(object, property, value, receiver) { + var desc = Object.getOwnPropertyDescriptor(object, property); + + if (desc === undefined) { + var parent = Object.getPrototypeOf(object); + + if (parent !== null) { + set$1(parent, property, value, receiver); + } + } else if ("value" in desc && desc.writable) { + desc.value = value; + } else { + var setter = desc.set; + + if (setter !== undefined) { + setter.call(receiver, value); + } + } + + return value; +}; + +/** + * + */ + +var noop = function noop() {}; + +var TextareaAutosize = function (_React$Component) { + inherits(TextareaAutosize, _React$Component); + + function TextareaAutosize(props) { + classCallCheck(this, TextareaAutosize); + + var _this = possibleConstructorReturn(this, _React$Component.call(this, props)); + + _this._onRootDOMNode = function (node) { + _this._rootDOMNode = node; + if (_this.props.inputRef) _this.props.inputRef(node); + }; + + _this._onChange = function (event) { + if (!_this._controlled) { + _this._resizeComponent(); + } + var _this$props = _this.props, + valueLink = _this$props.valueLink, + onChange = _this$props.onChange; + + if (valueLink) { + valueLink.requestChange(event.target.value); + } else { + onChange(event); + } + }; + + _this._resizeComponent = function () { + if (!_this._rootDOMNode) { + return; + } + + var _calculateNodeHeight = calculateNodeHeight(_this._rootDOMNode, _this.props.useCacheForDOMMeasurements, _this.props.rows || _this.props.minRows, _this.props.maxRows), + height = _calculateNodeHeight.height, + minHeight = _calculateNodeHeight.minHeight, + maxHeight = _calculateNodeHeight.maxHeight; + + if (_this.state.height !== height || _this.state.minHeight !== minHeight || _this.state.maxHeight !== maxHeight) { + _this.setState({ height: height, minHeight: minHeight, maxHeight: maxHeight }); + } + }; + + _this.state = { + height: props.style && props.style.height || 0, + minHeight: -Infinity, + maxHeight: Infinity + }; + + _this._controlled = typeof props.value === 'string'; + return _this; + } + + TextareaAutosize.prototype.render = function render() { + var _props = this.props, + valueLink = _props.valueLink, + _minRows = _props.minRows, + _maxRows = _props.maxRows, + _onHeightChange = _props.onHeightChange, + _useCacheForDOMMeasurements = _props.useCacheForDOMMeasurements, + _inputRef = _props.inputRef, + props = objectWithoutProperties(_props, ['valueLink', 'minRows', 'maxRows', 'onHeightChange', 'useCacheForDOMMeasurements', 'inputRef']); + + + if ((typeof valueLink === 'undefined' ? 'undefined' : _typeof(valueLink)) === 'object') { + props.value = valueLink.value; + } + + props.style = _extends({}, props.style, { + height: this.state.height + }); + + var maxHeight = Math.max(props.style.maxHeight || Infinity, this.state.maxHeight); + + if (maxHeight < this.state.height) { + props.style.overflow = 'hidden'; + } + + return React.createElement('textarea', _extends({}, props, { + onChange: this._onChange, + ref: this._onRootDOMNode + })); + }; + + TextareaAutosize.prototype.componentDidMount = function componentDidMount() { + this._resizeComponent(); + window.addEventListener('resize', this._resizeComponent); + }; + + TextareaAutosize.prototype.componentWillReceiveProps = function componentWillReceiveProps() { + // Re-render with the new content then recalculate the height as required. + this._clearNextFrame(); + this._onNextFrameActionId = onNextFrame(this._resizeComponent); + }; + + TextareaAutosize.prototype.componentDidUpdate = function componentDidUpdate(prevProps, prevState) { + // Invoke callback when old height does not equal to new one. + if (this.state.height !== prevState.height) { + this.props.onHeightChange(this.state.height); + } + }; + + TextareaAutosize.prototype.componentWillUnmount = function componentWillUnmount() { + // Remove any scheduled events to prevent manipulating the node after it's + // been unmounted. + this._clearNextFrame(); + window.removeEventListener('resize', this._resizeComponent); + }; + + TextareaAutosize.prototype._clearNextFrame = function _clearNextFrame() { + if (this._onNextFrameActionId) { + clearNextFrameAction(this._onNextFrameActionId); + } + }; + + /** + * Put focus on a