@@ -5,11 +5,14 @@ const statusNode = document.getElementById('status')
55const renderMode = document . getElementById ( 'render-mode' )
66const autoRenderToggle = document . getElementById ( 'auto-render' )
77const renderButton = document . getElementById ( 'render-button' )
8+ const copyComponentButton = document . getElementById ( 'copy-component' )
9+ const clearComponentButton = document . getElementById ( 'clear-component' )
810const styleMode = document . getElementById ( 'style-mode' )
11+ const copyStylesButton = document . getElementById ( 'copy-styles' )
12+ const clearStylesButton = document . getElementById ( 'clear-styles' )
913const shadowToggle = document . getElementById ( 'shadow-toggle' )
1014const jsxEditor = document . getElementById ( 'jsx-editor' )
1115const cssEditor = document . getElementById ( 'css-editor' )
12- const previewHost = document . getElementById ( 'preview-host' )
1316const styleWarning = document . getElementById ( 'style-warning' )
1417const cdnLoading = document . getElementById ( 'cdn-loading' )
1518
@@ -66,6 +69,7 @@ button:focus-visible {
6669jsxEditor . value = defaultJsx
6770cssEditor . value = defaultCss
6871
72+ let previewHost = document . getElementById ( 'preview-host' )
6973let jsxCodeEditor = null
7074let cssCodeEditor = null
7175let getJsxSource = ( ) => jsxEditor . value
@@ -196,7 +200,87 @@ const debounceRender = () => {
196200 scheduled = setTimeout ( renderPreview , 200 )
197201}
198202
199- const getShadowRoot = ( ) => {
203+ const setJsxSource = value => {
204+ if ( jsxCodeEditor ) {
205+ jsxCodeEditor . setValue ( value )
206+ }
207+ jsxEditor . value = value
208+ }
209+
210+ const setCssSource = value => {
211+ if ( cssCodeEditor ) {
212+ cssCodeEditor . setValue ( value )
213+ }
214+ cssEditor . value = value
215+ }
216+
217+ const clearComponentSource = ( ) => {
218+ setJsxSource ( '' )
219+ maybeRender ( )
220+ }
221+
222+ const clearStylesSource = ( ) => {
223+ setCssSource ( '' )
224+ maybeRender ( )
225+ }
226+
227+ const copyTextToClipboard = async text => {
228+ if ( navigator . clipboard ?. writeText ) {
229+ await navigator . clipboard . writeText ( text )
230+ return
231+ }
232+
233+ const fallbackInput = document . createElement ( 'textarea' )
234+ fallbackInput . value = text
235+ fallbackInput . setAttribute ( 'readonly' , 'true' )
236+ fallbackInput . style . position = 'absolute'
237+ fallbackInput . style . left = '-9999px'
238+ document . body . append ( fallbackInput )
239+ fallbackInput . select ( )
240+ const ok = document . execCommand ( 'copy' )
241+ fallbackInput . remove ( )
242+
243+ if ( ! ok ) {
244+ throw new Error ( 'Clipboard copy is not available in this browser context.' )
245+ }
246+ }
247+
248+ const copyComponentSource = async ( ) => {
249+ try {
250+ await copyTextToClipboard ( getJsxSource ( ) )
251+ setStatus ( 'Component copied' )
252+ } catch {
253+ setStatus ( 'Copy failed' )
254+ }
255+ }
256+
257+ const copyStylesSource = async ( ) => {
258+ try {
259+ await copyTextToClipboard ( getCssSource ( ) )
260+ setStatus ( 'Styles copied' )
261+ } catch {
262+ setStatus ( 'Copy failed' )
263+ }
264+ }
265+
266+ const recreatePreviewHost = ( ) => {
267+ const nextHost = document . createElement ( 'div' )
268+ nextHost . id = 'preview-host'
269+ nextHost . className = previewHost . className
270+ previewHost . replaceWith ( nextHost )
271+ previewHost = nextHost
272+ }
273+
274+ const getRenderTarget = ( ) => {
275+ if ( ! shadowToggle . checked && previewHost . shadowRoot ) {
276+ /* ShadowRoot cannot be detached, so recreate the host for light DOM mode. */
277+ if ( reactRoot ) {
278+ reactRoot . unmount ( )
279+ reactRoot = null
280+ }
281+ recreatePreviewHost ( )
282+ }
283+
200284 if ( shadowToggle . checked ) {
201285 if ( ! previewHost . shadowRoot ) {
202286 previewHost . attachShadow ( { mode : 'open' } )
@@ -605,7 +689,7 @@ const ensureReactRuntime = async () => {
605689
606690const renderDom = async ( ) => {
607691 const { jsx } = await ensureCoreRuntime ( )
608- const target = getShadowRoot ( )
692+ const target = getRenderTarget ( )
609693 clearTarget ( target )
610694 const compiledStyles = await compileStyles ( )
611695 applyStyles ( target , compiledStyles . css )
@@ -627,7 +711,7 @@ const renderDom = async () => {
627711}
628712
629713const renderReact = async ( ) => {
630- const target = getShadowRoot ( )
714+ const target = getRenderTarget ( )
631715 clearTarget ( target )
632716 const compiledStyles = await compileStyles ( )
633717 applyStyles ( target , compiledStyles . css )
@@ -666,7 +750,7 @@ const renderPreview = async () => {
666750 setStatus ( 'Rendered' )
667751 } catch ( error ) {
668752 setStatus ( 'Error' )
669- const target = getShadowRoot ( )
753+ const target = getRenderTarget ( )
670754 clearTarget ( target )
671755 const message = document . createElement ( 'pre' )
672756 message . textContent = error instanceof Error ? error . message : String ( error )
@@ -686,6 +770,10 @@ const maybeRender = () => {
686770 }
687771}
688772
773+ const updateRenderButtonVisibility = ( ) => {
774+ renderButton . hidden = autoRenderToggle . checked
775+ }
776+
689777renderMode . addEventListener ( 'change' , maybeRender )
690778styleMode . addEventListener ( 'change' , ( ) => {
691779 if ( cssCodeEditor ) {
@@ -695,14 +783,24 @@ styleMode.addEventListener('change', () => {
695783} )
696784shadowToggle . addEventListener ( 'change' , maybeRender )
697785autoRenderToggle . addEventListener ( 'change' , ( ) => {
786+ updateRenderButtonVisibility ( )
698787 if ( autoRenderToggle . checked ) {
699788 renderPreview ( )
700789 }
701790} )
702791renderButton . addEventListener ( 'click' , renderPreview )
792+ copyComponentButton . addEventListener ( 'click' , ( ) => {
793+ void copyComponentSource ( )
794+ } )
795+ clearComponentButton . addEventListener ( 'click' , clearComponentSource )
796+ copyStylesButton . addEventListener ( 'click' , ( ) => {
797+ void copyStylesSource ( )
798+ } )
799+ clearStylesButton . addEventListener ( 'click' , clearStylesSource )
703800jsxEditor . addEventListener ( 'input' , maybeRender )
704801cssEditor . addEventListener ( 'input' , maybeRender )
705802
803+ updateRenderButtonVisibility ( )
706804setStyleCompiling ( false )
707805setCdnLoading ( true )
708806void initializeCodeEditors ( )
0 commit comments