2020
2121const MASK_SVG = `url("data:image/svg+xml,%3Csvg%20xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg'%20viewBox%3D'0%200%2024%2024'%20fill%3D'none'%20stroke%3D'black'%20stroke-width%3D'1.75'%3E%3Cpath%20d%3D'M15%203h6v6'%2F%3E%3Cpath%20d%3D'M10%2014%2021%203'%2F%3E%3Cpath%20d%3D'M18%2013v6a2%202%200%200%201-2%202H5a2%202%200%200%201-2-2V8a2%202%200%200%201%202-2h6'%2F%3E%3C%2Fsvg%3E")`
2222const DEFAULT_PLAYGROUND_HTML = '<div id="root"></div>'
23- const GET_ELEMENT_BY_ID_RE = / d o c u m e n t \. g e t E l e m e n t B y I d \( \s * ( [ ' " ` ] ) ( [ ^ ' " ` ] + ) \1\s * \) /
23+ const DIRECT_MOUNT_PATTERNS = [
24+ / d o c u m e n t \. g e t E l e m e n t B y I d \( \s * ( [ ' " ` ] ) ( [ ^ ' " ` ] + ) \1\s * \) \s * \. \s * (?: a p p e n d C h i l d | a p p e n d | r e p l a c e C h i l d r e n ) \s * \( / ,
25+ / (?: k o | t k o ) \. a p p l y B i n d i n g s \s * \( [ \s \S ] * ?, \s * d o c u m e n t \. g e t E l e m e n t B y I d \( \s * ( [ ' " ` ] ) ( [ ^ ' " ` ] + ) \1\s * \) \s * \) / ,
26+ / t k o \. j s x \. r e n d e r \s * \( [ \s \S ] * ?, \s * d o c u m e n t \. g e t E l e m e n t B y I d \( \s * ( [ ' " ` ] ) ( [ ^ ' " ` ] + ) \1\s * \) \s * \) / ,
27+ / (?: R e a c t D O M \. ) ? c r e a t e R o o t \s * \( \s * d o c u m e n t \. g e t E l e m e n t B y I d \( \s * ( [ ' " ` ] ) ( [ ^ ' " ` ] + ) \1\s * \) \s * \) \s * \. r e n d e r \s * \( / ,
28+ / R e a c t D O M \. r e n d e r \s * \( [ \s \S ] * ?, \s * d o c u m e n t \. g e t E l e m e n t B y I d \( \s * ( [ ' " ` ] ) ( [ ^ ' " ` ] + ) \1\s * \) \s * \) /
29+ ]
30+ const ELEMENT_REF_RE =
31+ / (?: c o n s t | l e t | v a r ) \s + ( [ A - Z a - z _ $ ] [ \w $ ] * ) \s * = \s * d o c u m e n t \. g e t E l e m e n t B y I d \( \s * ( [ ' " ` ] ) ( [ ^ ' " ` ] + ) \2\s * \) / g
2432
2533function h ( tag , props , children = [ ] ) {
2634 return {
@@ -79,23 +87,40 @@ function indentBlock(code, spaces) {
7987 . join ( '\n' )
8088}
8189
90+ function findExplicitMountId ( code ) {
91+ for ( const pattern of DIRECT_MOUNT_PATTERNS ) {
92+ const match = code . match ( pattern )
93+ if ( match ) return match [ 2 ]
94+ }
95+
96+ for ( const match of code . matchAll ( ELEMENT_REF_RE ) ) {
97+ const [ , refName , , id ] = match
98+ const refPattern = new RegExp (
99+ String . raw `\b${ refName } \b\s*\.\s*(?:appendChild|append|replaceChildren)\s*\(|` +
100+ String . raw `(?:^|\W)(?:ko|tko)\.applyBindings\s*\([\s\S]*?,\s*${ refName } \b|` +
101+ String . raw `tko\.jsx\.render\s*\([\s\S]*?,\s*${ refName } \b|` +
102+ String . raw `(?:ReactDOM\.)?createRoot\s*\(\s*${ refName } \b\s*\)\s*\.render\s*\(|` +
103+ String . raw `ReactDOM\.render\s*\([\s\S]*?,\s*${ refName } \b` ,
104+ 'm'
105+ )
106+ if ( refPattern . test ( code ) ) return id
107+ }
108+
109+ return null
110+ }
111+
82112function inferPlaygroundHtml ( tsx ) {
83- const mountId = tsx . match ( GET_ELEMENT_BY_ID_RE ) ?. [ 2 ]
113+ const mountId = findExplicitMountId ( tsx )
84114 return mountId ? `<div id="${ mountId } "></div>` : DEFAULT_PLAYGROUND_HTML
85115}
86116
87117function wrapTsxForPlayground ( tsx ) {
88118 const code = tsx . trim ( )
89119 if ( ! code ) return code
90120
91- // Hand-authored full examples should keep their explicit setup.
92- if (
93- / t k o \. j s x \. r e n d e r \s * \( / . test ( code ) ||
94- / (?: ^ | \W ) (?: k o | t k o ) \. a p p l y B i n d i n g s \s * \( / . test ( code ) ||
95- / d o c u m e n t \. g e t E l e m e n t B y I d \s * \( / . test ( code )
96- ) {
97- return code
98- }
121+ // Hand-authored full examples should keep their explicit setup only when
122+ // they already include a concrete mount target.
123+ if ( findExplicitMountId ( code ) ) return code
99124
100125 const blocks = code . split ( / \n \s * \n / )
101126 const jsxIndex = blocks . findIndex ( block => looksLikeJsxExpression ( block . trim ( ) ) )
@@ -108,13 +133,15 @@ function wrapTsxForPlayground(tsx) {
108133
109134 let wrapped = ''
110135 if ( prelude ) wrapped += `${ prelude } \n\n`
111- wrapped += '// boilerplate added by the docs playground\n'
112- wrapped += "const root = document.getElementById('root')\n"
113- wrapped += 'const { node } = tko.jsx.render(\n'
114- wrapped += `${ indentBlock ( jsxBlock , 2 ) } \n`
115- wrapped += ')\n'
116- wrapped += 'root.appendChild(node)\n'
117- wrapped += 'tko.applyBindings({}, root)'
136+ wrapped += '{\n'
137+ wrapped += ' // boilerplate added by the docs playground\n'
138+ wrapped += " const __docsPlaygroundRoot = document.getElementById('root')\n"
139+ wrapped += ' const __docsPlaygroundRendered = tko.jsx.render(\n'
140+ wrapped += `${ indentBlock ( jsxBlock , 4 ) } \n`
141+ wrapped += ' )\n'
142+ wrapped += ' __docsPlaygroundRoot.appendChild(__docsPlaygroundRendered.node)\n'
143+ wrapped += ' tko.applyBindings({}, __docsPlaygroundRoot)\n'
144+ wrapped += '}'
118145 return wrapped
119146}
120147
0 commit comments