interface ICursorTransition { suffix : string ; property : string ; fn : ( element : Element ) => Element | null ; } class Cursor { private static readonly first = { suffix : "f" , property : "firstElementChild" , fn : ( element ) => element . firstElementChild } ; private static readonly last = { suffix : "l" , property : "lastElementChild" , fn : ( element ) => element . lastElementChild } ; private static readonly next = { suffix : "n" , property : "nextElementSibling" , fn : ( element ) => element . nextElementSibling } ; private static readonly previous = { suffix : "p" , property : "previousElementSibling" , fn : ( element ) => element . previousElementSibling } ; public readonly pathFns = new Map < string , ( element : Element ) => Element > ( ) ; private element : Element | null = null ; private path : string = "" ; private pathName : string = "" ; public start ( root ) { this . path = "return root" ; this . pathName = "" ; this . element = root ; } public first ( ) { this . moveTo ( Cursor . first ) ; } public last ( ) { this . moveTo ( Cursor . last ) ; } public next ( ) { this . moveTo ( Cursor . next ) ; } public previous ( ) { this . moveTo ( Cursor . previous ) ; } public child ( index ) { let _index = index ; let delta = this . element . childElementCount - _index ; if ( _index < delta ) { this . first ( ) ; while ( _index -- > 0 ) { this . next ( ) ; } } else { this . last ( ) ; while ( -- delta > 0 ) { this . previous ( ) ; } } } public end ( ) { let pathFn = this . pathFns . get ( this . pathName ) ; if ( ! pathFn ) { pathFn = new Function ( "root" , this . path ) as ( element : Element ) => Element ; this . pathFns . set ( this . pathName , pathFn ) ; } return pathFn ; } private moveTo ( transition ) { this . path += `.${ transition . property }` ; this . pathName += transition . suffix ; this . element = transition . fn ( this . element ) ; } } export interface ITemplateNode { tag : string ; classList ? : string [ ] ; props ? : Record < string , unknown > ; children ? : ITemplateNode [ ] ; ref ? : string ; } export class Template { private static readonly cursor = new Cursor ( ) ; private static build ( vnode , path , refs ) { const element = Object . assign ( document . createElement ( vnode . tag ) , vnode . props ) ; if ( vnode . classList ) { element . classList . add ( ... vnode . classList ) ; } if ( vnode . children ) { const level = path . length ; path . push ( 0 ) ; for ( const child of vnode . children ) { element . appendChild ( Template . build ( child , path , refs ) ) ; path [ level ] ++ ; } path . pop ( ) ; } if ( vnode . ref ) { refs . set ( vnode . ref , path . slice ( 0 ) ) ; } return element ; } private readonly content : Element ; private readonly refs = new Map < string , ( root : Element ) => Element > ( ) ; constructor ( content ) { const refToPath = new Map < string , number [ ] > ( ) ; this . content = Template . build ( content , [ ] , refToPath ) ; const cursor = Template . cursor ; const refs = this . refs ; for ( const [ ref , path ] of refToPath ) { cursor . start ( this . content ) ; for ( const index of path ) { cursor . child ( index ) ; } refs . set ( ref , cursor . end ( ) ) ; } } public get ( root , name ) { return this . refs . get ( name ) ! ( root ) ; } public clone ( ) { return document . importNode ( this . content , true ) ; } }