import { diff } from './shared' ; import { isObject } from '@kirei/shared' ; /**
 * Template patcher function to update a dynamic value in the DOM
 */ export type TemplatePatcher = ( pending : unknown ) => void ; /**
 * Type of patcher to use for compiling a TemplatePatcher
 */ export enum PatchType { NODE = 'node' , ATTR = 'attr' , TEXT = 'text' } /**
 * Used to configure how the template values are translated into the DOM,
 * each method should return a function which takes one parameter as the interpolated value.
 * Otherwise it fallbacks to the default compiler.
 */ export interface TemplateCompiler < T = TemplatePatcher | void > { param node param attr returns attr ? ( node , attr ) : T ; param ref returns node ? ( ref ) : T ; param node returns text ? ( node ) : T ; } param attr param value returns function mapAttribute < T extends object > ( attr , value ) { if ( ! isObject ( value ) ) { return value ; } else if ( Array . isArray ( value ) ) { if ( attr == 'style' ) { throw new TypeError ( 'Style attribute expressions cannot map array' ) ; } return value . filter ( x => x ) . join ( ' ' ) ; } const keys = Object . keys ( value ) . filter ( key => value [ key ] ) ; if ( attr == 'style' ) { return keys . map ( key => `${ key }=${ value [ key ] }` ) . join ( ';' ) ; } return keys . join ( ' ' ) ; } param node param name returns private function propPatcher ( node , name ) { return ( pending ) => { node [ name ] = pending ; } ; } param node param name returns private function eventPatcher ( node , name ) { let listener ; function boundListener ( e ) { return listener . call ( node , e ) ; } return ( pending ) => { if ( listener !== pending ) { if ( pending == null ) { node . removeEventListener ( name , boundListener , false ) ; } else if ( listener == null ) { node . addEventListener ( name , boundListener , false ) ; } listener = pending ; } } ; } /**
 * Default template compiler
 */ export const defaultCompiler = { attr ( node , attr ) { const prefix = attr [ 0 ] ; if ( prefix == '.' ) { return propPatcher ( node , attr . slice ( 1 ) ) ; } else if ( prefix == '@' ) { return eventPatcher ( node , attr . slice ( 1 ) ) ; } const shouldMap = attr == 'class' || attr == 'style' ; let value ; return ( pending ) => { if ( value === pending ) return ; value = pending ; if ( value == null ) { node . removeAttribute ( attr ) ; } else { node . setAttribute ( attr , shouldMap ? mapAttribute ( attr , value ) : value ) ; } } ; } , node ( ref ) { let nodes = [ ] ; let value ; let text ; const nodeParser = ( pending ) => { if ( pending == null ) { if ( value ) { value = pending ; nodes = diff ( ref , nodes , [ ] ) ; } } else if ( ! isObject ( pending ) ) { if ( value !== pending ) { value = pending ; if ( ! text ) { text = document . createTextNode ( value ) ; } else { text . textContent = value ; } nodes = diff ( ref , nodes , [ text ] ) ; } } else if ( 'nodeType' in pending && pending !== value ) { value = pending instanceof DocumentFragment ? Array . from ( pending . childNodes ) : [ pending ] ; nodes = diff ( ref , nodes , value ) ; } else if ( Array . isArray ( pending ) ) { value = pending ; if ( value . length === 0 || isObject ( value [ 0 ] ) ) { nodes = diff ( ref , nodes , value ) ; } else if ( value !== pending ) { nodeParser ( String ( pending ) ) ; } } else { throw new TypeError ( 'Invalid node expression in html template' ) ; } } ; return nodeParser ; } , text ( node ) { let value ; return ( pending ) => { if ( value !== pending ) { value = pending ; node . textContent = value == null ? '' : value ; } } ; } , } ;