license import { Inject , Injectable , OnDestroy , Provider } from '@angular/core' ; import { DOCUMENT } from '@angular/common' ; import { coerceCssPixelValue } from '@angular/cdk/coercion' ; import { CdkTable , _CoalescedStyleScheduler , _COALESCED_STYLE_SCHEDULER } from '@angular/cdk/table' ; import { ColumnResize } from './column-resize' ; /**
 * Provides an implementation for resizing a column.
 * The details of how resizing works for tables for flex mat-tables are quite different.
 */ @ Injectable ( ) export abstract class ResizeStrategy { protected abstract readonly columnResize : ColumnResize ; protected abstract readonly styleScheduler : _CoalescedStyleScheduler ; protected abstract readonly table : CdkTable < unknown > ; private _pendingResizeDelta : number | null = null ; /** Updates the width of the specified column. */ abstract applyColumnSize ( cssFriendlyColumnName , columnHeader , sizeInPx , previousSizeInPx ? ) ; /** Applies a minimum width to the specified column, updating its current width as needed. */ abstract applyMinColumnSize ( cssFriendlyColumnName , columnHeader , minSizeInPx ) ; /** Applies a maximum width to the specified column, updating its current width as needed. */ abstract applyMaxColumnSize ( cssFriendlyColumnName , columnHeader , minSizeInPx ) ; /** Adjusts the width of the table element by the specified delta. */ protected updateTableWidthAndStickyColumns ( delta ) { if ( this . _pendingResizeDelta === null ) { const tableElement = this . columnResize . elementRef . nativeElement ; const tableWidth = getElementWidth ( tableElement ) ; this . styleScheduler . schedule ( ( ) => { tableElement . style . width = coerceCssPixelValue ( tableWidth + this . _pendingResizeDelta ! ) ; this . _pendingResizeDelta = null ; } ) ; this . styleScheduler . scheduleEnd ( ( ) => { this . table . updateStickyColumnStyles ( ) ; } ) ; } this . _pendingResizeDelta = ( this . _pendingResizeDelta ?? 0 ) + delta ; } } /**
 * The optimially performing resize strategy for &lt;table&gt; elements with table-layout: fixed.
 * Tested against and outperformed:
 *   CSS selector
 *   CSS selector w/ CSS variable
 *   Updating all cell nodes
 */ @ Injectable ( ) export class TableLayoutFixedResizeStrategy extends ResizeStrategy { constructor ( protected readonly columnResize , @ Inject ( _COALESCED_STYLE_SCHEDULER ) protected readonly styleScheduler , protected readonly table ) { super ( ) ; } applyColumnSize ( _ , columnHeader , sizeInPx , previousSizeInPx ? ) { const delta = sizeInPx - ( previousSizeInPx ?? getElementWidth ( columnHeader ) ) ; if ( delta === 0 ) { return ; } this . styleScheduler . schedule ( ( ) => { columnHeader . style . width = coerceCssPixelValue ( sizeInPx ) ; } ) ; this . updateTableWidthAndStickyColumns ( delta ) ; } applyMinColumnSize ( _ , columnHeader , sizeInPx ) { const currentWidth = getElementWidth ( columnHeader ) ; const newWidth = Math . max ( currentWidth , sizeInPx ) ; this . applyColumnSize ( _ , columnHeader , newWidth , currentWidth ) ; } applyMaxColumnSize ( _ , columnHeader , sizeInPx ) { const currentWidth = getElementWidth ( columnHeader ) ; const newWidth = Math . min ( currentWidth , sizeInPx ) ; this . applyColumnSize ( _ , columnHeader , newWidth , currentWidth ) ; } } /**
 * The optimally performing resize strategy for flex mat-tables.
 * Tested against and outperformed:
 *   CSS selector w/ CSS variable
 *   Updating all mat-cell nodes
 */ @ Injectable ( ) export class CdkFlexTableResizeStrategy extends ResizeStrategy implements OnDestroy { private readonly _document : Document ; private readonly _columnIndexes = new Map < string , number > ( ) ; private readonly _columnProperties = new Map < string , Map < string , string > > ( ) ; private _styleElement ? : HTMLStyleElement ; private _indexSequence = 0 ; protected readonly defaultMinSize = 0 ; protected readonly defaultMaxSize = Number . MAX_SAFE_INTEGER ; constructor ( protected readonly columnResize , @ Inject ( _COALESCED_STYLE_SCHEDULER ) protected readonly styleScheduler , protected readonly table , @ Inject ( DOCUMENT ) document ) { super ( ) ; this . _document = document ; } applyColumnSize ( cssFriendlyColumnName , columnHeader , sizeInPx , previousSizeInPx ? ) { const delta = sizeInPx - ( previousSizeInPx ?? ( this . _getAppliedWidth ( cssFriendlyColumnName ) || columnHeader . offsetWidth ) ) ; if ( delta === 0 ) { return ; } const cssSize = coerceCssPixelValue ( sizeInPx ) ; this . _applyProperty ( cssFriendlyColumnName , 'flex' , `0 0.01 ${ cssSize }` ) ; this . updateTableWidthAndStickyColumns ( delta ) ; } applyMinColumnSize ( cssFriendlyColumnName , _ , sizeInPx ) { const cssSize = coerceCssPixelValue ( sizeInPx ) ; this . _applyProperty ( cssFriendlyColumnName , 'min-width' , cssSize , sizeInPx !== this . defaultMinSize ) ; this . updateTableWidthAndStickyColumns ( 0 ) ; } applyMaxColumnSize ( cssFriendlyColumnName , _ , sizeInPx ) { const cssSize = coerceCssPixelValue ( sizeInPx ) ; this . _applyProperty ( cssFriendlyColumnName , 'max-width' , cssSize , sizeInPx !== this . defaultMaxSize ) ; this . updateTableWidthAndStickyColumns ( 0 ) ; } protected getColumnCssClass ( cssFriendlyColumnName ) { return `cdk-column-${ cssFriendlyColumnName }` ; } ngOnDestroy ( ) { this . _styleElement ?. remove ( ) ; this . _styleElement = undefined ; } private _getPropertyValue ( cssFriendlyColumnName , key ) { const properties = this . _getColumnPropertiesMap ( cssFriendlyColumnName ) ; return properties . get ( key ) ; } private _getAppliedWidth ( cssFriendslyColumnName ) { return coercePixelsFromFlexValue ( this . _getPropertyValue ( cssFriendslyColumnName , 'flex' ) ) ; } private _applyProperty ( cssFriendlyColumnName , key , value , enable = true ) { const properties = this . _getColumnPropertiesMap ( cssFriendlyColumnName ) ; this . styleScheduler . schedule ( ( ) => { if ( enable ) { properties . set ( key , value ) ; } else { properties . delete ( key ) ; } this . _applySizeCss ( cssFriendlyColumnName ) ; } ) ; } private _getStyleSheet ( ) { if ( ! this . _styleElement ) { this . _styleElement = this . _document . createElement ( 'style' ) ; this . _styleElement . appendChild ( this . _document . createTextNode ( '' ) ) ; this . _document . head . appendChild ( this . _styleElement ) ; } return this . _styleElement . sheet as CSSStyleSheet ; } private _getColumnPropertiesMap ( cssFriendlyColumnName ) { let properties = this . _columnProperties . get ( cssFriendlyColumnName ) ; if ( properties === undefined ) { properties = new Map < string , string > ( ) ; this . _columnProperties . set ( cssFriendlyColumnName , properties ) ; } return properties ; } private _applySizeCss ( cssFriendlyColumnName ) { const properties = this . _getColumnPropertiesMap ( cssFriendlyColumnName ) ; const propertyKeys = Array . from ( properties . keys ( ) ) ; let index = this . _columnIndexes . get ( cssFriendlyColumnName ) ; if ( index === undefined ) { if ( ! propertyKeys . length ) { return ; } index = this . _indexSequence ++ ; this . _columnIndexes . set ( cssFriendlyColumnName , index ) ; } else { this . _getStyleSheet ( ) . deleteRule ( index ) ; } const columnClassName = this . getColumnCssClass ( cssFriendlyColumnName ) ; const tableClassName = this . columnResize . getUniqueCssClass ( ) ; const selector = `.${ tableClassName } .${ columnClassName }` ; const body = propertyKeys . map ( key => `${ key }:${ properties . get ( key ) }` ) . join ( ';' ) ; this . _getStyleSheet ( ) . insertRule ( `${ selector } {${ body }}` , index ! ) ; } } /** Converts CSS pixel values to numbers, eg "123px" to 123. Returns NaN for non pixel values. */ function coercePixelsFromCssValue ( cssValue ) { return Number ( cssValue . match ( /(\d+)px/ ) ?. [ 1 ] ) ; } /** Gets the style.width pixels on the specified element if present, otherwise its offsetWidth. */ function getElementWidth ( element ) { return coercePixelsFromCssValue ( element . style . width ) || element . offsetWidth ; } /**
 * Converts CSS flex values as set in CdkFlexTableResizeStrategy to numbers,
 * eg "0 0.01 123px" to 123.
 */ function coercePixelsFromFlexValue ( flexValue ) { return Number ( flexValue ?. match ( /0 0\.01 (\d+)px/ ) ?. [ 1 ] ) ; } export const TABLE_LAYOUT_FIXED_RESIZE_STRATEGY_PROVIDER = { provide : ResizeStrategy , useClass : TableLayoutFixedResizeStrategy , } ; export const FLEX_RESIZE_STRATEGY_PROVIDER = { provide : ResizeStrategy , useClass : CdkFlexTableResizeStrategy , } ;