import { Directive, Input, Injector, OnInit, ElementRef, HostListener, AfterViewInit, OnChanges, SimpleChanges } from '@angular/core'; import { NgControl } from '@angular/forms'; @Directive({ selector: '[auto-size]', }) export class TextareaAutoSizeDirective implements OnInit, AfterViewInit, OnChanges{ @Input('auto-size') enable = true; @Input() maxHeight: number; private previousValue = null; private isUserResized = false; private textarea; private document; private documentElement; private minHeight; constructor(private el: ElementRef, private ngControl: NgControl, private injector: Injector) { this.textarea = this.el.nativeElement; } ngOnInit() { this.init(); } ngOnChanges(changes: SimpleChanges) { if (changes.enable && !changes.enable.isFirstChange()) { this.sizeToFit(); } } ngAfterViewInit() { this.minHeight = this.textarea.getBoundingClientRect().height; } private init() { if (this.enable) { this.document = this.textarea.ownerDocument; this.documentElement = this.document.documentElement; if (this.textarea) { if (this.textarea.value) { this.sizeToFit(); } } if (this.ngControl) { this.ngControl.valueChanges.subscribe(v => { if (v !== this.previousValue) { this.sizeToFit(); } }); } } } @HostListener('input') onInput() { this.sizeToFit(); } @HostListener('change') onValueChange() { this.sizeToFit(); } private overflowOffset() { let offsetTop = 0; let el = this.textarea; while (el !== document.body && el !== null) { offsetTop += el.offsetTop || 0; el = el.offsetParent; } const top = offsetTop - document.defaultView.pageYOffset; const bottom = this.documentElement.clientHeight - (top + this.textarea.offsetHeight); return { top, bottom }; } private sizeToFit() { if (!this.enable) { return; } const textarea = this.textarea; const viewportMarginBottom = 100; if (this.isUserResized) { return; } if (this.textarea.value === this.previousValue) { return; } if (this.textarea.offsetWidth <= 0 && this.textarea.offsetHeight <= 0) { return; } // const { top, bottom } = this.overflowOffset(); // if (top < 0 || bottom < 0) { // return; // } const textareaStyle = getComputedStyle(textarea); const topBorderWidth = Number(textareaStyle.borderTopWidth.replace(/px/, '')); const bottomBorderWidth = Number(textareaStyle.borderBottomWidth.replace(/px/, '')); const isBorderBox = textareaStyle.boxSizing === 'border-box'; const borderAddOn = isBorderBox ? topBorderWidth + bottomBorderWidth : 0; // const maxHeight = Number(textareaStyle.height.replace(/px/, '')) + bottom; // const adjustedViewportMarginBottom = bottom < viewportMarginBottom ? bottom : viewportMarginBottom; // textarea.style.maxHeight = `${maxHeight - adjustedViewportMarginBottom}px`; if (this.maxHeight) { textarea.style.maxHeight = `${this.maxHeight}px`; } const container = textarea.parentElement; if (container instanceof HTMLElement) { const containerHeight = container.style.height; container.style.height = getComputedStyle(container).height; textarea.style.height = 'auto'; let h = textarea.scrollHeight + borderAddOn; if (this.minHeight > h) { h = this.minHeight; } textarea.style.height = `${h}px`; container.style.height = containerHeight; } this.previousValue = textarea.value; } }