ngx-autosize
Version:
Directive that automatically adjusts textarea height to fit content
229 lines (222 loc) • 9.66 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, EventEmitter, Directive, Input, Output, HostListener, NgModule } from '@angular/core';
class WindowRef {
get nativeWindow() {
return window;
}
}
WindowRef.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: WindowRef, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
WindowRef.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: WindowRef });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: WindowRef, decorators: [{
type: Injectable
}] });
const MAX_LOOKUP_RETRIES = 3;
class AutosizeDirective {
constructor(element, _window, _zone) {
this.element = element;
this._window = _window;
this._zone = _zone;
this.onlyGrow = false;
this.useImportant = false;
this.resized = new EventEmitter();
this.autosize = true;
this.retries = 0;
this._destroyed = false;
if (this.element.nativeElement.tagName !== 'TEXTAREA') {
this._findNestedTextArea();
}
else {
this.textAreaEl = this.element.nativeElement;
this.textAreaEl.style['overflow-y'] = 'hidden';
this._onTextAreaFound();
}
}
set minRows(value) {
this._minRows = +value;
if (this.textAreaEl) {
this.textAreaEl.rows = this._minRows;
}
}
;
set _autosize(autosize) {
this.autosize = typeof autosize === 'boolean'
? autosize
: true;
}
;
onInput(textArea) {
this.adjust();
}
ngOnDestroy() {
this._destroyed = true;
if (this._windowResizeHandler) {
this._window.nativeWindow.removeEventListener('resize', this._windowResizeHandler, false);
}
}
ngAfterContentChecked() {
this.adjust();
}
ngOnChanges(changes) {
this.adjust(true);
}
_findNestedTextArea() {
this.textAreaEl = this.element.nativeElement.querySelector('TEXTAREA');
if (!this.textAreaEl && this.element.nativeElement.shadowRoot) {
this.textAreaEl = this.element.nativeElement.shadowRoot.querySelector('TEXTAREA');
}
if (!this.textAreaEl) {
if (this.retries >= MAX_LOOKUP_RETRIES) {
console.warn('ngx-autosize: textarea not found');
}
else {
this.retries++;
setTimeout(() => {
this._findNestedTextArea();
}, 100);
}
return;
}
this.textAreaEl.style['overflow-y'] = 'hidden';
this._onTextAreaFound();
}
_onTextAreaFound() {
this._addWindowResizeHandler();
setTimeout(() => {
this.adjust();
});
}
_addWindowResizeHandler() {
this._windowResizeHandler = debounce(() => {
this._zone.run(() => {
this.adjust();
});
}, 200);
this._zone.runOutsideAngular(() => {
this._window.nativeWindow.addEventListener('resize', this._windowResizeHandler, false);
});
}
adjust(inputsChanged = false) {
if (this.autosize && !this._destroyed && this.textAreaEl && this.textAreaEl.parentNode) {
const currentText = this.textAreaEl.value;
if (inputsChanged === false &&
currentText === this._oldContent &&
this.textAreaEl.offsetWidth === this._oldWidth) {
return;
}
this._oldContent = currentText;
this._oldWidth = this.textAreaEl.offsetWidth;
const clone = this.textAreaEl.cloneNode(true);
const parent = this.textAreaEl.parentNode;
clone.style.width = this.textAreaEl.offsetWidth + 'px';
clone.style.visibility = 'hidden';
clone.style.position = 'absolute';
clone.textContent = currentText;
parent.appendChild(clone);
clone.style['overflow-y'] = 'hidden';
clone.style.height = 'auto';
let height = clone.scrollHeight;
// add into height top and bottom borders' width
let computedStyle = this._window.nativeWindow.getComputedStyle(clone, null);
height += parseInt(computedStyle.getPropertyValue('border-top-width'));
height += parseInt(computedStyle.getPropertyValue('border-bottom-width'));
if (computedStyle.getPropertyValue('box-sizing') === 'content-box') {
height -= parseInt(computedStyle.getPropertyValue('padding-top'));
height -= parseInt(computedStyle.getPropertyValue('padding-bottom'));
}
const oldHeight = this.textAreaEl.offsetHeight;
const willGrow = height > oldHeight;
if (this.onlyGrow === false || willGrow) {
const lineHeight = this._getLineHeight();
const rowsCount = height / lineHeight;
if (this._minRows && this._minRows >= rowsCount) {
height = this._minRows * lineHeight;
}
else if (this.maxRows && this.maxRows <= rowsCount) {
// never shrink the textarea if onlyGrow is true
const maxHeight = this.maxRows * lineHeight;
height = this.onlyGrow ? Math.max(maxHeight, oldHeight) : maxHeight;
this.textAreaEl.style['overflow-y'] = 'auto';
}
else {
this.textAreaEl.style['overflow-y'] = 'hidden';
}
const heightStyle = height + 'px';
const important = this.useImportant ? 'important' : '';
this.textAreaEl.style.setProperty('height', heightStyle, important);
this.resized.emit(height);
}
parent.removeChild(clone);
}
}
_getLineHeight() {
let lineHeight = parseInt(this.textAreaEl.style.lineHeight, 10);
if (isNaN(lineHeight) && this._window.nativeWindow.getComputedStyle) {
const styles = this._window.nativeWindow.getComputedStyle(this.textAreaEl);
lineHeight = parseInt(styles.lineHeight, 10);
}
if (isNaN(lineHeight)) {
const fontSize = this._window.nativeWindow.getComputedStyle(this.textAreaEl, null).getPropertyValue('font-size');
lineHeight = Math.floor(parseInt(fontSize.replace('px', ''), 10) * 1.5);
}
return lineHeight;
}
}
AutosizeDirective.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: AutosizeDirective, deps: [{ token: i0.ElementRef }, { token: WindowRef }, { token: i0.NgZone }], target: i0.ɵɵFactoryTarget.Directive });
AutosizeDirective.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "12.0.0", version: "13.3.1", type: AutosizeDirective, selector: "[autosize]", inputs: { minRows: "minRows", _autosize: ["autosize", "_autosize"], maxRows: "maxRows", onlyGrow: "onlyGrow", useImportant: "useImportant" }, outputs: { resized: "resized" }, host: { listeners: { "input": "onInput($event.target)" } }, usesOnChanges: true, ngImport: i0 });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: AutosizeDirective, decorators: [{
type: Directive,
args: [{
selector: '[autosize]'
}]
}], ctorParameters: function () { return [{ type: i0.ElementRef }, { type: WindowRef }, { type: i0.NgZone }]; }, propDecorators: { minRows: [{
type: Input
}], _autosize: [{
type: Input,
args: ['autosize']
}], maxRows: [{
type: Input
}], onlyGrow: [{
type: Input
}], useImportant: [{
type: Input
}], resized: [{
type: Output
}], onInput: [{
type: HostListener,
args: ['input', ['$event.target']]
}] } });
function debounce(func, timeout) {
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => {
func(...args);
}, timeout);
};
}
class AutosizeModule {
}
AutosizeModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: AutosizeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
AutosizeModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: AutosizeModule, declarations: [AutosizeDirective], exports: [AutosizeDirective] });
AutosizeModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: AutosizeModule, providers: [
WindowRef
], imports: [[]] });
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.3.1", ngImport: i0, type: AutosizeModule, decorators: [{
type: NgModule,
args: [{
declarations: [AutosizeDirective],
imports: [],
providers: [
WindowRef
],
exports: [AutosizeDirective]
}]
}] });
/*
* Public API Surface of ngx-autosize
*/
/**
* Generated bundle index. Do not edit.
*/
export { AutosizeDirective, AutosizeModule, WindowRef };
//# sourceMappingURL=ngx-autosize.mjs.map