@@ -344,179 +344,13 @@ <h2>T</h2>
344344import elkLayouts from "https://cdn.jsdelivr.net/npm/@mermaid-js/layout-elk@0.2.0/dist/mermaid-layout-elk.esm.min.mjs"
345345
346346
347-
348- import zenumlLayouts from "https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-zenuml@0.2.2/dist/mermaid-zenuml.esm.min.mjs"
349-
350-
351347const initStyles = ( ) => {
352348 const defaultStyle = document . createElement ( 'style' ) ;
353- defaultStyle . textContent = `pre.mermaid {
354- /* Same as .mermaid-container > pre */
355- display: block;
356- width: 100%;
357- }
358-
359- pre.mermaid > svg {
360- /* Same as .mermaid-container > pre > svg */
361- height: 500px;
362- width: 100%;
363- max-width: 100% !important;
364- }` ;
349+ defaultStyle . textContent = "pre.mermaid {\n /* Same as .mermaid-container > pre */\n display: block;\n width: 100%;\n}\n\npre.mermaid > svg {\n /* Same as .mermaid-container > pre > svg */\n height: 500px;\n width: 100%;\n max-width: 100% !important;\n}" ;
365350 document . head . appendChild ( defaultStyle ) ;
366351
367352 const fullscreenStyle = document . createElement ( 'style' ) ;
368- fullscreenStyle . textContent = `.mermaid-container {
369- display: flex;
370- flex-direction: row;
371- width: 100%;
372- }
373-
374- .mermaid-container > pre {
375- display: block;
376- width: 100%;
377- }
378-
379- .mermaid-container > pre > svg {
380- height: 500px;
381- width: 100%;
382- max-width: 100% !important;
383- }
384-
385- .mermaid-fullscreen-btn {
386- width: 28px;
387- height: 28px;
388- background: rgba(255, 255, 255, 0.95);
389- border: 1px solid rgba(0, 0, 0, 0.3);
390- border-radius: 4px;
391- cursor: pointer;
392- display: flex;
393- align-items: center;
394- justify-content: center;
395- transition: all 0.2s;
396- box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
397- font-size: 14px;
398- line-height: 1;
399- padding: 0;
400- color: #333;
401- }
402-
403- .mermaid-fullscreen-btn:hover {
404- opacity: 100% !important;
405- background: rgba(255, 255, 255, 1);
406- box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3);
407- transform: scale(1.1);
408- }
409-
410- .mermaid-fullscreen-btn.dark-theme {
411- background: rgba(50, 50, 50, 0.95);
412- border: 1px solid rgba(255, 255, 255, 0.3);
413- color: #e0e0e0;
414- }
415-
416- .mermaid-fullscreen-btn.dark-theme:hover {
417- background: rgba(60, 60, 60, 1);
418- box-shadow: 0 3px 10px rgba(255, 255, 255, 0.2);
419- }
420-
421- .mermaid-fullscreen-modal {
422- display: none;
423- position: fixed !important;
424- top: 0 !important;
425- left: 0 !important;
426- width: 95vw;
427- height: 100vh;
428- background: rgba(255, 255, 255, 0.98);
429- z-index: 9999;
430- padding: 20px;
431- overflow: auto;
432- }
433-
434- .mermaid-fullscreen-modal.dark-theme {
435- background: rgba(0, 0, 0, 0.98);
436- }
437-
438- .mermaid-fullscreen-modal.active {
439- display: flex;
440- align-items: center;
441- justify-content: center;
442- }
443-
444- .mermaid-container-fullscreen {
445- position: relative;
446- width: 95vw;
447- height: 90vh;
448- max-width: 95vw;
449- max-height: 90vh;
450- background: white;
451- border-radius: 8px;
452- padding: 20px;
453- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
454- overflow: auto;
455- display: flex;
456- align-items: center;
457- justify-content: center;
458- }
459-
460- .mermaid-container-fullscreen.dark-theme {
461- background: #1a1a1a;
462- box-shadow: 0 10px 40px rgba(0, 0, 0, 0.8);
463- }
464-
465- .mermaid-container-fullscreen pre.mermaid {
466- width: 100%;
467- height: 100%;
468- display: flex;
469- align-items: center;
470- justify-content: center;
471- }
472-
473- .mermaid-container-fullscreen .mermaid svg {
474- height: 100% !important;
475- width: 100% !important;
476- cursor: grab;
477- }
478-
479- .mermaid-fullscreen-close {
480- position: fixed !important;
481- top: 20px !important;
482- right: 20px !important;
483- width: 40px;
484- height: 40px;
485- background: rgba(255, 255, 255, 0.95);
486- border: 1px solid rgba(0, 0, 0, 0.2);
487- border-radius: 50%;
488- cursor: pointer;
489- z-index: 10000;
490- display: flex;
491- align-items: center;
492- justify-content: center;
493- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
494- transition: all 0.2s;
495- font-size: 24px;
496- line-height: 1;
497- color: #333;
498- }
499-
500- .mermaid-fullscreen-close:hover {
501- background: white;
502- box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);
503- transform: scale(1.1);
504- }
505-
506- .mermaid-fullscreen-close.dark-theme {
507- background: rgba(50, 50, 50, 0.95);
508- border: 1px solid rgba(255, 255, 255, 0.2);
509- color: #e0e0e0;
510- }
511-
512- .mermaid-fullscreen-close.dark-theme:hover {
513- background: rgba(60, 60, 60, 1);
514- box-shadow: 0 6px 16px rgba(255, 255, 255, 0.2);
515- }
516-
517- .mermaid-fullscreen-modal .mermaid-fullscreen-btn {
518- display: none !important;
519- }` ;
353+ fullscreenStyle . textContent = ".mermaid-container {\n position: relative;\n display: flex;\n flex-direction: row;\n width: 100%;\n}\n\n.mermaid-container > pre {\n display: block;\n width: 100%;\n}\n\n.mermaid-container > pre > svg {\n height: 500px;\n width: 100%;\n max-width: 100% !important;\n}\n\n.mermaid-fullscreen-btn {\n position: absolute;\n width: 28px;\n height: 28px;\n background: rgba(255, 255, 255, 0.95);\n border: 1px solid rgba(0, 0, 0, 0.3);\n border-radius: 4px;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.2s;\n box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);\n font-size: 14px;\n line-height: 1;\n padding: 0;\n color: #333;\n}\n\n.mermaid-fullscreen-btn:hover {\n opacity: 100% !important;\n background: rgba(255, 255, 255, 1);\n box-shadow: 0 3px 10px rgba(0, 0, 0, 0.3);\n transform: scale(1.1);\n}\n\n.mermaid-fullscreen-btn.dark-theme {\n background: rgba(50, 50, 50, 0.95);\n border: 1px solid rgba(255, 255, 255, 0.3);\n color: #e0e0e0;\n}\n\n.mermaid-fullscreen-btn.dark-theme:hover {\n background: rgba(60, 60, 60, 1);\n box-shadow: 0 3px 10px rgba(255, 255, 255, 0.2);\n}\n\n.mermaid-fullscreen-modal {\n display: none;\n position: fixed !important;\n top: 0 !important;\n left: 0 !important;\n width: 95vw;\n height: 100vh;\n background: rgba(255, 255, 255, 0.98);\n z-index: 9999;\n padding: 20px;\n overflow: auto;\n}\n\n.mermaid-fullscreen-modal.dark-theme {\n background: rgba(0, 0, 0, 0.98);\n}\n\n.mermaid-fullscreen-modal.active {\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.mermaid-container-fullscreen {\n position: relative;\n width: 95vw;\n height: 90vh;\n max-width: 95vw;\n max-height: 90vh;\n background: white;\n border-radius: 8px;\n padding: 20px;\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);\n overflow: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.mermaid-container-fullscreen.dark-theme {\n background: #1a1a1a;\n box-shadow: 0 10px 40px rgba(0, 0, 0, 0.8);\n}\n\n.mermaid-container-fullscreen pre.mermaid {\n width: 100%;\n height: 100%;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.mermaid-container-fullscreen .mermaid svg {\n height: 100% !important;\n width: 100% !important;\n cursor: grab;\n}\n\n.mermaid-fullscreen-close {\n position: fixed !important;\n top: 20px !important;\n right: 20px !important;\n width: 40px;\n height: 40px;\n background: rgba(255, 255, 255, 0.95);\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 50%;\n cursor: pointer;\n z-index: 10000;\n display: flex;\n align-items: center;\n justify-content: center;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);\n transition: all 0.2s;\n font-size: 24px;\n line-height: 1;\n color: #333;\n}\n\n.mermaid-fullscreen-close:hover {\n background: white;\n box-shadow: 0 6px 16px rgba(0, 0, 0, 0.4);\n transform: scale(1.1);\n}\n\n.mermaid-fullscreen-close.dark-theme {\n background: rgba(50, 50, 50, 0.95);\n border: 1px solid rgba(255, 255, 255, 0.2);\n color: #e0e0e0;\n}\n\n.mermaid-fullscreen-close.dark-theme:hover {\n background: rgba(60, 60, 60, 1);\n box-shadow: 0 6px 16px rgba(255, 255, 255, 0.2);\n}\n\n.mermaid-fullscreen-modal .mermaid-fullscreen-btn {\n display: none !important;\n}" ;
520354 document . head . appendChild ( fullscreenStyle ) ;
521355}
522356
@@ -563,6 +397,13 @@ <h2>T</h2>
563397let previousScrollOffset = [ window . scrollX , window . scrollY ] ;
564398let _lazyObserver = null ;
565399let _renderQueue = Promise . resolve ( ) ;
400+ let closeModal = ( ) => { } ;
401+
402+ document . addEventListener ( 'keydown' , ( e ) => {
403+ if ( e . key === 'Escape' && modal ?. classList . contains ( 'active' ) ) {
404+ closeModal ( ) ;
405+ }
406+ } ) ;
566407
567408// Apply d3 zoom to each SVG. Idempotent: an SVG already wrapped is skipped,
568409// so this is safe to call both for the initial batch and for diagrams that
@@ -659,8 +500,10 @@ <h2>T</h2>
659500 el . removeAttribute ( 'data-mermaid-deferred' ) ;
660501 // Apply zoom to the now-rendered diagram, matching
661502 // the decoration visible diagrams receive.
662- if ( "True" === "True" ) {
663- addZoomToSvgs ( d3 . selectAll ( ".mermaid svg" ) ) ;
503+ if ( true ) {
504+ addZoomToSvgs ( d3 . selectAll ( ".mermaid" ) . select ( function ( ) {
505+ return this . querySelector ( "svg" ) ;
506+ } ) ) ;
664507 }
665508 } catch ( e ) {
666509 console . error ( "Mermaid deferred rendering failed:" , e ) ;
@@ -681,10 +524,12 @@ <h2>T</h2>
681524 const mermaids_active = document . querySelectorAll ( ".mermaid:not([data-mermaid-deferred]):not([data-mermaid-render-failed])" ) ;
682525 const mermaids_processed = document . querySelectorAll ( ".mermaid:not([data-mermaid-deferred]):not([data-mermaid-render-failed])[data-processed='true']" ) ;
683526
684- if ( "True" === "True" ) {
527+ if ( true ) {
685528 const mermaids_to_add_zoom = - 1 === - 1 ? mermaids_active . length : - 1 ;
686529 if ( mermaids_to_add_zoom > 0 ) {
687- var svgs = d3 . selectAll ( ".mermaid svg" ) ;
530+ var svgs = d3 . selectAll ( ".mermaid" ) . select ( function ( ) {
531+ return this . querySelector ( "svg" ) ;
532+ } ) ;
688533 // Wait until every active (visible) diagram has rendered. Deferred
689534 // diagrams are excluded, so a fixed d3_node_count that counts a
690535 // hidden diagram no longer loops forever waiting for its SVG.
@@ -701,7 +546,7 @@ <h2>T</h2>
701546 }
702547
703548 // Stop here if not adding fullscreen capability
704- if ( "True" !== "True" ) return ;
549+ if ( ! true ) return ;
705550
706551 if ( modal !== null ) {
707552 // Destroy existing modal
@@ -724,7 +569,7 @@ <h2>T</h2>
724569 modalContent = modal . querySelector ( '.mermaid-container-fullscreen' ) ;
725570 const closeBtn = modal . querySelector ( '.mermaid-fullscreen-close' ) ;
726571
727- const closeModal = ( ) => {
572+ closeModal = ( ) => {
728573 modal . classList . remove ( 'active' ) ;
729574 modalContent . innerHTML = '' ;
730575 document . body . style . overflow = ''
@@ -735,12 +580,6 @@ <h2>T</h2>
735580 modal . addEventListener ( 'click' , ( e ) => {
736581 if ( e . target === modal ) closeModal ( ) ;
737582 } ) ;
738- document . addEventListener ( 'keydown' , ( e ) => {
739- if ( e . key === 'Escape' && modal . classList . contains ( 'active' ) ) {
740- closeModal ( ) ;
741- }
742- } ) ;
743-
744583 document . querySelectorAll ( '.mermaid' ) . forEach ( ( mermaidDiv ) => {
745584 if ( mermaidDiv . parentNode . classList . contains ( 'mermaid-container' ) ||
746585 mermaidDiv . closest ( '.mermaid-fullscreen-modal' ) ) {
@@ -760,8 +599,8 @@ <h2>T</h2>
760599 const fullscreenBtn = document . createElement ( 'button' ) ;
761600 fullscreenBtn . className = 'mermaid-fullscreen-btn' + ( darkTheme ? ' dark-theme' : '' ) ;
762601 fullscreenBtn . setAttribute ( 'aria-label' , 'View diagram in fullscreen' ) ;
763- fullscreenBtn . textContent = '⛶' ;
764- fullscreenBtn . style . opacity = ' 50%' ;
602+ fullscreenBtn . textContent = "\u26f6" ;
603+ fullscreenBtn . style . opacity = " 50%" ;
765604
766605 // Calculate dynamic position based on diagram's margin and padding
767606 const diagramStyle = window . getComputedStyle ( mermaidDiv ) ;
@@ -773,7 +612,7 @@ <h2>T</h2>
773612 fullscreenBtn . style . right = `${ marginRight + paddingRight + 4 } px` ;
774613
775614 fullscreenBtn . addEventListener ( 'click' , ( ) => {
776- previousScrollOffset = [ window . scroll , window . scrollY ] ;
615+ previousScrollOffset = [ window . scrollX , window . scrollY ] ;
777616 const clone = mermaidDiv . cloneNode ( true ) ;
778617 modalContent . innerHTML = '' ;
779618 modalContent . appendChild ( clone ) ;
@@ -785,9 +624,9 @@ <h2>T</h2>
785624 svg . style . width = '100%' ;
786625 svg . style . height = 'auto' ;
787626 svg . style . maxWidth = '100%' ;
788- svg . style . sdisplay = 'block' ;
627+ svg . style . display = 'block' ;
789628
790- if ( "True" === "True" ) {
629+ if ( true ) {
791630 setTimeout ( ( ) => {
792631 const g = svg . querySelector ( 'g' ) ;
793632 if ( g ) {
@@ -810,9 +649,27 @@ <h2>T</h2>
810649 } ) ;
811650} ;
812651
652+
653+ // Resolves once the lazily-loaded zenuml plugin has registered (or immediately
654+ // when the page has no zenuml diagram). `load` awaits this before rendering.
655+ let zenumlReady = Promise . resolve ( ) ;
656+ const pageHasZenuml = ( ) => [ ...document . querySelectorAll ( ".mermaid" ) ] . some ( ( el ) => {
657+ const code = el . getAttribute ( "data-original-code" ) || el . textContent || "" ;
658+ const diagram = code
659+ . replace ( / ^ \s * - - - \s * \n [ ^ ] * ?\n - - - \s * / , "" )
660+ . replace ( / ^ \s * % % \{ [ ^ ] * ?\} % % \s * / , "" ) ;
661+ return / ^ \s * z e n u m l \b / i. test ( diagram ) ;
662+ } ) ;
663+
664+
813665const load = async ( ) => {
814666 initStyles ( ) ;
815667
668+
669+ // Wait for the zenuml plugin (loaded lazily below) to finish registering
670+ // before rendering, so zenuml diagrams are recognised.
671+ await zenumlReady ;
672+
816673 await runMermaid ( true ) ;
817674
818675 const reRunIfThemeChanges = async ( ) => {
@@ -821,10 +678,8 @@ <h2>T</h2>
821678 darkTheme = newDarkTheme ;
822679 console . log ( "Theme change detected, re-running mermaid with" , darkTheme ? "dark" : "default" , "theme" ) ;
823680 await mermaid . initialize (
824- { ...JSON . parse (
825- `{"startOnLoad": false}`
826- ) ,
827- ...{ darkMode : darkTheme , theme : darkTheme ? 'dark' : 'default' } ,
681+ { ...{ "startOnLoad" : false } ,
682+ ...{ darkMode : darkTheme , theme : darkTheme ? "dark" : "default" } ,
828683 }
829684 ) ;
830685 await runMermaid ( true ) ;
@@ -848,15 +703,40 @@ <h2>T</h2>
848703
849704
850705
851- mermaid . registerExternalDiagrams ( [ zenumlLayouts ] ) ;
706+ // Only load and register the zenuml plugin when the page actually contains a
707+ // zenuml diagram. This avoids fetching the (large) zenuml bundle on every page,
708+ // and avoids its side effects where they are not needed. The import is kept off
709+ // the module's top level (no top-level await) so the "load" listener below is
710+ // still registered synchronously; `load` awaits `zenumlReady` before rendering.
711+ if ( pageHasZenuml ( ) ) {
712+ // The zenuml bundle injects a stylesheet that declares generic design tokens
713+ // (--background, --border, --white, ...) on :root, clobbering identically-named
714+ // tokens in the host theme (sphinxawesome, shibuya, ...) and causing a flash of
715+ // restyled page content. Confine those declarations to the zenuml diagram
716+ // subtree so they no longer leak onto the host page.
717+ const descopeZenumlStyle = ( node ) => {
718+ if ( ! node || node . tagName !== "STYLE" || node . hasAttribute ( "data-zenuml-descoped" ) ) return ;
719+ const css = node . textContent || "" ;
720+ if ( ! css . includes ( "--occurance-border" ) ) return ; // zenuml stylesheet signature
721+ node . setAttribute ( "data-zenuml-descoped" , "true" ) ;
722+ node . textContent = css . replace ( / : r o o t \b / g, ".zenuml" ) ;
723+ } ;
724+ new MutationObserver ( ( records ) => {
725+ for ( const rec of records ) {
726+ for ( const node of rec . addedNodes ) descopeZenumlStyle ( node ) ;
727+ }
728+ } ) . observe ( document . head , { childList : true } ) ;
729+
730+ zenumlReady = import ( "https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-zenuml@0.2.2/dist/mermaid-zenuml.esm.min.mjs" ) . then ( ( { default : zenumlLayouts } ) => {
731+ mermaid . registerExternalDiagrams ( [ zenumlLayouts ] ) ;
732+ } ) ;
733+ }
852734
853735
854736console . log ( "Initializing mermaid with" , darkTheme ? "dark" : "default" , "theme" ) ;
855737mermaid . initialize (
856- { ...JSON . parse (
857- `{"startOnLoad": false}`
858- ) ,
859- ...{ darkMode : darkTheme , theme : darkTheme ? 'dark' : 'default' } ,
738+ { ...{ "startOnLoad" : false } ,
739+ ...{ darkMode : darkTheme , theme : darkTheme ? "dark" : "default" } ,
860740 }
861741) ;
862742
0 commit comments