UNPKG

handsontable

Version:

Handsontable is a JavaScript Data Grid available for React, Angular and Vue.

207 lines (200 loc) • 7.7 kB
/* eslint-disable jsdoc/require-description-complete-sentence */ /** * autoResize - resizes a DOM element to the width and height of another DOM element * * Copyright 2014, Marcin Warpechowski * Licensed under the MIT license */ /* eslint-enable jsdoc/require-description-complete-sentence */ /** * Attaches an event listener to the given element. * * @param {HTMLElement} element The element to observe. * @param {string} eventName The name of the event to listen for. * @param {Function} handler The function to call when the event is triggered. */ function observe(element, eventName, handler) { element.addEventListener(eventName, handler, false); } /** * Removes an event listener from an element. * * @param {HTMLElement} element The element to remove the event listener from. * @param {string} eventName The name of the event to remove. * @param {Function} handler The function to remove as a listener. */ function unObserve(element, eventName, handler) { element.removeEventListener(eventName, handler, false); } /** * Returns the computed style of an element. * * @param {Element} element The element to get the computed style from. * @returns {CSSStyleDeclaration} The computed style of the element. */ function getComputedStyle(element) { return element.ownerDocument.defaultView.getComputedStyle(element); } /** * @typedef InputElementResizerConfig * @property {number} minWidth The minimum width of the element. * @property {number} maxWidth The maximum width of the element. * @property {number} minHeight The minimum height of the element. * @property {number} maxHeight The maximum height of the element. * @property {function(HTMLElement): string} textContent The function that returns the text content to measure. */ /** * @typedef InputElementResizer * @property {function(HTMLElement, InputElementResizerConfig, boolean): void} init Initializes the resizer. * @property {function(): void} resize Resizes the element. * @property {function(): void} unObserve Removes the event listeners. */ /** * Creates an input element resizer. * * @param {Document} ownerDocument The document to create the resizer for. * @param {InputElementResizerConfig} initialOptions The configuration to extend the defaults with. * @returns {InputElementResizer} */ export function createInputElementResizer(ownerDocument) { let initialOptions = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; const defaults = { minHeight: 200, maxHeight: 300, minWidth: 100, maxWidth: 300, textContent: element => element.value, ...initialOptions }; const body = ownerDocument.body; const textHolder = ownerDocument.createTextNode(''); const textContainer = ownerDocument.createElement('span'); let observedElement; /** * Resizes the element. */ function resize() { var _getComputedStyle, _getComputedStyle2; textHolder.textContent = defaults.textContent(observedElement); // Won't expand the element size for displaying body as for example, `grid`, `inline-grid` or `flex` with // `flex-direction` set as `column`. textContainer.style.position = 'absolute'; textContainer.style.fontSize = getComputedStyle(observedElement).fontSize; textContainer.style.fontFamily = getComputedStyle(observedElement).fontFamily; textContainer.style.whiteSpace = 'pre'; body.appendChild(textContainer); const paddingStart = parseInt(((_getComputedStyle = getComputedStyle(observedElement)) === null || _getComputedStyle === void 0 ? void 0 : _getComputedStyle.paddingInlineStart) || 0, 10); const paddingEnd = parseInt(((_getComputedStyle2 = getComputedStyle(observedElement)) === null || _getComputedStyle2 === void 0 ? void 0 : _getComputedStyle2.paddingInlineEnd) || 0, 10); const width = textContainer.clientWidth + paddingStart + paddingEnd + 1; body.removeChild(textContainer); const elementStyle = observedElement.style; elementStyle.height = `${defaults.minHeight}px`; if (defaults.minWidth > width) { elementStyle.width = `${defaults.minWidth}px`; } else if (width > defaults.maxWidth) { elementStyle.width = `${defaults.maxWidth}px`; } else { elementStyle.width = `${width}px`; } const scrollHeight = observedElement.scrollHeight ? observedElement.scrollHeight - 1 : 0; if (defaults.minHeight > scrollHeight) { elementStyle.height = `${defaults.minHeight}px`; } else if (defaults.maxHeight < scrollHeight) { elementStyle.height = `${defaults.maxHeight}px`; elementStyle.overflowY = 'visible'; } else { elementStyle.height = `${scrollHeight}px`; } } /** * Resizes the element after a delay. */ function delayedResize() { ownerDocument.defaultView.setTimeout(resize, 0); } /** * Extends the default configuration. * * @param {InputElementResizerConfig} config The configuration to extend the defaults with. */ function extendDefaults(config) { if (config && config.minHeight) { if (config.minHeight === 'inherit') { defaults.minHeight = observedElement.clientHeight; } else { const minHeight = parseInt(config.minHeight, 10); if (!isNaN(minHeight)) { defaults.minHeight = minHeight; } } } if (config && config.maxHeight) { if (config.maxHeight === 'inherit') { defaults.maxHeight = observedElement.clientHeight; } else { const maxHeight = parseInt(config.maxHeight, 10); if (!isNaN(maxHeight)) { defaults.maxHeight = maxHeight; } } } if (config && config.minWidth) { if (config.minWidth === 'inherit') { defaults.minWidth = observedElement.clientWidth; } else { const minWidth = parseInt(config.minWidth, 10); if (!isNaN(minWidth)) { defaults.minWidth = minWidth; } } } if (config && config.maxWidth) { if (config.maxWidth === 'inherit') { defaults.maxWidth = observedElement.clientWidth; } else { const maxWidth = parseInt(config.maxWidth, 10); if (!isNaN(maxWidth)) { defaults.maxWidth = maxWidth; } } } if (!textContainer.firstChild) { textContainer.className = 'autoResize'; textContainer.style.display = 'inline-block'; textContainer.appendChild(textHolder); } } /** * Initializes the resizer. * * @param {HTMLElement} elementToObserve The element to observe. * @param {InputElementResizerConfig} config The configuration to extend the defaults with. * @param {boolean} [doObserve=false] Whether to observe the element and resize it on every input change. */ function init(elementToObserve, config) { let doObserve = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false; observedElement = elementToObserve; extendDefaults(config); if (observedElement.nodeName === 'TEXTAREA') { observedElement.style.resize = 'none'; observedElement.style.height = `${defaults.minHeight}px`; observedElement.style.minWidth = `${defaults.minWidth}px`; observedElement.style.maxWidth = `${defaults.maxWidth}px`; observedElement.style.overflowY = 'hidden'; } if (doObserve) { observe(observedElement, 'input', resize); // the keydown event is necessary for undo stack to work properly observe(observedElement, 'keydown', delayedResize); } resize(); } return { init, resize, unObserve() { unObserve(observedElement, 'input', resize); unObserve(observedElement, 'keydown', delayedResize); } }; }