UNPKG

zent

Version:

一套前端设计语言和基于React的实现

276 lines (231 loc) 7.71 kB
/** * The MIT License (MIT) * Copyright (c) 2015 Jack Moore * * A fork of https://github.com/jackmoore/autosize for these reasons: * * 1. Project has no update since late 2018 * 2. TypeScript definition is buggy */ import { addEventListener } from '../component/event-handler'; interface IMethods { update: () => void; destroy: () => void; } interface IStyles { [key: string]: string; // make tsc happy: ts(7053) height: string; resize: string; overflowX: string; overflowY: string; wordWrap: string; } const map = new Map<HTMLTextAreaElement, IMethods>(); const createEvent = (name: string) => new Event(name, { bubbles: true }); function assign(ta: HTMLTextAreaElement) { if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || map.has(ta)) return; let heightOffset = NaN; let clientWidth = NaN; let cachedHeight = NaN; 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'; } 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: string) { { // 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; } function getParentOverflows(el: Element) { const arr = []; while (el && el.parentNode && el.parentNode instanceof Element) { if (el.parentNode.scrollTop) { arr.push({ node: el.parentNode, scrollTop: el.parentNode.scrollTop, }); } el = el.parentNode; } return arr; } 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(); const styleHeight = Math.round(parseFloat(ta.style.height)); const computed = window.getComputedStyle(ta, null); // Using offsetHeight as a replacement for computed.height in IE, because IE does not account use of border-box let 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 (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 } } } const pageResize = () => { if (ta.clientWidth !== clientWidth) { update(); } }; const eventCancelList: Array<() => void> = []; const destroy = ((style: IStyles) => { eventCancelList.forEach(unsubscribe => unsubscribe()); eventCancelList.splice(0, eventCancelList.length); Object.keys(style).forEach(key => { (ta.style as unknown as { [key: string]: string })[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, }); eventCancelList.push(addEventListener(ta, 'autosize:destroy', destroy)); // 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) { eventCancelList.push(addEventListener(ta, 'keyup', update)); } eventCancelList.push(addEventListener(window, 'resize', pageResize)); eventCancelList.push(addEventListener(ta, 'input', update)); eventCancelList.push(addEventListener(ta, 'autosize:update', update)); ta.style.overflowX = 'hidden'; ta.style.wordWrap = 'break-word'; map.set(ta, { destroy, update, }); init(); } function _destroy(ta: HTMLTextAreaElement) { const methods = map.get(ta); if (methods) { methods.destroy(); } } function _update(ta: HTMLTextAreaElement) { const methods = map.get(ta); if (methods) { methods.update(); } } function isNodeList( el: HTMLTextAreaElement | NodeListOf<HTMLTextAreaElement> ): el is NodeListOf<HTMLTextAreaElement> { return 'length' in el; } export function autosize(el: HTMLTextAreaElement): HTMLTextAreaElement; export function autosize( el: NodeListOf<HTMLTextAreaElement> ): NodeListOf<HTMLTextAreaElement>; export function autosize( el: HTMLTextAreaElement | NodeListOf<HTMLTextAreaElement> ) { if (el) { Array.prototype.forEach.call(isNodeList(el) ? el : [el], x => assign(x)); } return el; } export function destroy(el: HTMLTextAreaElement): HTMLTextAreaElement; export function destroy( el: NodeListOf<HTMLTextAreaElement> ): NodeListOf<HTMLTextAreaElement>; export function destroy( el: HTMLTextAreaElement | NodeListOf<HTMLTextAreaElement> ) { if (el) { Array.prototype.forEach.call(isNodeList(el) ? el : [el], _destroy); } return el; } export function update(el: HTMLTextAreaElement): HTMLTextAreaElement; export function update( el: NodeListOf<HTMLTextAreaElement> ): NodeListOf<HTMLTextAreaElement>; export function update( el: HTMLTextAreaElement | NodeListOf<HTMLTextAreaElement> ) { if (el) { Array.prototype.forEach.call(isNodeList(el) ? el : [el], _update); } return el; }