import { IHTMLElement , IShadowRoot , Node } from 'happy-dom' ; import ShadowRootCSSCache from './ShadowRootCSSCache' ; import ShadowRootCSSRenderer from './ShadowRootCSSRenderer' ; import IHappyDOMServerRenderOptions from '../IHappyDOMServerRenderOptions' ; /**
 * Scopes elements and CSS inside shadow roots.
 */ export default class ShadowRootRenderer { private renderOptions : IHappyDOMServerRenderOptions ; private cssCache : ShadowRootCSSCache = new ShadowRootCSSCache ( ) ; param renderOptions constructor ( renderOptions ) { this . renderOptions = renderOptions ; } param element param cssCache param options returns public getScopedClone ( element ) { const clone = < IHTMLElement > element . cloneNode ( true ) ; ( < IShadowRoot > clone . shadowRoot ) = element . shadowRoot . cloneNode ( true ) ; this . extractAndScopeCSS ( clone ) ; this . moveChildNodesIntoSlots ( clone ) ; return clone ; } returns public getScopedCSS ( ) { return this . cssCache . getAllScoped ( ) ; } param element private moveChildNodesIntoSlots ( element ) { const slotChildren = { } ; const slots = Array . from ( element . shadowRoot . querySelectorAll ( 'slot' ) ) ; for ( let i = 0 , max = element . children . length ; i < max ; i ++ ) { const child = element . children [ i ] ; const name = child . getAttribute ( 'slot' ) || 'default' ; slotChildren [ name ] = slotChildren [ name ] || [ ] ; slotChildren [ name ] . push ( child ) ; } for ( const slot of slots ) { const name = slot . getAttribute ( 'name' ) || 'default' ; if ( slotChildren [ name ] ) { for ( const child of slotChildren [ name ] ) { slot . parentNode . insertBefore ( child , slot ) ; } slot . parentNode . removeChild ( slot ) ; } } if ( element . childNodes . length > 0 ) { console . warn ( 'Warning! Custom element "' + element . tagName + '" did not have any matching slot for ' + element . childNodes . length + ' child node(s).' ) ; for ( const child of Array . from ( element . childNodes ) ) { element . removeChild ( child ) ; } } } param element private extractAndScopeCSS ( element ) { const options = this . renderOptions ; const cache = this . cssCache ; if ( options . extractCSS && options . scopeCSS ) { const css = this . extractCSS ( element . shadowRoot ) ; const scopeID = cache . getScopeID ( css ) ; let scopedCSS = cache . getScoped ( css ) ; if ( ! scopedCSS ) { scopedCSS = ShadowRootCSSRenderer . scope ( css , scopeID , element . tagName ) ; cache . setScoped ( css , scopedCSS ) ; } element . classList . add ( scopeID ) ; this . scopeChildElements ( element . shadowRoot , scopeID ) ; } else if ( options . extractCSS ) { const css = this . extractCSS ( element . shadowRoot ) ; cache . setScoped ( css , css ) ; } else if ( options . scopeCSS ) { const styles = Array . from ( element . shadowRoot . querySelectorAll ( 'style' ) ) ; for ( const style of styles ) { const css = style . textContent ; const scopeID = cache . getScopeID ( css ) ; element . classList . add ( scopeID ) ; style . textContent = ShadowRootCSSRenderer . scope ( css , scopeID , element . tagName ) ; cache . setScoped ( css , style . textContent ) ; this . scopeChildElements ( element . shadowRoot , scopeID ) ; } } } param shadowRoot returns private extractCSS ( shadowRoot ) { const styles = Array . from ( shadowRoot . querySelectorAll ( 'style' ) ) ; let css = '' ; for ( const style of styles ) { style . parentNode . removeChild ( style ) ; css += style . textContent ; } return css ; } param element param id private scopeChildElements ( element , id ) { if ( element . nodeType === Node . ELEMENT_NODE ) { ( < IHTMLElement > element ) . classList . add ( id ) ; } if ( element . nodeType === Node . DOCUMENT_FRAGMENT_NODE || ( element . nodeType === Node . ELEMENT_NODE && ( < IHTMLElement > element ) . tagName !== 'slot' ) ) { for ( let i = 0 , max = element . children . length ; i < max ; i ++ ) { const child = element . children [ i ] ; this . scopeChildElements ( < IHTMLElement > child , id ) ; } } } }