11import type * as Types from '@app/Types.ts'
22import Constant from '@app/Constant.ts'
33import Attrs from '@app/Render/Attrs.ts'
4+ import Element from '@app/Render/Element.ts'
45import Layout from '@app/Render/Layout.ts'
5- import RenderElement from '@app/Render/Element.ts'
66import Style from '@app/Render/Style.ts'
77
8+ /** DOM Element type alias (avoid clash with imported Element class). */
9+ type DomElement = InstanceType < typeof globalThis . Element >
10+
811/**
912 * Renders schema to DOM.
1013 * @description Builds elements from nodes and applies layout and style.
1114 */
1215export default class Render {
1316 /** SVG root tag name. */
14- static readonly svgTagName : string = RenderElement . svgTagName
17+ static readonly svgTagName : string = Element . svgTagName
1518 /** Template element tag name. */
16- static readonly templateTagName : string = RenderElement . templateTagName
19+ static readonly templateTagName : string = Element . templateTagName
1720
1821 /**
1922 * Apply attribute map to element.
2023 * @description Sets class, value, style string, and generic attributes.
2124 * @param element - Target DOM element
2225 * @param attrs - Attribute key-value map
2326 */
24- static applyAttrs ( element : Element , attrs : Types . Attrs ) : void {
27+ static applyAttrs ( element : DomElement , attrs : Types . Attrs ) : void {
2528 Attrs . applyAttrs ( element , attrs )
2629 }
2730
@@ -63,26 +66,55 @@ export default class Render {
6366 * @param isSvg - Whether parent is SVG context
6467 * @returns Created DOM element
6568 */
66- static createElementForNode ( node : Types . Node , doc : Document , isSvg : boolean ) : Element {
67- return RenderElement . createElementForNode ( node , doc , isSvg )
69+ static createElementForNode ( node : Types . Node , doc : Document , isSvg : boolean ) : DomElement {
70+ return Element . createElementForNode ( node , doc , isSvg )
6871 }
6972
7073 /**
7174 * Render schema root into container.
72- * @description Appends each root node result to container.
75+ * @description Appends each root node result to container; optional refs and onNodeMount .
7376 * @param schema - Frozen schema
7477 * @param container - Target HTML element
78+ * @param options - Optional refs map and onNodeMount callback
7579 */
76- static render ( schema : Types . Schema , container : HTMLElement ) : void {
80+ static render ( schema : Types . Schema , container : HTMLElement , options ?: Types . RenderOptions ) : void {
7781 const ownerDoc = container . ownerDocument ?? document
82+ let renderContext :
83+ | {
84+ refs ?: Map < string , DomElement >
85+ onNodeMount ?: ( node : Types . Node , element : DomElement ) => void
86+ mounted ?: Array < { node : Types . Node ; element : DomElement } >
87+ }
88+ | undefined
89+ if ( options ) {
90+ renderContext = { }
91+ if ( options . refs !== undefined ) {
92+ renderContext . refs = options . refs
93+ }
94+ if ( options . onNodeMount !== undefined ) {
95+ renderContext . onNodeMount = options . onNodeMount
96+ renderContext . mounted = [ ]
97+ }
98+ if ( Object . keys ( renderContext ) . length === 0 ) {
99+ renderContext = undefined
100+ }
101+ }
78102 for ( const node of schema . root ) {
79- const result = Render . renderNode ( node , ownerDoc , false )
80- if ( result instanceof DocumentFragment ) {
81- while ( result . firstChild ) {
82- container . appendChild ( result . firstChild )
103+ const nodeResult = Render . renderNode ( node , ownerDoc , false , renderContext )
104+ if ( nodeResult instanceof DocumentFragment ) {
105+ while ( nodeResult . firstChild ) {
106+ container . appendChild ( nodeResult . firstChild )
83107 }
84108 } else {
85- container . appendChild ( result )
109+ container . appendChild ( nodeResult )
110+ }
111+ if ( renderContext ?. refs && node . id ) {
112+ renderContext . refs . set ( node . id , nodeResult as DomElement )
113+ }
114+ }
115+ if ( renderContext ?. onNodeMount && renderContext . mounted ) {
116+ for ( const { node, element } of renderContext . mounted ) {
117+ renderContext . onNodeMount ( node , element )
86118 }
87119 }
88120 }
@@ -93,16 +125,27 @@ export default class Render {
93125 * @param node - Schema node
94126 * @param doc - Document to create in
95127 * @param parentIsSvg - Whether parent is SVG context
128+ * @param renderContext - Optional context for refs and onNodeMount
96129 * @returns Created element or DocumentFragment for template
97130 */
98131 static renderNode (
99132 node : Types . Node ,
100133 doc : Document ,
101- parentIsSvg : boolean
102- ) : Element | DocumentFragment {
103- const tag = node . type
104- const isSvg = parentIsSvg || tag === Render . svgTagName
134+ parentIsSvg : boolean ,
135+ renderContext ?: {
136+ refs ?: Map < string , DomElement >
137+ mounted ?: Array < { node : Types . Node ; element : DomElement } >
138+ }
139+ ) : DomElement | DocumentFragment {
140+ const tagName = node . type
141+ const isSvg = parentIsSvg || tagName === Render . svgTagName
105142 const element = Render . createElementForNode ( node , doc , isSvg )
143+ if ( renderContext ?. refs && node . id ) {
144+ renderContext . refs . set ( node . id , element as DomElement )
145+ }
146+ if ( renderContext ?. mounted ) {
147+ renderContext . mounted . push ( { node, element : element as DomElement } )
148+ }
106149 if ( node . id && element instanceof HTMLElement ) {
107150 element . id = node . id
108151 }
@@ -130,21 +173,27 @@ export default class Render {
130173 element . appendChild ( doc . createTextNode ( node . content ) )
131174 }
132175 }
133- if ( ! Constant . voidTags . has ( tag ) && node . children && node . children . length > 0 ) {
134- let target : DocumentFragment | Element
176+ if ( ! Constant . voidTags . has ( tagName ) && node . children && node . children . length > 0 ) {
177+ let appendTarget : DocumentFragment | DomElement
135178 if ( element instanceof HTMLTemplateElement ) {
136- target = element . content
179+ appendTarget = element . content
137180 } else {
138- target = element
181+ appendTarget = element
139182 }
140183 for ( const child of node . children ) {
141- const childResult = Render . renderNode ( child , doc , isSvg )
184+ const childResult = Render . renderNode ( child , doc , isSvg , renderContext )
142185 if ( childResult instanceof DocumentFragment ) {
143186 while ( childResult . firstChild ) {
144- target . appendChild ( childResult . firstChild )
187+ appendTarget . appendChild ( childResult . firstChild )
145188 }
146189 } else {
147- target . appendChild ( childResult )
190+ appendTarget . appendChild ( childResult )
191+ }
192+ if ( renderContext ?. refs && child . id ) {
193+ renderContext . refs . set ( child . id , childResult as DomElement )
194+ }
195+ if ( renderContext ?. mounted ) {
196+ renderContext . mounted . push ( { node : child , element : childResult as DomElement } )
148197 }
149198 }
150199 }
0 commit comments