-/*!
- Autosize v1.18.1 - 2013-11-05
- Automatically adjust textarea height based on user input.
- (c) 2013 Jack Moore - http://www.jacklmoore.com/autosize
- license: http://www.opensource.org/licenses/mit-license.php
-*/
-(function ($) {
- var
- defaults = {
- className: 'autosizejs',
- append: '',
- callback: false,
- resizeDelay: 10
- },
-
- // border:0 is unnecessary, but avoids a bug in Firefox on OSX
- copy = '<textarea tabindex="-1" style="position:absolute; top:-999px; left:0; right:auto; bottom:auto; border:0; padding: 0; -moz-box-sizing:content-box; -webkit-box-sizing:content-box; box-sizing:content-box; word-wrap:break-word; height:0 !important; min-height:0 !important; overflow:hidden; transition:none; -webkit-transition:none; -moz-transition:none;"/>',
-
- // line-height is conditionally included because IE7/IE8/old Opera do not return the correct value.
- typographyStyles = [
- 'fontFamily',
- 'fontSize',
- 'fontWeight',
- 'fontStyle',
- 'letterSpacing',
- 'textTransform',
- 'wordSpacing',
- 'textIndent'
- ],
-
- // to keep track which textarea is being mirrored when adjust() is called.
- mirrored,
-
- // the mirror element, which is used to calculate what size the mirrored element should be.
- mirror = $(copy).data('autosize', true)[0];
-
- // test that line-height can be accurately copied.
- mirror.style.lineHeight = '99px';
- if ($(mirror).css('lineHeight') === '99px') {
- typographyStyles.push('lineHeight');
+const map = (typeof Map === "function") ? new Map() : (function () {
+ const keys = [];
+ const values = [];
+
+ return {
+ has(key) {
+ return keys.indexOf(key) > -1;
+ },
+ get(key) {
+ return values[keys.indexOf(key)];
+ },
+ set(key, value) {
+ if (keys.indexOf(key) === -1) {
+ keys.push(key);
+ values.push(value);
+ }
+ },
+ delete(key) {
+ const index = keys.indexOf(key);
+ if (index > -1) {
+ keys.splice(index, 1);
+ values.splice(index, 1);
+ }
+ },
}
- mirror.style.lineHeight = '';
+})();
+
+let createEvent = (name)=> new Event(name, {bubbles: true});
+try {
+ new Event('test');
+} catch(e) {
+ // IE does not support `new Event()`
+ createEvent = (name)=> {
+ const evt = document.createEvent('Event');
+ evt.initEvent(name, true, false);
+ return evt;
+ };
+}
+
+function assign(ta) {
+ if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || map.has(ta)) return;
- $.fn.autosize = function (options) {
- if (!this.length) {
- return this;
+ let heightOffset = null;
+ let clientWidth = null;
+ let cachedHeight = null;
+
+ function init() {
+ const style = window.getComputedStyle(ta, null);
+
+ if (style.resize === 'vertical') {
+ ta.style.resize = 'none';
+ } else if (style.resize === 'both') {
+ ta.style.resize = 'horizontal';
}
- options = $.extend({}, defaults, options || {});
+ if (style.boxSizing === 'content-box') {
+ heightOffset = -(parseFloat(style.paddingTop)+parseFloat(style.paddingBottom));
+ } else {
+ heightOffset = parseFloat(style.borderTopWidth)+parseFloat(style.borderBottomWidth);
+ }
+ // Fix when a textarea is not on document body and heightOffset is Not a Number
+ if (isNaN(heightOffset)) {
+ heightOffset = 0;
+ }
+
+ update();
+ }
- if (mirror.parentNode !== document.body) {
- $(document.body).append(mirror);
+ function changeOverflow(value) {
+ {
+ // Chrome/Safari-specific fix:
+ // When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space
+ // made available by removing the scrollbar. The following forces the necessary text reflow.
+ const width = ta.style.width;
+ ta.style.width = '0px';
+ // Force reflow:
+ /* jshint ignore:start */
+ ta.offsetWidth;
+ /* jshint ignore:end */
+ ta.style.width = width;
}
- return this.each(function () {
- var
- ta = this,
- $ta = $(ta),
- maxHeight,
- minHeight,
- boxOffset = 0,
- callback = $.isFunction(options.callback),
- originalStyles = {
- height: ta.style.height,
- overflow: ta.style.overflow,
- overflowY: ta.style.overflowY,
- wordWrap: ta.style.wordWrap,
- resize: ta.style.resize
- },
- timeout,
- width = $ta.width();
-
- if ($ta.data('autosize')) {
- // exit if autosize has already been applied, or if the textarea is the mirror element.
- return;
- }
- $ta.data('autosize', true);
+ ta.style.overflowY = value;
+ }
- if ($ta.css('box-sizing') === 'border-box' || $ta.css('-moz-box-sizing') === 'border-box' || $ta.css('-webkit-box-sizing') === 'border-box'){
- boxOffset = $ta.outerHeight() - $ta.height();
- }
+ function getParentOverflows(el) {
+ const arr = [];
- // IE8 and lower return 'auto', which parses to NaN, if no min-height is set.
- minHeight = Math.max(parseInt($ta.css('minHeight'), 10) - boxOffset || 0, $ta.height());
-
- $ta.css({
- overflow: 'hidden',
- overflowY: 'hidden',
- wordWrap: 'break-word', // horizontal overflow is hidden, so break-word is necessary for handling words longer than the textarea width
- resize: ($ta.css('resize') === 'none' || $ta.css('resize') === 'vertical') ? 'none' : 'horizontal'
- });
-
- // The mirror width must exactly match the textarea width, so using getBoundingClientRect because it doesn't round the sub-pixel value.
- function setWidth() {
- var style, width;
-
- if ('getComputedStyle' in window) {
- style = window.getComputedStyle(ta, null);
- width = ta.getBoundingClientRect().width;
-
- $.each(['paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth'], function(i,val){
- width -= parseInt(style[val],10);
- });
-
- mirror.style.width = width + 'px';
- }
- else {
- // window.getComputedStyle, getBoundingClientRect returning a width are unsupported and unneeded in IE8 and lower.
- mirror.style.width = Math.max($ta.width(), 0) + 'px';
- }
+ while (el && el.parentNode && el.parentNode instanceof Element) {
+ if (el.parentNode.scrollTop) {
+ arr.push({
+ node: el.parentNode,
+ scrollTop: el.parentNode.scrollTop,
+ })
}
+ el = el.parentNode;
+ }
- function initMirror() {
- var styles = {};
-
- mirrored = ta;
- mirror.className = options.className;
- maxHeight = parseInt($ta.css('maxHeight'), 10);
-
- // mirror is a duplicate textarea located off-screen that
- // is automatically updated to contain the same text as the
- // original textarea. mirror always has a height of 0.
- // This gives a cross-browser supported way getting the actual
- // height of the text, through the scrollTop property.
- $.each(typographyStyles, function(i,val){
- styles[val] = $ta.css(val);
- });
- $(mirror).css(styles);
-
- setWidth();
-
- // Chrome-specific fix:
- // When the textarea y-overflow is hidden, Chrome doesn't reflow the text to account for the space
- // made available by removing the scrollbar. This workaround triggers the reflow for Chrome.
- if (window.chrome) {
- var width = ta.style.width;
- ta.style.width = '0px';
- var ignore = ta.offsetWidth;
- ta.style.width = width;
- }
- }
+ return arr;
+ }
- // Using mainly bare JS in this function because it is going
- // to fire very often while typing, and needs to very efficient.
- function adjust() {
- var height, original;
-
- if (mirrored !== ta) {
- initMirror();
- } else {
- setWidth();
- }
-
- mirror.value = ta.value + options.append;
- mirror.style.overflowY = ta.style.overflowY;
- original = parseInt(ta.style.height,10);
-
- // Setting scrollTop to zero is needed in IE8 and lower for the next step to be accurately applied
- mirror.scrollTop = 0;
-
- mirror.scrollTop = 9e4;
-
- // Using scrollTop rather than scrollHeight because scrollHeight is non-standard and includes padding.
- height = mirror.scrollTop;
-
- if (maxHeight && height > maxHeight) {
- ta.style.overflowY = 'scroll';
- height = maxHeight;
- } else {
- ta.style.overflowY = 'hidden';
- if (height < minHeight) {
- height = minHeight;
- }
- }
-
- height += boxOffset;
-
- if (original !== height) {
- ta.style.height = height + 'px';
- if (callback) {
- options.callback.call(ta,ta);
- }
- }
- }
+ function resize() {
+ if (ta.scrollHeight === 0) {
+ // If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
+ return;
+ }
+
+ const overflows = getParentOverflows(ta);
+ const docTop = document.documentElement && document.documentElement.scrollTop; // Needed for Mobile IE (ticket #240)
+
+ ta.style.height = '';
+ ta.style.height = (ta.scrollHeight+heightOffset)+'px';
+
+ // used to check if an update is actually necessary on window.resize
+ clientWidth = ta.clientWidth;
+
+ // prevents scroll-position jumping
+ overflows.forEach(el => {
+ el.node.scrollTop = el.scrollTop
+ });
+
+ if (docTop) {
+ document.documentElement.scrollTop = docTop;
+ }
+ }
+
+ function update() {
+ resize();
- function resize () {
- clearTimeout(timeout);
- timeout = setTimeout(function(){
- var newWidth = $ta.width();
+ const styleHeight = Math.round(parseFloat(ta.style.height));
+ const computed = window.getComputedStyle(ta, null);
- if (newWidth !== width) {
- width = newWidth;
- adjust();
- }
- }, parseInt(options.resizeDelay,10));
+ // Using offsetHeight as a replacement for computed.height in IE, because IE does not account use of border-box
+ var actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(computed.height)) : ta.offsetHeight;
+
+ // The actual height not matching the style height (set via the resize method) indicates that
+ // the max-height has been exceeded, in which case the overflow should be allowed.
+ if (actualHeight < styleHeight) {
+ if (computed.overflowY === 'hidden') {
+ changeOverflow('scroll');
+ resize();
+ actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight;
+ }
+ } else {
+ // Normally keep overflow set to hidden, to avoid flash of scrollbar as the textarea expands.
+ if (computed.overflowY !== 'hidden') {
+ changeOverflow('hidden');
+ resize();
+ actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight;
}
+ }
- if ('onpropertychange' in ta) {
- if ('oninput' in ta) {
- // Detects IE9. IE9 does not fire onpropertychange or oninput for deletions,
- // so binding to onkeyup to catch most of those occasions. There is no way that I
- // know of to detect something like 'cut' in IE9.
- $ta.on('input.autosize keyup.autosize', adjust);
- } else {
- // IE7 / IE8
- $ta.on('propertychange.autosize', function(){
- if(event.propertyName === 'value'){
- adjust();
- }
- });
- }
- } else {
- // Modern Browsers
- $ta.on('input.autosize', adjust);
+ if (cachedHeight !== actualHeight) {
+ cachedHeight = actualHeight;
+ const evt = createEvent('autosize:resized');
+ try {
+ ta.dispatchEvent(evt);
+ } catch (err) {
+ // Firefox will throw an error on dispatchEvent for a detached element
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=889376
}
+ }
+ }
- // Set options.resizeDelay to false if using fixed-width textarea elements.
- // Uses a timeout and width check to reduce the amount of times adjust needs to be called after window resize.
+ const pageResize = () => {
+ if (ta.clientWidth !== clientWidth) {
+ update();
+ }
+ };
- if (options.resizeDelay !== false) {
- $(window).on('resize.autosize', resize);
- }
+ const destroy = (style => {
+ window.removeEventListener('resize', pageResize, false);
+ ta.removeEventListener('input', update, false);
+ ta.removeEventListener('keyup', update, false);
+ ta.removeEventListener('autosize:destroy', destroy, false);
+ ta.removeEventListener('autosize:update', update, false);
- // Event for manual triggering if needed.
- // Should only be needed when the value of the textarea is changed through JavaScript rather than user input.
- $ta.on('autosize.resize', adjust);
-
- // Event for manual triggering that also forces the styles to update as well.
- // Should only be needed if one of typography styles of the textarea change, and the textarea is already the target of the adjust method.
- $ta.on('autosize.resizeIncludeStyle', function() {
- mirrored = null;
- adjust();
- });
-
- $ta.on('autosize.destroy', function(){
- mirrored = null;
- clearTimeout(timeout);
- $(window).off('resize', resize);
- $ta
- .off('autosize')
- .off('.autosize')
- .css(originalStyles)
- .removeData('autosize');
- });
-
- // Call adjust in case the textarea already contains text.
- adjust();
+ Object.keys(style).forEach(key => {
+ ta.style[key] = style[key];
});
+
+ map.delete(ta);
+ }).bind(ta, {
+ height: ta.style.height,
+ resize: ta.style.resize,
+ overflowY: ta.style.overflowY,
+ overflowX: ta.style.overflowX,
+ wordWrap: ta.style.wordWrap,
+ });
+
+ ta.addEventListener('autosize:destroy', destroy, false);
+
+ // IE9 does not fire onpropertychange or oninput for deletions,
+ // so binding to onkeyup to catch most of those events.
+ // There is no way that I know of to detect something like 'cut' in IE9.
+ if ('onpropertychange' in ta && 'oninput' in ta) {
+ ta.addEventListener('keyup', update, false);
+ }
+
+ window.addEventListener('resize', pageResize, false);
+ ta.addEventListener('input', update, false);
+ ta.addEventListener('autosize:update', update, false);
+ ta.style.overflowX = 'hidden';
+ ta.style.wordWrap = 'break-word';
+
+ map.set(ta, {
+ destroy,
+ update,
+ });
+
+ init();
+}
+
+function destroy(ta) {
+ const methods = map.get(ta);
+ if (methods) {
+ methods.destroy();
+ }
+}
+
+function update(ta) {
+ const methods = map.get(ta);
+ if (methods) {
+ methods.update();
+ }
+}
+
+let autosize = null;
+
+// Do nothing in Node.js environment and IE8 (or lower)
+if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
+ autosize = el => el;
+ autosize.destroy = el => el;
+ autosize.update = el => el;
+} else {
+ autosize = (el, options) => {
+ if (el) {
+ Array.prototype.forEach.call(el.length ? el : [el], x => assign(x, options));
+ }
+ return el;
+ };
+ autosize.destroy = el => {
+ if (el) {
+ Array.prototype.forEach.call(el.length ? el : [el], destroy);
+ }
+ return el;
};
-}(window.jQuery || window.$)); // jQuery or jQuery-like library, such as Zepto
\ No newline at end of file
+ autosize.update = el => {
+ if (el) {
+ Array.prototype.forEach.call(el.length ? el : [el], update);
+ }
+ return el;
+ };
+}
+
+export default autosize;