import sanitize from 'uparser' ; import { persistent , createWalker , createTemplate , LRUWeakMap } from './shared' ; import { defaultCompiler , PatchType } from './compiler' ; import type { TemplateCompiler , TemplatePatcher } from './compiler' ; export type { TemplateCompiler } ; type TemplateType = 'html' | 'svg' ; /**
 * Tags to force text only from node content
 */ const TEXT_TAGS = [ 'style' , 'textarea' ] ; /**
 * Prefix to glue template strings by the interpolated value slot
 */ const PREFIX = 'isµ' ; /**
 * Cache to store precompiled templates indexed by the template strings
 * as a LRUCache it only stores the most recently 'X' used elements.
 * Effectively trading memory for performance and performance for memory.
 */ const contentCache = new LRUWeakMap < TemplateStringsArray , TemplateContent > ( 500 ) ; /**
 * Template cache, used with template to remember past renders
 */ export interface TemplateCache { stack : TemplateCache [ ] ; instance : TemplateInstance ; node : Node ; } /**
 * Compiled template from strings and patcher function
 */ interface TemplateContent { node : HTMLTemplateElement ; patches : TemplatePatch [ ] ; } /**
 * Patch information for TemplatePatcher node traversal
 */ interface TemplatePatch { type : PatchType ; attr : string ; path : number [ ] ; } /**
 * Represents a rendered template instance
 */ interface TemplateInstance { strings : TemplateStringsArray ; type : string ; patchers : TemplatePatcher [ ] ; root : DocumentFragment ; } returns private export function createCache ( ) { return { stack : [ ] , instance : null , node : null } ; } param node param type param attr returns function createPatch ( node , type , attr ? ) { const path = [ ] ; while ( node . parentNode ) { let i = 0 ; for ( let n = node ; ( n = n . previousSibling ) ; i ++ ) ; node = node . parentNode ; path . push ( i ) ; } return { attr , type , path } ; } param type param strings param scopeName returns function compileContent ( type , strings , scopeName ? ) { const isSvg = type === 'svg' ; const template = createTemplate ( isSvg , sanitize ( strings , PREFIX , isSvg ) ) ; const patches = [ ] ; const walker = createWalker ( template . content ) ; const len = strings . length - 1 ; let i = 0 ; let search = `${ PREFIX }${ i }` ; const styles = template . querySelectorAll ( 'style' ) ; if ( styles . length ) { const style = styles [ 0 ] ; template . insertBefore ( style , template . firstChild ) ; for ( let i = 1 ; i < styles . length ; i ++ ) { const el = styles [ i ] ; el . parentNode ?. removeChild ( el ) ; style . textContent += el . textContent ; } } while ( i < len ) { const node = walker . nextNode ( ) ; if ( ! node ) throw new Error ( 'Parsing error' ) ; if ( node . nodeType == Node . COMMENT_NODE ) { if ( node . textContent === search ) { node . textContent = '' ; patches [ i ] = createPatch ( node , PatchType . NODE ) ; search = `${ PREFIX }${ ++ i }` ; } } else { const el = node as Element ; let attr ; while ( ( attr = el . getAttribute ( search ) ) ) { el . removeAttribute ( search ) ; patches [ i ] = createPatch ( node , PatchType . ATTR , attr ) ; search = `${ PREFIX }${ ++ i }` ; } if ( TEXT_TAGS . includes ( el . localName ) && node . textContent . trim ( ) === `<!--${ search }-->` ) { node . textContent = '' ; patches [ i ] = createPatch ( node , PatchType . TEXT ) ; search = `${ PREFIX }${ ++ i }` ; } } } if ( scopeName && typeof window != 'undefined' ) { window . ShadyCSS ?. prepareTemplate ( template , scopeName ) ; } return { node : template , patches } ; } /**
 * Class for composing template content and patching
 */ export class Template { /**
     * Template type, html or svg
     */ readonly type : TemplateType ; /**
     * Template strings
     */ readonly strings : TemplateStringsArray ; /**
     * Interpolated template values
     */ readonly values : any [ ] ; param cache param values param compiler private private static updateValues ( cache , values , compiler ) { const { stack } = cache ; for ( let i = 0 ; i < values . length ; i ++ ) { const value = values [ i ] ; let cache ; if ( value instanceof Template ) { cache = stack [ i ] ?? createCache ( ) ; values [ i ] = value . update ( cache , compiler ) ; } else if ( Array . isArray ( value ) ) { cache = stack [ i ] ?? createCache ( ) ; Template . updateValues ( cache , value , compiler ) ; } stack [ i ] = cache ; } if ( values . length < stack . length ) { stack . splice ( values . length ) ; } } param type param strings param values constructor ( type , strings , values ) { this . type = type ; this . strings = strings ; this . values = values ; } param compiler param scopeName returns updateOnce ( compiler ? , scopeName ? ) { return this . update ( createCache ( ) , compiler , scopeName ) ; } param cache param compiler param scopeName returns update ( cache , compiler ? , scopeName ? ) { const { strings , type , values } = this ; let { instance } = cache ; if ( ! compiler ) { compiler = defaultCompiler ; } if ( ! instance || instance . strings !== strings || instance . type !== type ) { instance = ( cache . instance = this . compile ( compiler , scopeName ) ) ; } const { patchers } = instance ; Template . updateValues ( cache , values , compiler ) ; for ( let i = 0 ; i < values . length ; i ++ ) { patchers [ i ] ( values [ i ] ) ; } return cache . node ?? ( cache . node = persistent ( instance . root ) ) ; } param compiler param scopeName returns private private compile ( compiler , scopeName ? ) { const { strings , type } = this ; let content = contentCache . get ( strings ) ; if ( ! content ) { contentCache . set ( strings , ( content = compileContent ( type , strings , scopeName ) ) ) ; } const { patches , node } = content ; const root = document . importNode ( node . content , true ) ; const patchers = patches . map ( ( { type , attr , path } ) => { const node = path . reduceRight < Node > ( ( n , i ) => n . childNodes [ i ] , root ) as Element & Comment & Text ; return compiler ?. [ type ] ?. ( node , attr ) || defaultCompiler [ type ] ( node , attr ) as TemplatePatcher ; } ) ; return { strings , type , patchers , root } ; } }