markstream-svelte 0.0.4-beta.2 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -27,9 +27,48 @@ Optional heavy renderers stay as peer dependencies, matching the Vue and React p
27
27
  Plain Markdown does not require these packages:
28
28
 
29
29
  ```bash
30
- pnpm add katex mermaid stream-monaco @terrastruct/d2 @antv/infographic
30
+ pnpm add katex mermaid stream-diffs @terrastruct/d2 @antv/infographic
31
31
  ```
32
32
 
33
+ `stream-diffs` powers the enhanced code blocks (smaller runtime, no `monaco-editor`).
34
+ If you prefer the legacy Monaco-based rendering, install `stream-monaco` instead;
35
+ it is used automatically as a fallback when `stream-diffs` is absent.
36
+
37
+ ## Enhanced Code Blocks
38
+
39
+ `CodeBlockNode` renders a single code block with the header, toolbar, and a `stream-diffs` File / FileDiff surface. Inside `MarkdownRender`, code blocks resolve to the same runtime automatically.
40
+
41
+ ```svelte
42
+ <script lang="ts">
43
+ import { CodeBlockNode } from 'markstream-svelte'
44
+ import type { CodeBlockMonacoOptions } from 'markstream-svelte'
45
+
46
+ const node = {
47
+ type: 'code_block',
48
+ language: 'ts',
49
+ code: 'const answer = 42',
50
+ raw: 'const answer = 42',
51
+ }
52
+
53
+ // fontSize / lineHeight / tabSize also drive the streaming <pre> fallback so
54
+ // the enhanced surface swaps in without a visual jump.
55
+ const monacoOptions: CodeBlockMonacoOptions = {
56
+ fontSize: 14,
57
+ lineHeight: 21,
58
+ tabSize: 4,
59
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
60
+ wordWrap: 'off',
61
+ theme: 'vitesse-dark',
62
+ renderSideBySide: true,
63
+ MAX_HEIGHT: 640,
64
+ }
65
+ </script>
66
+
67
+ <CodeBlockNode {node} {monacoOptions} isDark showLineNumbers />
68
+ ```
69
+
70
+ Component-level options: `isDark`, `showLineNumbers` (default `true`), and `monacoOptions` for both single blocks and diff blocks (`renderSideBySide`, `diffHunkActionsOnHover`, `onDiffHunkAction`). When neither `stream-diffs` nor `stream-monaco` is installed, the block renders as a plain `<pre>`.
71
+
33
72
  ## Basic Usage
34
73
 
35
74
  ```svelte
@@ -7,8 +7,9 @@
7
7
  import { hideTooltip, showTooltipForAnchor } from '../tooltip/singletonTooltip'
8
8
  import { getLanguageIcon, isLikelyIncompleteLanguageIdentifier, languageMap, normalizeLanguageIdentifier, resolveMonacoLanguageId } from '../utils/languageIcon'
9
9
  import HtmlPreviewFrame from './HtmlPreviewFrame.svelte'
10
+ import PreCodeNode from './PreCodeNode.svelte'
10
11
  import { copyTextToClipboard, resolveCssSize } from './shared/rich-block-helpers'
11
- import { getString, sanitizeClassToken } from './shared/node-helpers'
12
+ import { getString } from './shared/node-helpers'
12
13
 
13
14
  type Props = {
14
15
  node: SvelteRenderableNode
@@ -30,6 +31,7 @@
30
31
  showPreviewButton?: boolean
31
32
  showCollapseButton?: boolean
32
33
  showFontSizeButtons?: boolean
34
+ showLineNumbers?: boolean
33
35
  htmlPreviewAllowScripts?: boolean
34
36
  htmlPreviewSandbox?: string | undefined
35
37
  }
@@ -54,6 +56,7 @@
54
56
  showPreviewButton = true,
55
57
  showCollapseButton = true,
56
58
  showFontSizeButtons = true,
59
+ showLineNumbers = true,
57
60
  htmlPreviewAllowScripts = false,
58
61
  htmlPreviewSandbox = undefined
59
62
  }: Props = $props()
@@ -72,6 +75,7 @@
72
75
  revealLineCount: 0,
73
76
  })
74
77
  const streamingLanguageTokens = ['javascript', 'plaintext', 'shellscript', 'typescript']
78
+ const defaultPreFallbackFontFamily = '"SF Mono", Monaco, Consolas, "Ubuntu Mono", "Liberation Mono", "Courier New", monospace'
75
79
 
76
80
  function resolveRecoverableFallbackLanguage(error: unknown) {
77
81
  const message = error instanceof Error ? error.message : String(error ?? '')
@@ -129,13 +133,16 @@
129
133
  let useFallback = $state(false)
130
134
  let fallbackLanguage = $state('')
131
135
  let editorKind: 'single' | 'diff' | null = $state(null)
136
+ let editorStreamMode: boolean | null = $state(null)
137
+ let editorRevealed = $state(false)
138
+ let fallbackRetired = $state(false)
132
139
  let createEditorPromise: Promise<void> | null = $state(null)
133
140
  let mounted = $state(false)
134
141
  let collapsed = $state(false)
135
142
  let expanded = $state(false)
136
143
  let copied = $state(false)
137
144
  let previewOpen = $state(false)
138
- let codeFontSize = $state(13)
145
+ let codeFontSize = $state(12)
139
146
  let copyTimer: ReturnType<typeof setTimeout> | null = $state(null)
140
147
  let lifecycleId = $state(0)
141
148
  let heightSyncRaf: number | null = $state(null)
@@ -171,7 +178,7 @@
171
178
  : lightTheme ?? resolvedThemes?.lightTheme,
172
179
  resolvedIsDark ? 'vitesse-dark' : 'vitesse-light',
173
180
  ))
174
- let defaultCodeFontSize = $derived(Number(mergedMonacoOptions.fontSize) || 13)
181
+ let defaultCodeFontSize = $derived(Number(mergedMonacoOptions.fontSize) || 12)
175
182
  let minWidthValue = $derived(resolveCssSize(minWidth ?? resolvedThemes?.minWidth))
176
183
  let maxWidthValue = $derived(resolveCssSize(maxWidth ?? resolvedThemes?.maxWidth))
177
184
  let containerStyle = $derived([
@@ -186,9 +193,12 @@
186
193
  let documentStreaming = $derived(context?.final === false || resolvedLoading)
187
194
  let shouldDeferStreamingLanguage = $derived(resolvedStream !== false && documentStreaming && (isLikelyIncompleteLanguageIdentifier(rawLanguage) || isStreamingLanguagePrefix(rawLanguage)))
188
195
  let shouldRender = $derived(!(resolvedLoading && !code.trim()))
189
- let preLanguageClass = $derived(sanitizeClassToken(rawLanguage || monacoLanguage))
190
- let showPreWhileMonacoLoads = $derived(!useFallback && !shouldDelayEditor && !shouldDeferStreamingLanguage && !editorReady)
191
- let showPreFallback = $derived(useFallback || shouldDelayEditor || shouldDeferStreamingLanguage || showPreWhileMonacoLoads)
196
+ let preFallbackNode = $derived({
197
+ ...(node as any),
198
+ code,
199
+ loading: resolvedLoading,
200
+ } as SvelteRenderableNode)
201
+ let preFallbackStyle = $derived(buildPreFallbackStyle())
192
202
  let settledRefreshSignature = $derived(diff
193
203
  ? `${monacoLanguage}\0${originalCode}\0${updatedCode || code}`
194
204
  : `${monacoLanguage}\0${code}`)
@@ -258,6 +268,58 @@
258
268
  return getString((sourceNode as any)?.code)
259
269
  }
260
270
 
271
+ function readPositiveMetric(value: unknown) {
272
+ const number = Number(value)
273
+ return Number.isFinite(number) && number > 0 ? number : undefined
274
+ }
275
+
276
+ function readNonNegativeMetric(value: unknown) {
277
+ const number = Number(value)
278
+ return Number.isFinite(number) && number >= 0 ? number : undefined
279
+ }
280
+
281
+ function getCodeLineHeight() {
282
+ return readPositiveMetric(mergedMonacoOptions.lineHeight)
283
+ ?? (codeFontSize === 12 ? 18 : Math.max(12, Math.round(codeFontSize * 1.5)))
284
+ }
285
+
286
+ function getCodePadding() {
287
+ const padding = mergedMonacoOptions.padding as Record<string, unknown> | undefined
288
+ const defaultPadding = diff ? 0 : 8
289
+ return {
290
+ top: readNonNegativeMetric(padding?.top) ?? defaultPadding,
291
+ bottom: readNonNegativeMetric(padding?.bottom) ?? defaultPadding,
292
+ }
293
+ }
294
+
295
+ function getCodeFontFamily() {
296
+ return typeof mergedMonacoOptions.fontFamily === 'string' && mergedMonacoOptions.fontFamily.trim()
297
+ ? mergedMonacoOptions.fontFamily.trim()
298
+ : defaultPreFallbackFontFamily
299
+ }
300
+
301
+ function buildPreFallbackStyle() {
302
+ const padding = getCodePadding()
303
+ const fontFamily = getCodeFontFamily()
304
+ const tabSize = readPositiveMetric(mergedMonacoOptions.tabSize) ?? 4
305
+ const lineHeight = getCodeLineHeight()
306
+ return [
307
+ `--markstream-code-font-family: ${fontFamily}`,
308
+ `--vscode-editor-font-size: ${codeFontSize}px`,
309
+ `--vscode-editor-line-height: ${lineHeight}px`,
310
+ `--markstream-code-padding-y: ${padding.top}px`,
311
+ `--markstream-pre-line-number-top: ${padding.top}px`,
312
+ `font-family: ${fontFamily}`,
313
+ `font-size: ${codeFontSize}px`,
314
+ `line-height: ${lineHeight}px`,
315
+ `padding-top: ${padding.top}px`,
316
+ `padding-right: var(--markstream-code-padding-x, 12px)`,
317
+ `padding-bottom: ${padding.bottom}px`,
318
+ `padding-left: var(--markstream-code-padding-left, 52px)`,
319
+ `tab-size: ${tabSize}`,
320
+ ].join('; ')
321
+ }
322
+
261
323
  function getThemeName(theme: CodeBlockMonacoTheme | undefined, fallback: string) {
262
324
  if (typeof theme === 'string' && theme)
263
325
  return theme
@@ -306,9 +368,25 @@
306
368
  wrappingIndent: 'same',
307
369
  revealDebounceMs: 75,
308
370
  }
371
+ const padding = getCodePadding()
372
+ const configuredUnsafeCSS = typeof raw.unsafeCSS === 'string' ? raw.unsafeCSS : ''
373
+ const unsafeCSS = `[data-file], [data-diff] { --diffs-min-number-column-width-default: 4ch !important; }
374
+ ${configuredUnsafeCSS}`.trim()
309
375
  const finalOptions = {
310
376
  MAX_HEIGHT: maxHeight,
377
+ fontFamily: getCodeFontFamily(),
311
378
  fontSize: codeFontSize,
379
+ lineHeight: getCodeLineHeight(),
380
+ // stream-diffs expects a boolean (`disableLineNumbers: options.lineNumbers === false`);
381
+ // passing 'off'/'on' strings would always be truthy and defeat showLineNumbers={false}.
382
+ lineNumbers: showLineNumbers !== false,
383
+ padding,
384
+ unsafeCSS,
385
+ // The component owns the streaming fallback and file header. Initialize
386
+ // stream-diffs in final mode so the revealed surface has highlighting,
387
+ // line numbers, and the same geometry as the fallback.
388
+ disableFileHeader: true,
389
+ stream: false,
312
390
  themes: buildThemeList(),
313
391
  }
314
392
 
@@ -408,6 +486,7 @@
408
486
  }
409
487
 
410
488
  helpers = mod.useMonaco(syncRuntimeMonacoOptions())
489
+ await Promise.resolve(helpers.setTheme?.(requestedTheme))
411
490
  lastThemeRequest = requestedTheme
412
491
  })().finally(() => {
413
492
  ensureMonacoPromise = null
@@ -439,8 +518,9 @@
439
518
  ? Boolean(helpers.getDiffEditorView?.())
440
519
  : Boolean(helpers.getEditorView?.())
441
520
  const hasEditor = hasEditorView && hasRenderedEditorDom(desiredKind)
521
+ const desiredStreamMode = false
442
522
 
443
- if (!hasEditor || editorKind !== desiredKind) {
523
+ if (!hasEditor || editorKind !== desiredKind || editorStreamMode !== desiredStreamMode) {
444
524
  await recreateEditor(desiredKind)
445
525
  if (!mounted || useFallback || !helpers)
446
526
  return
@@ -467,9 +547,72 @@
467
547
  function hasRenderedEditorDom(kind: 'single' | 'diff') {
468
548
  if (!editorHost)
469
549
  return false
470
- return kind === 'diff'
471
- ? Boolean(editorHost.querySelector('.monaco-diff-editor'))
472
- : Boolean(editorHost.querySelector('.monaco-editor'))
550
+ if (kind === 'diff') {
551
+ return Boolean(editorHost.querySelector([
552
+ '.monaco-diff-editor',
553
+ 'diffs-container',
554
+ '.stream-diffs-shell',
555
+ '[data-stream-diffs-state]',
556
+ ].join(',')))
557
+ }
558
+ return Boolean(editorHost.querySelector([
559
+ '.monaco-editor',
560
+ 'diffs-container',
561
+ '.stream-diffs-shell',
562
+ '[data-stream-diffs-state]',
563
+ ].join(',')))
564
+ }
565
+
566
+ function getVisualEditorSurface() {
567
+ return editorHost?.querySelector<HTMLElement>([
568
+ '.monaco-diff-editor',
569
+ '.monaco-editor',
570
+ 'diffs-container',
571
+ '[data-stream-diffs-state]',
572
+ '.stream-diffs-shell',
573
+ ].join(',')) ?? null
574
+ }
575
+
576
+ function isEditorVisuallyReady(kind: 'single' | 'diff', requireRevealed = false) {
577
+ if (!editorHost || !hasRenderedEditorDom(kind))
578
+ return false
579
+ if (requireRevealed) {
580
+ const hostStyle = window.getComputedStyle(editorHost)
581
+ if (hostStyle.display === 'none' || hostStyle.visibility === 'hidden' || Number.parseFloat(hostStyle.opacity || '1') <= 0.01)
582
+ return false
583
+ }
584
+ const surface = getVisualEditorSurface()
585
+ if (!surface)
586
+ return false
587
+ const rect = surface.getBoundingClientRect()
588
+ if (rect.width <= 0 || rect.height <= 0)
589
+ return false
590
+ const style = window.getComputedStyle(surface)
591
+ return style.display !== 'none'
592
+ && style.visibility !== 'hidden'
593
+ && Number.parseFloat(style.opacity || '1') > 0.01
594
+ }
595
+
596
+ async function prepareEditorHandoff(kind: 'single' | 'diff', creationId: number) {
597
+ await tick()
598
+ // Time-box the handoff: if visual readiness can't be confirmed (e.g. a
599
+ // hidden/zero-size container), reveal the editor anyway once its DOM is
600
+ // mounted so the block never strands in the pre-fallback forever.
601
+ const deadline = Date.now() + 1500
602
+ let attempt = 0
603
+ while (Date.now() < deadline && attempt < 30) {
604
+ attempt += 1
605
+ if (!mounted || !editorHost || lifecycleId !== creationId)
606
+ return false
607
+ syncEditorHostHeight(true)
608
+ await nextAnimationFrame()
609
+ if (isEditorVisuallyReady(kind)) {
610
+ syncEditorHostHeight(true)
611
+ await nextAnimationFrame()
612
+ return isEditorVisuallyReady(kind)
613
+ }
614
+ }
615
+ return !!(mounted && editorHost && lifecycleId === creationId && hasRenderedEditorDom(kind))
473
616
  }
474
617
 
475
618
  async function recreateEditor(kind: 'single' | 'diff') {
@@ -487,6 +630,7 @@
487
630
  lastLayoutWidth = null
488
631
  lastLayoutHeight = null
489
632
 
633
+ editorStreamMode = false
490
634
  if (kind === 'diff' && typeof helpers.createDiffEditor === 'function') {
491
635
  await helpers.createDiffEditor(editorHost, originalCode, updatedCode || code, monacoLanguage)
492
636
  await Promise.resolve(helpers.updateDiff?.(originalCode, updatedCode || code, monacoLanguage))
@@ -498,10 +642,17 @@
498
642
  editorKind = 'single'
499
643
  }
500
644
  applyEditorOptions()
501
- editorReady = true
502
645
  bindEditorHeightSync()
503
- scheduleEditorHeightSync()
504
646
  queueThemeSync()
647
+ if (!await prepareEditorHandoff(kind, creationId))
648
+ return
649
+ // Apply the reveal and fallback retirement in one Svelte render. The
650
+ // editor surface has already passed the hidden-host readiness check, so
651
+ // the browser never paints an intermediate frame with neither layer.
652
+ editorRevealed = true
653
+ fallbackRetired = true
654
+ editorReady = true
655
+ scheduleEditorHeightSync()
505
656
  }
506
657
  catch (error) {
507
658
  if (mounted) {
@@ -583,6 +734,9 @@
583
734
  }
584
735
  catch {}
585
736
  editorKind = null
737
+ editorStreamMode = null
738
+ editorRevealed = false
739
+ fallbackRetired = false
586
740
  editorReady = false
587
741
  lastLayoutWidth = null
588
742
  lastLayoutHeight = null
@@ -597,9 +751,25 @@
597
751
  function applyEditorOptions() {
598
752
  const target = diff ? helpers?.getDiffEditorView?.() : helpers?.getEditorView?.()
599
753
  target?.updateOptions?.({ fontSize: codeFontSize, automaticLayout: false })
754
+ syncEditorGeometryVars()
600
755
  scheduleEditorHeightSync()
601
756
  }
602
757
 
758
+ // Align the enhanced surface with the pre-fallback geometry (see vue3):
759
+ // stream-diffs/pierre honor these CSS variables on the editor host.
760
+ function syncEditorGeometryVars() {
761
+ if (!editorHost)
762
+ return
763
+ const tabSize = readPositiveMetric(mergedMonacoOptions.tabSize) ?? 4
764
+ editorHost.style.setProperty('--diffs-tab-size', String(tabSize))
765
+ const rawPadding = mergedMonacoOptions.padding
766
+ const hasConfiguredPadding = Boolean(rawPadding && typeof rawPadding === 'object')
767
+ if (hasConfiguredPadding)
768
+ editorHost.style.setProperty('--diffs-gap-block', `${getCodePadding().top}px`)
769
+ else
770
+ editorHost.style.removeProperty('--diffs-gap-block')
771
+ }
772
+
603
773
  function getMaxHeightValue() {
604
774
  const raw = resolvedMonacoOptions.MAX_HEIGHT
605
775
  if (raw === 'none' || raw == null)
@@ -735,8 +905,8 @@
735
905
  heightSyncDisposables = []
736
906
  }
737
907
 
738
- function syncEditorHostHeight() {
739
- if (!editorHost || !helpers || !editorReady || collapsed)
908
+ function syncEditorHostHeight(preparing = false) {
909
+ if (!editorHost || !helpers || (!editorReady && !preparing) || collapsed)
740
910
  return
741
911
 
742
912
  const maxHeight = getMaxHeightValue()
@@ -805,7 +975,7 @@
805
975
  const editor = helpers?.getEditorView?.()
806
976
  const height = Number(editor?.getContentHeight?.() || 0)
807
977
  if (height > 0)
808
- return Math.ceil(height + 1)
978
+ return Math.ceil(height)
809
979
  }
810
980
  catch {}
811
981
  return null
@@ -960,11 +1130,17 @@
960
1130
  {#if !collapsed}
961
1131
  <div class:code-block-body--expanded={expanded} class="code-block-body">
962
1132
  {#if !shouldDelayEditor}
963
- <div bind:this={editorHost} class:is-hidden={showPreFallback} class="code-editor-container"></div>
964
- {/if}
965
- {#if showPreFallback}
966
- <pre class="code-pre-fallback"><code class={preLanguageClass ? `language-${preLanguageClass}` : undefined}>{code}</code></pre>
1133
+ <div bind:this={editorHost} class:is-hidden={!editorRevealed} class="code-editor-container"></div>
967
1134
  {/if}
1135
+ <div class:is-hidden={fallbackRetired} class="code-editor-fallback-surface">
1136
+ <PreCodeNode
1137
+ class="code-pre-fallback"
1138
+ enhanceable={false}
1139
+ node={preFallbackNode}
1140
+ showLineNumbers={showLineNumbers !== false}
1141
+ style={preFallbackStyle}
1142
+ />
1143
+ </div>
968
1144
  </div>
969
1145
  {/if}
970
1146
 
@@ -20,6 +20,7 @@ type Props = {
20
20
  showPreviewButton?: boolean;
21
21
  showCollapseButton?: boolean;
22
22
  showFontSizeButtons?: boolean;
23
+ showLineNumbers?: boolean;
23
24
  htmlPreviewAllowScripts?: boolean;
24
25
  htmlPreviewSandbox?: string | undefined;
25
26
  };
@@ -4,9 +4,17 @@
4
4
 
5
5
  type Props = {
6
6
  node: SvelteRenderableNode
7
+ showLineNumbers?: boolean
8
+ enhanceable?: boolean
9
+ class?: string
10
+ style?: string
7
11
  };
8
12
  let {
9
- node
13
+ node,
14
+ showLineNumbers = false,
15
+ enhanceable = true,
16
+ class: className = undefined,
17
+ style = undefined,
10
18
  }: Props = $props()
11
19
 
12
20
  let languageRaw = $derived(getString((node as any)?.language).trim())
@@ -14,8 +22,55 @@
14
22
  let code = $derived(getString((node as any)?.code))
15
23
  let diff = $derived(Boolean((node as any)?.diff))
16
24
  let loading = $derived((node as any)?.loading === true)
25
+ let displayCode = $derived(loading ? code : code.replace(/\r\n$|\n$|\r$/, ''))
26
+
27
+ // Non-diff line numbers, aligned with the Vue 3 PreCodeNode.
28
+ let showLineGutter = $derived(showLineNumbers === true && !diff)
29
+ let lineCount = $derived(countCodeLines(displayCode))
30
+ let lineNumbersText = $derived(buildLineNumbersText(lineCount))
31
+ let lineNumberLayoutStyle = $derived(
32
+ showLineGutter
33
+ ? `--markstream-pre-line-number-width: ${Math.max(4, String(lineCount).length)}ch; --markstream-pre-diff-line-number-width: ${Math.max(4, String(lineCount).length)}ch; --markstream-code-padding-left: calc(var(--markstream-pre-line-number-padding-left, 2ch) + var(--markstream-pre-line-number-width, 2ch) + var(--markstream-pre-line-number-padding-right, 1ch) + var(--markstream-pre-line-number-separator-width, 2px) + var(--markstream-pre-line-number-gap-to-code, 1ch));`
34
+ : '',
35
+ )
36
+ let mergedStyle = $derived([lineNumberLayoutStyle, style].filter(Boolean).join(' '))
37
+
38
+ function countCodeLines(codeStr: string) {
39
+ let count = 1
40
+ for (let index = 0; index < codeStr.length; index++) {
41
+ if (codeStr[index] === '\n') {
42
+ count++
43
+ }
44
+ else if (codeStr[index] === '\r') {
45
+ count++
46
+ if (codeStr[index + 1] === '\n')
47
+ index++
48
+ }
49
+ }
50
+ return codeStr.length ? count : 1
51
+ }
52
+
53
+ function buildLineNumbersText(count: number) {
54
+ let out = ''
55
+ for (let line = 1; line <= count; line++)
56
+ out += `${out ? '\n' : ''}${line}`
57
+ return out
58
+ }
17
59
  </script>
18
60
 
19
61
  {#if !(loading && !code.trim())}
20
- <pre data-markstream-code-block="1" data-markstream-language={languageRaw || undefined} data-markstream-loading={loading ? '1' : undefined} data-markstream-diff={diff ? '1' : undefined} data-markstream-original={diff ? encodeDataPayload(getString((node as any)?.originalCode)) : undefined} data-markstream-updated={diff ? encodeDataPayload(getString((node as any)?.updatedCode)) : undefined} aria-busy={loading ? 'true' : undefined}><code class={language ? `language-${language}` : undefined}>{code}</code></pre>
62
+ <pre
63
+ data-markstream-code-block={enhanceable ? '1' : undefined}
64
+ data-markstream-language={languageRaw || undefined}
65
+ data-markstream-loading={loading ? '1' : undefined}
66
+ data-markstream-diff={diff ? '1' : undefined}
67
+ data-markstream-original={diff ? encodeDataPayload(getString((node as any)?.originalCode)) : undefined}
68
+ data-markstream-updated={diff ? encodeDataPayload(getString((node as any)?.updatedCode)) : undefined}
69
+ data-markstream-pre="1"
70
+ data-markstream-line-numbers={showLineGutter ? '1' : undefined}
71
+ aria-busy={loading ? 'true' : undefined}
72
+ class:markstream-pre--line-numbers={showLineGutter}
73
+ class={[language ? `language-${language}` : '', className].filter(Boolean).join(' ') || undefined}
74
+ style={mergedStyle || undefined}
75
+ >{#if showLineGutter}<span class="markstream-pre__line-numbers" aria-hidden="true"><span class="markstream-pre__line-numbers-text">{lineNumbersText}</span></span>{/if}<code class="markstream-pre__code" translate="no">{displayCode}</code></pre>
21
76
  {/if}
@@ -1,6 +1,10 @@
1
1
  import type { SvelteRenderableNode } from './shared/node-helpers';
2
2
  type Props = {
3
3
  node: SvelteRenderableNode;
4
+ showLineNumbers?: boolean;
5
+ enhanceable?: boolean;
6
+ class?: string;
7
+ style?: string;
4
8
  };
5
9
  declare const PreCodeNode: import("svelte").Component<Props, {}, "">;
6
10
  type PreCodeNode = ReturnType<typeof PreCodeNode>;
@@ -27,6 +27,7 @@ export type NodeRendererCodeBlockProps = Partial<{
27
27
  showPreviewButton: boolean;
28
28
  showCollapseButton: boolean;
29
29
  showFontSizeButtons: boolean;
30
+ showLineNumbers: boolean;
30
31
  htmlPreviewAllowScripts: boolean;
31
32
  htmlPreviewSandbox: string;
32
33
  }> & Record<string, unknown>;