diff --git a/changelog.md b/changelog.md index 7687100..7606159 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,7 @@ ## Changelog ##### v.6.0.0 - 2023-02-10 +* Reworked to remove use of cached (potentially stale) style values. Fixes #404 * Dropped support for Internet Explorer ##### v.5.0.2 - 2022-11-20 diff --git a/dist/autosize.esm.js b/dist/autosize.esm.js index 1caed73..f9ef33d 100644 --- a/dist/autosize.esm.js +++ b/dist/autosize.esm.js @@ -1 +1 @@ -var e=new Map;function t(t){var o=e.get(t);o&&o.destroy()}function o(t){var o=e.get(t);o&&o.update()}var r=null;"undefined"==typeof window?((r=function(e){return e}).destroy=function(e){return e},r.update=function(e){return e}):((r=function(t,o){return t&&Array.prototype.forEach.call(t.length?t:[t],function(t){return function(t){if(t&&t.nodeName&&"TEXTAREA"===t.nodeName&&!e.has(t)){var o,r=null,n=null,i=null,l=function(){t.clientWidth!==n&&u()},a=function(o){window.removeEventListener("resize",l,!1),t.removeEventListener("input",u,!1),t.removeEventListener("keyup",u,!1),t.removeEventListener("autosize:destroy",a,!1),t.removeEventListener("autosize:update",u,!1),Object.keys(o).forEach(function(e){t.style[e]=o[e]}),e.delete(t)}.bind(t,{height:t.style.height,resize:t.style.resize,overflowY:t.style.overflowY,overflowX:t.style.overflowX,wordWrap:t.style.wordWrap});t.addEventListener("autosize:destroy",a,!1),window.addEventListener("resize",l,!1),t.addEventListener("input",u,!1),t.addEventListener("autosize:update",u,!1),t.style.overflowX="hidden",t.style.wordWrap="break-word",e.set(t,{destroy:a,update:u}),"vertical"===(o=window.getComputedStyle(t)).resize?t.style.resize="none":"both"===o.resize&&(t.style.resize="horizontal"),r="content-box"===o.boxSizing?-(parseFloat(o.paddingTop)+parseFloat(o.paddingBottom)):parseFloat(o.borderTopWidth)+parseFloat(o.borderBottomWidth),isNaN(r)&&(r=0),u()}function s(e){var o=t.style.width;t.style.width="0px",t.style.width=o,t.style.overflowY=e}function d(){if(0!==t.scrollHeight){var e=function(e){for(var t=[];e&&e.parentNode&&e.parentNode instanceof Element;)e.parentNode.scrollTop&&t.push([e.parentNode,e.parentNode.scrollTop]),e=e.parentNode;return function(){return t.forEach(function(e){var t=e[0],o=e[1];t.style.scrollBehavior="auto",t.scrollTop=o,t.style.scrollBehavior=null})}}(t);t.style.height="",t.style.height=t.scrollHeight+r+"px",n=t.clientWidth,e()}}function u(){d();var e=parseFloat(t.style.height),o=window.getComputedStyle(t),r=parseFloat(o.height);rparseFloat(o.maxHeight)?("hidden"===o.overflowY&&(t.style.overflow="scroll"),i=parseFloat(o.maxHeight)):"hidden"!==o.overflowY&&(t.style.overflow="hidden"),t.style.height=i+"px",e&&(t.style.textAlign=e),a(),r!==i&&(t.dispatchEvent(new Event("autosize:resized",{bubbles:!0})),r=i),n!==o.overflow&&!e){var s=o.textAlign;"hidden"===o.overflow&&(t.style.textAlign="start"===s?"end":"start"),l(s)}}}}(t)}),t}).destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],t),e},o.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],r),e});var n=o;export default n; diff --git a/dist/autosize.js b/dist/autosize.js index f85dd8c..130c550 100644 --- a/dist/autosize.js +++ b/dist/autosize.js @@ -7,46 +7,7 @@ function assign(ta) { if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || assignedElements.has(ta)) return; - var heightOffset = null; - var clientWidth = null; - var cachedHeight = null; - - function init() { - var style = window.getComputedStyle(ta); - - if (style.resize === 'vertical') { - ta.style.resize = 'none'; - } else if (style.resize === 'both') { - ta.style.resize = 'horizontal'; - } - - 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(); - } - - 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. - var width = ta.style.width; - ta.style.width = '0px'; // Force reflow: - /* jshint ignore:end */ - - ta.style.width = width; - } - ta.style.overflowY = value; - } + var previousHeight = null; function cacheScrollTops(el) { var arr = []; @@ -70,7 +31,15 @@ }; } - function resize() { + var computed = window.getComputedStyle(ta); + + function update(cachedTextAlign) { + if (cachedTextAlign === void 0) { + cachedTextAlign = null; + } + + var initialOverflowY = computed.overflowY; + if (ta.scrollHeight === 0) { // If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM. return; @@ -78,70 +47,83 @@ var restoreScrollTops = cacheScrollTops(ta); - ta.style.height = ''; // this is necessary for getting an accurate scrollHeight on the next line + ta.style.height = ''; // this is necessary for to scrollHeight to accurately reflect situations where the textarea should shrink + // disallow vertical resizing - ta.style.height = ta.scrollHeight + heightOffset + 'px'; // used to check if an update is actually necessary on window.resize + if (computed.resize === 'vertical') { + ta.style.resize = 'none'; + } else if (computed.resize === 'both') { + ta.style.resize = 'horizontal'; + } - clientWidth = ta.clientWidth; - restoreScrollTops(); - } + var newHeight; - function update() { - resize(); - var styleHeight = parseFloat(ta.style.height); - var computed = window.getComputedStyle(ta); - var actualHeight = parseFloat(computed.height); // 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 (computed.boxSizing === 'content-box') { + newHeight = ta.scrollHeight - (parseFloat(computed.paddingTop) + parseFloat(computed.paddingBottom)); + } else { + newHeight = ta.scrollHeight + parseFloat(computed.borderTopWidth) + parseFloat(computed.borderBottomWidth); + } - if (actualHeight < styleHeight) { + if (computed.maxHeight !== 'none' && newHeight > parseFloat(computed.maxHeight)) { if (computed.overflowY === 'hidden') { - changeOverflow('scroll'); - resize(); - actualHeight = parseFloat(window.getComputedStyle(ta).height); - } - } else { - // Normally keep overflow set to hidden, to avoid flash of scrollbar as the textarea expands. - if (computed.overflowY !== 'hidden') { - changeOverflow('hidden'); - resize(); - actualHeight = parseFloat(window.getComputedStyle(ta).height); + ta.style.overflow = 'scroll'; } + + newHeight = parseFloat(computed.maxHeight); + } else if (computed.overflowY !== 'hidden') { + ta.style.overflow = 'hidden'; + } + + ta.style.height = newHeight + 'px'; + + if (cachedTextAlign) { + ta.style.textAlign = cachedTextAlign; } - if (cachedHeight !== actualHeight) { - cachedHeight = actualHeight; + restoreScrollTops(); + + if (previousHeight !== newHeight) { ta.dispatchEvent(new Event('autosize:resized', { bubbles: true })); + previousHeight = newHeight; } - } - var pageResize = function pageResize() { - if (ta.clientWidth !== clientWidth) { - update(); + if (initialOverflowY !== computed.overflow && !cachedTextAlign) { + var textAlign = computed.textAlign; + + if (computed.overflow === 'hidden') { + // Webkit fails to reflow text after overflow is hidden, + // even if hiding overflow would allow text to fit more compactly. + // The following is intended to force the necessary text reflow. + ta.style.textAlign = textAlign === 'start' ? 'end' : 'start'; + } + + update(textAlign); } - }; + } var destroy = function (style) { - window.removeEventListener('resize', pageResize, false); + window.removeEventListener('resize', update, false); ta.removeEventListener('input', update, false); ta.removeEventListener('keyup', update, false); ta.removeEventListener('autosize:destroy', destroy, false); ta.removeEventListener('autosize:update', update, false); Object.keys(style).forEach(function (key) { - ta.style[key] = style[key]; + return ta.style[key] = style[key]; }); assignedElements["delete"](ta); }.bind(ta, { height: ta.style.height, resize: ta.style.resize, + textAlign: ta.style.textAlign, overflowY: ta.style.overflowY, overflowX: ta.style.overflowX, wordWrap: ta.style.wordWrap }); ta.addEventListener('autosize:destroy', destroy, false); - window.addEventListener('resize', pageResize, false); + window.addEventListener('resize', update, false); ta.addEventListener('input', update, false); ta.addEventListener('autosize:update', update, false); ta.style.overflowX = 'hidden'; @@ -150,7 +132,7 @@ destroy: destroy, update: update }); - init(); + update(); } function destroy(ta) { diff --git a/dist/autosize.min.js b/dist/autosize.min.js index d64e636..c89b132 100644 --- a/dist/autosize.min.js +++ b/dist/autosize.min.js @@ -1 +1 @@ -!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e||self).autosize=t()}(this,function(){var e=new Map;function t(t){var o=e.get(t);o&&o.destroy()}function o(t){var o=e.get(t);o&&o.update()}var n=null;return"undefined"==typeof window?((n=function(e){return e}).destroy=function(e){return e},n.update=function(e){return e}):((n=function(t,o){return t&&Array.prototype.forEach.call(t.length?t:[t],function(t){return function(t){if(t&&t.nodeName&&"TEXTAREA"===t.nodeName&&!e.has(t)){var o,n=null,r=null,i=null,l=function(){t.clientWidth!==r&&u()},a=function(o){window.removeEventListener("resize",l,!1),t.removeEventListener("input",u,!1),t.removeEventListener("keyup",u,!1),t.removeEventListener("autosize:destroy",a,!1),t.removeEventListener("autosize:update",u,!1),Object.keys(o).forEach(function(e){t.style[e]=o[e]}),e.delete(t)}.bind(t,{height:t.style.height,resize:t.style.resize,overflowY:t.style.overflowY,overflowX:t.style.overflowX,wordWrap:t.style.wordWrap});t.addEventListener("autosize:destroy",a,!1),window.addEventListener("resize",l,!1),t.addEventListener("input",u,!1),t.addEventListener("autosize:update",u,!1),t.style.overflowX="hidden",t.style.wordWrap="break-word",e.set(t,{destroy:a,update:u}),"vertical"===(o=window.getComputedStyle(t)).resize?t.style.resize="none":"both"===o.resize&&(t.style.resize="horizontal"),n="content-box"===o.boxSizing?-(parseFloat(o.paddingTop)+parseFloat(o.paddingBottom)):parseFloat(o.borderTopWidth)+parseFloat(o.borderBottomWidth),isNaN(n)&&(n=0),u()}function d(e){var o=t.style.width;t.style.width="0px",t.style.width=o,t.style.overflowY=e}function s(){if(0!==t.scrollHeight){var e=function(e){for(var t=[];e&&e.parentNode&&e.parentNode instanceof Element;)e.parentNode.scrollTop&&t.push([e.parentNode,e.parentNode.scrollTop]),e=e.parentNode;return function(){return t.forEach(function(e){var t=e[0],o=e[1];t.style.scrollBehavior="auto",t.scrollTop=o,t.style.scrollBehavior=null})}}(t);t.style.height="",t.style.height=t.scrollHeight+n+"px",r=t.clientWidth,e()}}function u(){s();var e=parseFloat(t.style.height),o=window.getComputedStyle(t),n=parseFloat(o.height);nparseFloat(r.maxHeight)?("hidden"===r.overflowY&&(t.style.overflow="scroll"),l=parseFloat(r.maxHeight)):"hidden"!==r.overflowY&&(t.style.overflow="hidden"),t.style.height=l+"px",e&&(t.style.textAlign=e),a(),o!==l&&(t.dispatchEvent(new Event("autosize:resized",{bubbles:!0})),o=l),n!==r.overflow&&!e){var s=r.textAlign;"hidden"===r.overflow&&(t.style.textAlign="start"===s?"end":"start"),i(s)}}}}(t)}),t}).destroy=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],t),e},r.update=function(e){return e&&Array.prototype.forEach.call(e.length?e:[e],o),e}),r}); diff --git a/src/autosize.js b/src/autosize.js index 448b549..e8d4186 100644 --- a/src/autosize.js +++ b/src/autosize.js @@ -3,48 +3,7 @@ const assignedElements = new Map(); function assign(ta) { if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || assignedElements.has(ta)) return; - let heightOffset = null; - let clientWidth = null; - let cachedHeight = null; - - function init() { - const style = window.getComputedStyle(ta); - - if (style.resize === 'vertical') { - ta.style.resize = 'none'; - } else if (style.resize === 'both') { - ta.style.resize = 'horizontal'; - } - - 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(); - } - - 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; - } - - ta.style.overflowY = value; - } + let previousHeight = null; function cacheScrollTops(el) { const arr = []; @@ -63,7 +22,11 @@ function assign(ta) { }); } - function resize() { + const computed = window.getComputedStyle(ta); + + function update(cachedTextAlign = null) { + let initialOverflowY = computed.overflowY; + if (ta.scrollHeight === 0) { // If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM. return; @@ -72,67 +35,71 @@ function assign(ta) { // ensure the scrollTop values of parent elements are not modified as a consequence of calculating the textarea height const restoreScrollTops = cacheScrollTops(ta); - ta.style.height = ''; // this is necessary for getting an accurate scrollHeight on the next line - ta.style.height = (ta.scrollHeight+heightOffset)+'px'; - - // used to check if an update is actually necessary on window.resize - clientWidth = ta.clientWidth; - - restoreScrollTops(); - } + ta.style.height = ''; // this is necessary for to scrollHeight to accurately reflect situations where the textarea should shrink - function update() { - resize(); + // disallow vertical resizing + if (computed.resize === 'vertical') { + ta.style.resize = 'none'; + } else if (computed.resize === 'both') { + ta.style.resize = 'horizontal'; + } - const styleHeight = parseFloat(ta.style.height); - const computed = window.getComputedStyle(ta); + let newHeight; - var actualHeight = parseFloat(computed.height); + if (computed.boxSizing === 'content-box') { + newHeight = ta.scrollHeight - (parseFloat(computed.paddingTop)+parseFloat(computed.paddingBottom)); + } else { + newHeight = ta.scrollHeight + parseFloat(computed.borderTopWidth)+parseFloat(computed.borderBottomWidth); + } - // 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.maxHeight !== 'none' && newHeight > parseFloat(computed.maxHeight)) { if (computed.overflowY === 'hidden') { - changeOverflow('scroll'); - resize(); - actualHeight = parseFloat(window.getComputedStyle(ta).height); - } - } else { - // Normally keep overflow set to hidden, to avoid flash of scrollbar as the textarea expands. - if (computed.overflowY !== 'hidden') { - changeOverflow('hidden'); - resize(); - actualHeight = parseFloat(window.getComputedStyle(ta).height); + ta.style.overflow = 'scroll'; } + newHeight = parseFloat(computed.maxHeight); + } else if (computed.overflowY !== 'hidden') { + ta.style.overflow = 'hidden'; } - if (cachedHeight !== actualHeight) { - cachedHeight = actualHeight; + ta.style.height = newHeight+'px'; + + if (cachedTextAlign) { + ta.style.textAlign = cachedTextAlign; + } + + restoreScrollTops(); + + if (previousHeight !== newHeight) { ta.dispatchEvent(new Event('autosize:resized', {bubbles: true})); + previousHeight = newHeight; } - } - const pageResize = () => { - if (ta.clientWidth !== clientWidth) { - update(); + if (initialOverflowY !== computed.overflow && !cachedTextAlign) { + const textAlign = computed.textAlign; + + if (computed.overflow === 'hidden') { + // Webkit fails to reflow text after overflow is hidden, + // even if hiding overflow would allow text to fit more compactly. + // The following is intended to force the necessary text reflow. + ta.style.textAlign = textAlign === 'start' ? 'end' : 'start'; + } + + update(textAlign); } - }; + } const destroy = (style => { - window.removeEventListener('resize', pageResize, false); + window.removeEventListener('resize', update, false); ta.removeEventListener('input', update, false); ta.removeEventListener('keyup', update, false); ta.removeEventListener('autosize:destroy', destroy, false); ta.removeEventListener('autosize:update', update, false); - - Object.keys(style).forEach(key => { - ta.style[key] = style[key]; - }); - + Object.keys(style).forEach(key => ta.style[key] = style[key]); assignedElements.delete(ta); }).bind(ta, { height: ta.style.height, resize: ta.style.resize, + textAlign: ta.style.textAlign, overflowY: ta.style.overflowY, overflowX: ta.style.overflowX, wordWrap: ta.style.wordWrap, @@ -140,7 +107,7 @@ function assign(ta) { ta.addEventListener('autosize:destroy', destroy, false); - window.addEventListener('resize', pageResize, false); + window.addEventListener('resize', update, false); ta.addEventListener('input', update, false); ta.addEventListener('autosize:update', update, false); ta.style.overflowX = 'hidden'; @@ -151,7 +118,7 @@ function assign(ta) { update, }); - init(); + update(); } function destroy(ta) {