@@ -48,7 +48,9 @@ type BlockRegistryEntry = {
4848 id : string
4949 container : HTMLElement
5050 diagramHost : HTMLElement
51- collapseButton : HTMLButtonElement
51+ codeHost : HTMLElement
52+ setView : ( view : 'diagram' | 'code' , options ?: { userInitiated ?: boolean } ) => void
53+ userSelectedView : 'diagram' | 'code' | null
5254 downloadSvgButton : HTMLButtonElement
5355 downloadPngButton : HTMLButtonElement
5456 lastSvg : string | null
@@ -292,6 +294,12 @@ async function renderBlock(block: MermaidBlock) {
292294 registry . lastRenderId = renderId
293295 updateDownloadButtons ( registry )
294296 cleanupGhostNodes ( renderId , diagramHost . ownerDocument )
297+
298+ if ( registry . userSelectedView !== 'code' ) {
299+ registry . setView ( 'diagram' )
300+ } else {
301+ registry . setView ( 'code' )
302+ }
295303 } catch ( err ) {
296304 if ( container . dataset [ BLOCK_DATA_SOURCE ] !== source ) {
297305 cleanupGhostNodes ( renderId , diagramHost . ownerDocument )
@@ -305,6 +313,8 @@ async function renderBlock(block: MermaidBlock) {
305313 registry . lastRenderId = undefined
306314 updateDownloadButtons ( registry )
307315 cleanupGhostNodes ( renderId , diagramHost . ownerDocument )
316+
317+ registry . setView ( 'diagram' )
308318 }
309319}
310320
@@ -342,12 +352,18 @@ function ensureContainer(pre: HTMLElement): BlockRegistryEntry {
342352 actionGroup . style . alignItems = 'center'
343353 actionGroup . style . gap = '0.5rem'
344354
345- const collapseButton = createActionButton ( doc , 'Hide diagram' )
355+ const viewToggleGroup = doc . createElement ( 'div' )
356+ viewToggleGroup . style . display = 'flex'
357+ viewToggleGroup . style . alignItems = 'center'
358+ viewToggleGroup . style . gap = '0.35rem'
346359
347- const openRawButton = createActionButton ( doc , 'Scroll to code' )
348- openRawButton . addEventListener ( 'click' , ( ) => {
349- pre . scrollIntoView ( { behavior : 'smooth' , block : 'center' } )
350- } )
360+ const diagramToggle = createActionButton ( doc , 'Diagram' )
361+ diagramToggle . dataset [ 'coderchartToggle' ] = 'true'
362+ diagramToggle . setAttribute ( 'aria-pressed' , 'false' )
363+
364+ const codeToggle = createActionButton ( doc , 'Code' )
365+ codeToggle . dataset [ 'coderchartToggle' ] = 'true'
366+ codeToggle . setAttribute ( 'aria-pressed' , 'false' )
351367
352368 const downloadSvgButton = createActionButton ( doc , 'Download SVG' )
353369 downloadSvgButton . addEventListener ( 'click' , ( ) => {
@@ -359,56 +375,95 @@ function ensureContainer(pre: HTMLElement): BlockRegistryEntry {
359375 void handleDownloadPng ( pre )
360376 } )
361377
362- collapseButton . addEventListener ( 'click' , ( ) => {
363- const isHidden = container . dataset [ 'collapsed' ] === 'true'
364- if ( isHidden ) {
365- container . dataset [ 'collapsed' ] = 'false'
366- collapseButton . textContent = 'Hide diagram'
367- } else {
368- container . dataset [ 'collapsed' ] = 'true'
369- collapseButton . textContent = 'Show diagram'
370- }
371- updateCollapsedState ( container )
372- } )
378+ viewToggleGroup . append ( diagramToggle , codeToggle )
373379
374- actionGroup . append ( collapseButton , openRawButton , downloadSvgButton , downloadPngButton )
380+ actionGroup . append ( viewToggleGroup , downloadSvgButton , downloadPngButton )
375381 header . append ( title , actionGroup )
376382 container . append ( header )
377383
378384 const body = doc . createElement ( 'div' )
385+ body . dataset [ 'coderchartBody' ] = 'true'
379386 body . style . background = getBodyBackground ( )
380387 body . style . padding = '1rem'
381388 body . style . overflowX = 'auto'
382389
390+ const diagramHost = doc . createElement ( 'div' )
391+ diagramHost . dataset [ 'coderchartPane' ] = 'diagram'
392+ diagramHost . style . display = 'none'
393+
394+ const codeHost = doc . createElement ( 'div' )
395+ codeHost . dataset [ 'coderchartPane' ] = 'code'
396+ codeHost . style . display = 'none'
397+
398+ body . append ( diagramHost , codeHost )
383399 container . append ( body )
384400
385401 if ( typeof pre . insertAdjacentElement === 'function' ) {
386- pre . insertAdjacentElement ( 'afterend ' , container )
402+ pre . insertAdjacentElement ( 'beforebegin ' , container )
387403 } else if ( pre . parentNode ) {
388- pre . parentNode . insertBefore ( container , pre . nextSibling )
404+ pre . parentNode . insertBefore ( container , pre )
389405 }
390406
407+ codeHost . append ( pre )
408+
391409 const entry : BlockRegistryEntry = {
392410 id : blockId ,
393411 container,
394- diagramHost : body ,
395- collapseButton,
412+ diagramHost,
413+ codeHost,
414+ setView : ( ) => undefined ,
415+ userSelectedView : null ,
396416 downloadSvgButton,
397417 downloadPngButton,
398418 lastSvg : null ,
399419 }
420+
421+ const applyView = ( view : 'diagram' | 'code' , options ?: { userInitiated ?: boolean } ) => {
422+ if ( options ?. userInitiated ) {
423+ entry . userSelectedView = view
424+ }
425+ container . dataset [ 'view' ] = view
426+ diagramToggle . dataset [ 'coderchartActive' ] = view === 'diagram' ? 'true' : 'false'
427+ codeToggle . dataset [ 'coderchartActive' ] = view === 'code' ? 'true' : 'false'
428+ diagramToggle . setAttribute ( 'aria-pressed' , view === 'diagram' ? 'true' : 'false' )
429+ codeToggle . setAttribute ( 'aria-pressed' , view === 'code' ? 'true' : 'false' )
430+ updateButtonAppearance ( diagramToggle )
431+ updateButtonAppearance ( codeToggle )
432+ updatePaneVisibility ( container )
433+ }
434+
435+ entry . setView = applyView
436+
437+ diagramToggle . addEventListener ( 'click' , ( ) => {
438+ applyView ( 'diagram' , { userInitiated : true } )
439+ } )
440+
441+ codeToggle . addEventListener ( 'click' , ( ) => {
442+ applyView ( 'code' , { userInitiated : true } )
443+ } )
444+
400445 processedBlocks . set ( pre , entry )
401- updateCollapsedState ( container )
446+ applyView ( 'code' )
402447 updateDownloadButtons ( entry )
403448
404449 return entry
405450}
406451
407- function updateCollapsedState ( container : HTMLElement ) {
408- const isCollapsed = container . dataset [ 'collapsed' ] === 'true'
409- const body = container . lastElementChild as HTMLElement | null
452+ function updatePaneVisibility ( container : HTMLElement ) {
453+ const body = container . querySelector ( '[data-coderchart-body="true"]' ) as HTMLElement | null
410454 if ( ! body ) return
411- body . style . display = isCollapsed ? 'none' : 'block'
455+ body . style . display = 'block'
456+
457+ const view = ( container . dataset [ 'view' ] as 'diagram' | 'code' ) || 'diagram'
458+ const diagramHost = body . querySelector ( '[data-coderchart-pane="diagram"]' ) as HTMLElement | null
459+ const codeHost = body . querySelector ( '[data-coderchart-pane="code"]' ) as HTMLElement | null
460+
461+ if ( diagramHost ) {
462+ diagramHost . style . display = view === 'diagram' ? 'block' : 'none'
463+ }
464+ if ( codeHost ) {
465+ codeHost . style . display = view === 'code' ? 'block' : 'none'
466+ }
412467}
413468
414469function createActionButton ( doc : Document , label : string ) : HTMLButtonElement {
@@ -421,19 +476,30 @@ function createActionButton(doc: Document, label: string): HTMLButtonElement {
421476 button . style . padding = '0.25rem 0.75rem'
422477 button . style . borderRadius = '0.5rem'
423478 button . style . border = getButtonBorder ( )
424- button . style . background = getButtonBackground ( )
425- button . style . color = getPrimaryTextColor ( )
479+ updateButtonAppearance ( button )
426480 button . style . cursor = 'pointer'
427481 button . style . transition = 'background 150ms ease, border 150ms ease'
428482 button . addEventListener ( 'mouseenter' , ( ) => {
429483 button . style . background = getButtonHoverBackground ( )
484+ if ( button . dataset [ 'coderchartToggle' ] === 'true' ) {
485+ button . style . opacity = '1'
486+ }
430487 } )
431488 button . addEventListener ( 'mouseleave' , ( ) => {
432- button . style . background = getButtonBackground ( )
489+ updateButtonAppearance ( button )
433490 } )
434491 return button
435492}
436493
494+ function updateButtonAppearance ( button : HTMLButtonElement ) {
495+ const isToggle = button . dataset [ 'coderchartToggle' ] === 'true'
496+ const isActive = button . dataset [ 'coderchartActive' ] === 'true'
497+ button . style . border = getButtonBorder ( )
498+ button . style . background = isToggle && isActive ? getButtonHoverBackground ( ) : getButtonBackground ( )
499+ button . style . color = getPrimaryTextColor ( )
500+ button . style . opacity = isToggle ? ( isActive ? '1' : '0.75' ) : '1'
501+ }
502+
437503function getPrimaryTextColor ( ) : string {
438504 return isDarkMode ( ) ? 'rgba(226, 232, 240, 0.95)' : '#1f2937'
439505}
@@ -825,10 +891,7 @@ function refreshContainerStyles() {
825891 }
826892 entry . diagramHost . style . background = getBodyBackground ( )
827893 entry . container . querySelectorAll ( 'button' ) . forEach ( ( element ) => {
828- const button = element as HTMLButtonElement
829- button . style . border = getButtonBorder ( )
830- button . style . background = getButtonBackground ( )
831- button . style . color = getPrimaryTextColor ( )
894+ updateButtonAppearance ( element as HTMLButtonElement )
832895 } )
833896 } )
834897}
0 commit comments