1818
1919 <div class =" incremark-shiki-body" >
2020 <div
21- v-if =" renderedHtml"
21+ v-if =" shouldRenderVega && !renderedHtml"
22+ ref =" vegaContainer"
23+ class =" incremark-vega"
24+ />
25+
26+ <div
27+ v-else-if =" renderedHtml"
2228 class =" incremark-shiki-html"
2329 v-html =" renderedHtml"
2430 />
3137<script setup lang="ts">
3238import type { Code } from ' mdast' ;
3339import { computed , onBeforeUnmount , onMounted , ref , watch } from ' vue' ;
40+ import embed from ' vega-embed' ;
3441
3542import { highlightCodeSnippetHtml , type IncremarkCodeTheme } from ' ./incremarkCodeHighlight' ;
3643
@@ -53,15 +60,18 @@ const props = withDefaults(defineProps<{
5360const renderedHtml = ref (' ' );
5461const copied = ref (false );
5562const prefersDarkMode = ref (isDarkDocument ());
63+ const vegaContainer = ref <HTMLDivElement | null >(null );
5664
5765let copyResetTimeout: number | null = null ;
5866let renderRequestId = 0 ;
5967let scheduledFrameId: number | null = null ;
6068let themeObserver: MutationObserver | null = null ;
69+ let vegaResult: { finalize: () => void } | null = null ;
6170
6271const sourceCode = computed (() => props .node .value ?? ' ' );
6372const language = computed (() => props .node .lang ?.trim ().toLowerCase () || ' text' );
6473const languageLabel = computed (() => props .node .lang ?.trim () || ' text' );
74+ const shouldRenderVega = computed (() => language .value === ' vega-lite' && props .blockStatus === ' completed' );
6575const codeTheme = computed <IncremarkCodeTheme >(() => {
6676 const requestedTheme = props .theme ?? (prefersDarkMode .value ? props .darkTheme : props .lightTheme );
6777
@@ -77,7 +87,7 @@ const codeTheme = computed<IncremarkCodeTheme>(() => {
7787});
7888
7989watch (
80- [sourceCode , language , codeTheme , () => props .disableHighlight ],
90+ [sourceCode , language , codeTheme , () => props .disableHighlight , () => props . blockStatus ],
8191 () => {
8292 scheduleHighlight ();
8393 },
8696
8797onMounted (() => {
8898 if (typeof MutationObserver === ' undefined' || typeof document === ' undefined' ) {
99+ scheduleHighlight ();
89100 return ;
90101 }
91102
@@ -97,10 +108,13 @@ onMounted(() => {
97108 attributes: true ,
98109 attributeFilter: [' class' ],
99110 });
111+
112+ scheduleHighlight ();
100113});
101114
102115onBeforeUnmount (() => {
103116 renderRequestId += 1 ;
117+ clearVega ();
104118
105119 if (copyResetTimeout !== null ) {
106120 window .clearTimeout (copyResetTimeout );
@@ -154,6 +168,45 @@ function scheduleHighlight() {
154168async function renderHighlight() {
155169 const requestId = ++ renderRequestId ;
156170
171+ if (shouldRenderVega .value ) {
172+ renderedHtml .value = ' ' ;
173+
174+ if (! sourceCode .value || ! vegaContainer .value ) {
175+ return ;
176+ }
177+
178+ try {
179+ clearVega ();
180+ const spec = JSON .parse (sourceCode .value );
181+
182+ if (spec .width == null ) {
183+ spec .width = ' container' ;
184+ }
185+
186+ if (spec .autosize == null ) {
187+ spec .autosize = { type: ' fit-x' , contains: ' padding' };
188+ }
189+
190+ const result = await embed (vegaContainer .value , spec , {
191+ actions: false ,
192+ renderer: ' svg' ,
193+ });
194+
195+ if (requestId !== renderRequestId ) {
196+ result .finalize ();
197+ return ;
198+ }
199+
200+ vegaResult = result ;
201+ return ;
202+ } catch (error ) {
203+ clearVega ();
204+ console .error (' Failed to render Vega-Lite block' , error );
205+ }
206+ } else {
207+ clearVega ();
208+ }
209+
157210 if (! sourceCode .value || props .disableHighlight ) {
158211 renderedHtml .value = ' ' ;
159212 return ;
@@ -177,6 +230,15 @@ async function renderHighlight() {
177230function isDarkDocument(): boolean {
178231 return typeof document !== ' undefined' && document .documentElement .classList .contains (' dark' );
179232}
233+
234+ function clearVega() {
235+ vegaResult ?.finalize ();
236+ vegaResult = null ;
237+
238+ if (vegaContainer .value ) {
239+ vegaContainer .value .innerHTML = ' ' ;
240+ }
241+ }
180242 </script >
181243
182244<style scoped>
@@ -265,6 +327,11 @@ function isDarkDocument(): boolean {
265327 overflow-x : auto ;
266328}
267329
330+ .incremark-vega {
331+ padding : 18px ;
332+ width : 100% ;
333+ }
334+
268335.incremark-shiki-fallback {
269336 margin : 0 ;
270337 padding : 18px ;
@@ -298,4 +365,12 @@ function isDarkDocument(): boolean {
298365:deep(.incremark-shiki-html .line ) {
299366 min-height : 1.65em ;
300367}
368+
369+ :deep(.incremark-vega .vega-embed ) {
370+ width : 100% ;
371+ }
372+
373+ :deep(.incremark-vega ){
374+ padding : 0 ;
375+ }
301376 </style >
0 commit comments