export type VNodeInput = string | number | VNode ; const indent = ( level ) : string => '  ' . repeat ( level ) ; /**
 * Virtual Node for storing `sgml` style documents
 **/ export abstract class VNode { param level toString ( level = 0 ) { return indent ( level ) + '[VNode]' ; } abstract get textContent ( ) : string ; abstract set textContent ( v ) ; } /**
 * Virtual Text Node for storing text leaf nodes
 **/ export class VNodeText extends VNode { text : string ; constructor ( text ) { super ( ) ; this . text = text ; } toString ( level = 0 ) { return indent ( level ) + this . text ; } get textContent ( ) : string { return this . text ; } set textContent ( v ) { this . text = v ; } } /**
 * Virtual Element Node for storing tagged elements
 **/ export class VNodeElement extends VNode { tag : string ; attrs : Record < string , string > ; children : VNode [ ] ; constructor ( tag , attrs , children ) { super ( ) ; this . tag = tag ; this . attrs = attrs ; this . children = children ; } get textContent ( ) : string { if ( this . children . length === 0 ) return '' ; if ( this . children . length === 1 ) { return this . children [ 0 ] . textContent ; } return this . children . map ( ( c ) => c . textContent ) . join ( '' ) ; } set textContent ( v ) { this . children = [ new VNodeText ( v ) ] ; } toString ( level = 0 ) { const attrs = this . toStringAttrs ( ) ; const padding = indent ( level ) ; const children = this . toStringChildren ( level ) ; const result = `${ padding }<${ this . tag }${ attrs }` ; if ( children === '' ) return `${ result } />` ; return `${ result }>${ children }</${ this . tag }>` ; } param tag * tags ( tag ) { if ( this . tag === tag ) yield this ; for ( const child of this . elementChildren ( ) ) yield * child . tags ( tag ) ; return null ; } param tags find ( ... rest ) ; find ( tag , ... rest ) { for ( const node of this . tags ( tag ) ) { if ( node === this ) continue ; if ( rest . length === 0 ) return node ; const child = node . find ( ... rest ) ; if ( child != null ) return child ; } return null ; } /** Iterate over VNodeElement children */ * elementChildren ( ) { for ( const child of this . children ) if ( child instanceof VNodeElement ) yield child ; return null ; } private toStringAttrs ( ) { const keys = Object . keys ( this . attrs ) ; if ( keys . length === 0 ) { return '' ; } let out = '' ; for ( const key of keys ) { const val = this . attrs [ key ] ; if ( val == null ) continue ; out += ` ${ key }="${ val }"` ; } return out ; } private toStringChildren ( level = 0 ) { if ( this . children . length === 0 ) return '' ; if ( this . children . length === 1 ) { const n1 = this . children [ 0 ] ; if ( n1 instanceof VNodeText ) return n1 . text ; } return `\n${ this . children . map ( ( c ) => c . toString ( level + 1 ) ) . join ( `\n` ) }\n${ indent ( level ) }` ; } } param children function normalizeChildren ( children ? ) { if ( children == null ) { return [ ] ; } if ( Array . isArray ( children ) ) { const childNodes = [ ] ; for ( const c of children ) { if ( c == null ) continue ; if ( typeof c === 'string' || typeof c === 'number' ) { childNodes . push ( new VNodeText ( String ( c ) ) ) ; continue ; } childNodes . push ( c ) ; } return childNodes ; } else if ( typeof children === 'string' || typeof children === 'number' ) { return [ new VNodeText ( String ( children ) ) ] ; } return [ children ] ; } example param tag param attrs param children export function V ( tag ) ; export function V ( tag , children ) ; export function V ( tag , attrs , children ? ) ; export function V ( tag , arg1 ? , children ? ) { const hasAttrs = typeof arg1 === 'object' && ! Array . isArray ( arg1 ) ; if ( ! hasAttrs ) { if ( children != null ) throw new Error ( 'Invalid input' ) ; children = arg1 ; } return new VNodeElement ( tag , hasAttrs ? arg1 : { } , normalizeChildren ( children ) ) ; }