import { Directive , ElementRef , Input , OnDestroy , OnInit , Renderer2 } from '@angular/core' ; import { DesignTokenHelper } from '@kirbydesign/core' ; import { ResizeObserverService } from '../../components/shared/resize-observer/resize-observer.service' ; import { LineClampHelper } from '../../helpers/line-clamp-helper' ; const fontSize = DesignTokenHelper . fontSize ; const lineHeight = DesignTokenHelper . lineHeight ; interface HeadingSize { name : string ; fontSize : string ; lineHeight : string ; } export interface FitHeadingConfig { maxLines : number ; } @ Directive ( { selector : `h1[kirbyFitHeading],h2[kirbyFitHeading],h3[kirbyFitHeading]` , providers : [ LineClampHelper ] , } ) export class FitHeadingDirective implements OnInit , OnDestroy { @ Input ( 'kirbyFitHeading' ) config ? : FitHeadingConfig ; private isObservingHostElement : boolean ; private hostElementClone : Element ; private isScalingHeader : boolean ; private headingSizes : HeadingSize [ ] = [ { name : 'h1' , fontSize : fontSize ( 'xl' ) , lineHeight : lineHeight ( 'xl' ) , } , { name : 'h2' , fontSize : fontSize ( 'l' ) , lineHeight : lineHeight ( 'l' ) , } , { name : 'h3' , fontSize : fontSize ( 'm' ) , lineHeight : lineHeight ( 'm' ) , } , ] ; constructor ( private elementRef , private renderer , private resizeObserverService , private lineClampHelper ) { } ngOnInit ( ) { if ( this . config && this . config . maxLines ) { this . lineClampHelper . setMaxLines ( this . elementRef . nativeElement , this . config . maxLines ) ; this . observeResize ( ) ; this . isObservingHostElement = true ; } } ngOnDestroy ( ) { if ( this . isObservingHostElement ) { this . resizeObserverService . unobserve ( this . elementRef ) ; if ( this . hostElementClone ) { this . renderer . removeChild ( this . elementRef . nativeElement , this . hostElementClone ) ; } } } private observeResize ( ) { this . resizeObserverService . observe ( this . elementRef , ( ) => { this . scaleHeader ( ) ; } ) ; } private scaleHeader ( ) { if ( this . isScalingHeader ) return ; this . isScalingHeader = true ; if ( ! this . hostElementClone ) { this . hostElementClone = this . generateHostElementClone ( ) ; this . renderer . appendChild ( this . elementRef . nativeElement , this . hostElementClone ) ; } this . renderer . setStyle ( this . hostElementClone , 'width' , `${ this . elementRef . nativeElement . clientWidth }px` ) ; const fallbackSize = this . headingSizes [ this . headingSizes . length - 1 ] ; const fittedSize = this . headingSizes . find ( this . canFitHeading . bind ( this ) ) || fallbackSize ; this . setSize ( this . elementRef . nativeElement , fittedSize ) ; this . lineClampHelper . setLineHeight ( this . elementRef . nativeElement , fittedSize . lineHeight ) ; this . isScalingHeader = false ; } private canFitHeading ( size ) { this . setSize ( this . hostElementClone , size ) ; const lines = this . hostElementClone . clientHeight / parseInt ( size . lineHeight ) ; return lines <= this . config . maxLines ; } private generateHostElementClone ( ) { const clone = this . elementRef . nativeElement . cloneNode ( true ) ; this . renderer . setStyle ( clone , 'position' , 'absolute' ) ; this . renderer . setStyle ( clone , 'visibility' , 'hidden' ) ; this . lineClampHelper . removeLineClamp ( clone ) ; return clone ; } private setSize ( el , size ) { this . renderer . setStyle ( el , 'font-size' , size . fontSize ) ; this . renderer . setStyle ( el , 'line-height' , size . lineHeight ) ; } }