markstream-svelte 0.0.1-beta.4 → 0.0.1-beta.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/dist/components/InlineCodeNode.svelte +60 -8
- package/dist/components/InlineCodeNode.svelte.d.ts +3 -1
- package/dist/components/NodeOutlet.svelte +2 -1
- package/dist/components/NodeRenderer.svelte +254 -12
- package/dist/components/TextNode.svelte +12 -14
- package/dist/components/shared/node-helpers.d.ts +5 -0
- package/dist/components/shared/node-helpers.js +1 -0
- package/dist/composables/useSmoothMarkdownStream.svelte.d.ts +19 -0
- package/dist/composables/useSmoothMarkdownStream.svelte.js +40 -0
- package/dist/context/smoothStreaming.d.ts +15 -0
- package/dist/context/smoothStreaming.js +14 -0
- package/dist/index.css +38 -4
- package/dist/index.d.ts +4 -0
- package/dist/index.js +2 -0
- package/dist/index.px.css +38 -4
- package/dist/index.tailwind.css +38 -4
- package/package.json +6 -4
|
@@ -1,13 +1,65 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type { SvelteRenderableNode } from './shared/node-helpers'
|
|
2
|
+
import type { SvelteRenderableNode, SvelteRenderContext } from './shared/node-helpers'
|
|
3
|
+
import { resolveStreamingTextState } from 'markstream-core'
|
|
3
4
|
import { getString } from './shared/node-helpers'
|
|
4
|
-
|
|
5
|
+
|
|
5
6
|
interface Props {
|
|
6
|
-
node: SvelteRenderableNode
|
|
7
|
+
node: SvelteRenderableNode
|
|
8
|
+
context?: SvelteRenderContext
|
|
9
|
+
indexKey?: string | number
|
|
7
10
|
}
|
|
8
|
-
|
|
9
|
-
let {
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
|
|
12
|
+
let {
|
|
13
|
+
node,
|
|
14
|
+
context = undefined,
|
|
15
|
+
indexKey = undefined,
|
|
16
|
+
}: Props = $props()
|
|
17
|
+
|
|
18
|
+
let previousKey = ''
|
|
19
|
+
let previousCode = ''
|
|
20
|
+
let deltaClass = 'markstream-svelte-text__stream-delta--a'
|
|
21
|
+
|
|
22
|
+
const code = $derived(getString((node as any)?.code ?? (node as any)?.content ?? (node as any)?.raw))
|
|
23
|
+
const streamKey = $derived(
|
|
24
|
+
`${String(context?.customId ?? 'global')}:${String(context?.streamRenderVersion ?? 0)}:${String(indexKey ?? 'inline-code')}`,
|
|
25
|
+
)
|
|
26
|
+
const fadeEnabled = $derived(context?.fade !== false)
|
|
27
|
+
|
|
28
|
+
const streamInfo = $derived.by(() => {
|
|
29
|
+
const state = context?.textStreamState
|
|
30
|
+
const previous = streamKey === previousKey
|
|
31
|
+
? previousCode
|
|
32
|
+
: (state?.get(streamKey) ?? '')
|
|
33
|
+
|
|
34
|
+
const result = resolveStreamingTextState({
|
|
35
|
+
nextContent: code,
|
|
36
|
+
previousContent: previous,
|
|
37
|
+
typewriterEnabled: fadeEnabled,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
if (result.appended) {
|
|
41
|
+
deltaClass = deltaClass.endsWith('--a')
|
|
42
|
+
? 'markstream-svelte-text__stream-delta--b'
|
|
43
|
+
: 'markstream-svelte-text__stream-delta--a'
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
previousKey = streamKey
|
|
47
|
+
previousCode = code
|
|
48
|
+
state?.set(streamKey, code)
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
stableCode: result.settledContent,
|
|
52
|
+
deltaCode: result.streamedDelta,
|
|
53
|
+
deltaClass,
|
|
54
|
+
}
|
|
55
|
+
})
|
|
12
56
|
</script>
|
|
13
|
-
|
|
57
|
+
|
|
58
|
+
<code class="inline-code-node">
|
|
59
|
+
{streamInfo.stableCode}
|
|
60
|
+
{#if streamInfo.deltaCode}
|
|
61
|
+
<span class="markstream-svelte-text__stream-delta text-node-stream-delta {streamInfo.deltaClass}">
|
|
62
|
+
{streamInfo.deltaCode}
|
|
63
|
+
</span>
|
|
64
|
+
{/if}
|
|
65
|
+
</code>
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { SvelteRenderableNode } from './shared/node-helpers';
|
|
1
|
+
import type { SvelteRenderableNode, SvelteRenderContext } from './shared/node-helpers';
|
|
2
2
|
interface Props {
|
|
3
3
|
node: SvelteRenderableNode;
|
|
4
|
+
context?: SvelteRenderContext;
|
|
5
|
+
indexKey?: string | number;
|
|
4
6
|
}
|
|
5
7
|
declare const InlineCodeNode: import("svelte").Component<Props, {}, "">;
|
|
6
8
|
type InlineCodeNode = ReturnType<typeof InlineCodeNode>;
|
|
@@ -103,6 +103,7 @@
|
|
|
103
103
|
isDark={context?.isDark}
|
|
104
104
|
indexKey={indexKey}
|
|
105
105
|
typewriter={context?.typewriter}
|
|
106
|
+
fade={context?.fade}
|
|
106
107
|
{...customInputs}
|
|
107
108
|
/>
|
|
108
109
|
{:else if resolvedType === 'text' || resolvedType === 'text_special'}
|
|
@@ -136,7 +137,7 @@
|
|
|
136
137
|
{:else if resolvedType === 'image'}
|
|
137
138
|
<ImageNode {node} />
|
|
138
139
|
{:else if resolvedType === 'inline_code'}
|
|
139
|
-
<InlineCodeNode {node} />
|
|
140
|
+
<InlineCodeNode {node} {context} {indexKey} />
|
|
140
141
|
{:else if resolvedType === 'strong'}
|
|
141
142
|
<StrongNode {node} {context} {indexKey} />
|
|
142
143
|
{:else if resolvedType === 'emphasis'}
|
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
SvelteRenderableNode,
|
|
6
6
|
SvelteRenderContext,
|
|
7
7
|
} from './shared/node-helpers'
|
|
8
|
-
import { onDestroy, onMount, tick } from 'svelte'
|
|
8
|
+
import { getContext, onDestroy, onMount, setContext, tick } from 'svelte'
|
|
9
9
|
import { getCustomNodeComponents, subscribeCustomComponents } from '../customComponents'
|
|
10
10
|
import { disposeRenderedHtmlEnhancements, enhanceRenderedHtml } from '../enhanceRenderedHtml'
|
|
11
11
|
import NodeOutlet from './NodeOutlet.svelte'
|
|
12
12
|
import { buildRenderContext, resolveParsedNodes } from './shared/node-helpers'
|
|
13
|
+
import { useSmoothMarkdownStream } from '../composables/useSmoothMarkdownStream.svelte'
|
|
14
|
+
import { SMOOTH_STREAMING_CONTEXT } from '../context/smoothStreaming'
|
|
15
|
+
import type { SmoothStreamingContextValue } from '../context/smoothStreaming'
|
|
13
16
|
|
|
14
17
|
type NodeRendererComponentProps = NodeRendererProps & NodeRendererEvents & {
|
|
15
18
|
className?: string
|
|
@@ -45,7 +48,8 @@
|
|
|
45
48
|
isDark = false,
|
|
46
49
|
customId = undefined,
|
|
47
50
|
indexKey = undefined,
|
|
48
|
-
typewriter =
|
|
51
|
+
typewriter = false,
|
|
52
|
+
fade = true,
|
|
49
53
|
batchRendering = true,
|
|
50
54
|
initialRenderBatchSize = 40,
|
|
51
55
|
renderBatchSize = 80,
|
|
@@ -56,6 +60,8 @@
|
|
|
56
60
|
maxLiveNodes = 320,
|
|
57
61
|
liveNodeBuffer = 60,
|
|
58
62
|
allowHtml = true,
|
|
63
|
+
smoothStreaming = 'auto' as boolean | 'auto',
|
|
64
|
+
smoothStreamingOptions = undefined,
|
|
59
65
|
className = '',
|
|
60
66
|
onCopy = undefined,
|
|
61
67
|
onHandleArtifactClick = undefined,
|
|
@@ -78,10 +84,91 @@
|
|
|
78
84
|
let renderBatchToken = 0
|
|
79
85
|
const textStreamState = new Map<string, string>()
|
|
80
86
|
|
|
87
|
+
const smoothStream = useSmoothMarkdownStream(smoothStreamingOptions)
|
|
88
|
+
let hasMountedForSmoothStreaming = $state(typeof window === 'undefined' || smoothStreaming === true)
|
|
89
|
+
const hasNodes = $derived(Array.isArray(nodes))
|
|
90
|
+
const parentSmoothStreaming = getContext<SmoothStreamingContextValue | undefined>(
|
|
91
|
+
SMOOTH_STREAMING_CONTEXT,
|
|
92
|
+
)
|
|
93
|
+
const smoothStreamingEligible = $derived.by(() => {
|
|
94
|
+
if (smoothStreaming === false)
|
|
95
|
+
return false
|
|
96
|
+
if (hasNodes)
|
|
97
|
+
return false
|
|
98
|
+
// When the parent renderer is already pacing content, avoid double-pacing
|
|
99
|
+
// in nested renderers (e.g. thinking blocks, custom tag content).
|
|
100
|
+
// Only applies in 'auto' mode — smoothStreaming === true explicitly opts in.
|
|
101
|
+
if (smoothStreaming !== true && parentSmoothStreaming?.())
|
|
102
|
+
return false
|
|
103
|
+
if (smoothStreaming === true)
|
|
104
|
+
return true
|
|
105
|
+
return typewriter === true || (maxLiveNodes ?? 0) <= 0
|
|
106
|
+
})
|
|
107
|
+
const smoothStreamingEnabled = $derived(hasMountedForSmoothStreaming && smoothStreamingEligible)
|
|
108
|
+
setContext(SMOOTH_STREAMING_CONTEXT, () => smoothStreamingEnabled)
|
|
109
|
+
|
|
110
|
+
// Baseline sync: in auto mode with initial static content, ensure the smooth
|
|
111
|
+
// stream source/visible is already synced before smooth streaming activates.
|
|
112
|
+
// This prevents a blank flash when the mount gate opens and renderContent
|
|
113
|
+
// switches from raw content to smoothStream.visible (which would be empty).
|
|
114
|
+
if (
|
|
115
|
+
smoothStreaming !== true
|
|
116
|
+
&& !Array.isArray(nodes)
|
|
117
|
+
&& content
|
|
118
|
+
) {
|
|
119
|
+
smoothStream.reset(content)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const renderContent = $derived(smoothStreamingEnabled ? smoothStream.visible : (content ?? ''))
|
|
123
|
+
const rawContent = $derived(content ?? '')
|
|
124
|
+
const smoothSourceSynced = $derived(hasNodes || smoothStream.source === rawContent)
|
|
125
|
+
const requestedFinal = $derived.by(() => {
|
|
126
|
+
const base = parseOptions ?? {}
|
|
127
|
+
return final ?? (base as any).final
|
|
128
|
+
})
|
|
129
|
+
const effectiveFinal = $derived.by(() => {
|
|
130
|
+
if (smoothStreamingEnabled && requestedFinal != null)
|
|
131
|
+
return Boolean(requestedFinal && smoothSourceSynced && smoothStream.caughtUp)
|
|
132
|
+
return requestedFinal
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
onMount(() => {
|
|
136
|
+
hasMountedForSmoothStreaming = true
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
$effect(() => {
|
|
140
|
+
const nextContent = content ?? ''
|
|
141
|
+
if (hasNodes) {
|
|
142
|
+
smoothStream.reset('')
|
|
143
|
+
return
|
|
144
|
+
}
|
|
145
|
+
if (!smoothStreamingEnabled) {
|
|
146
|
+
smoothStream.reset(nextContent)
|
|
147
|
+
if (requestedFinal)
|
|
148
|
+
smoothStream.finish({ flush: true })
|
|
149
|
+
return
|
|
150
|
+
}
|
|
151
|
+
const source = smoothStream.source
|
|
152
|
+
if (!nextContent) {
|
|
153
|
+
smoothStream.reset('')
|
|
154
|
+
}
|
|
155
|
+
else if (nextContent === source) {
|
|
156
|
+
// no-op
|
|
157
|
+
}
|
|
158
|
+
else if (nextContent.startsWith(source)) {
|
|
159
|
+
smoothStream.enqueue(nextContent.slice(source.length))
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
smoothStream.reset(nextContent)
|
|
163
|
+
}
|
|
164
|
+
if (requestedFinal)
|
|
165
|
+
smoothStream.finish()
|
|
166
|
+
})
|
|
167
|
+
|
|
81
168
|
let rendererProps = $derived({
|
|
82
|
-
content,
|
|
169
|
+
content: renderContent,
|
|
83
170
|
nodes,
|
|
84
|
-
final,
|
|
171
|
+
final: effectiveFinal,
|
|
85
172
|
parseOptions,
|
|
86
173
|
customMarkdownIt,
|
|
87
174
|
debugPerformance,
|
|
@@ -106,6 +193,7 @@
|
|
|
106
193
|
customId,
|
|
107
194
|
indexKey,
|
|
108
195
|
typewriter,
|
|
196
|
+
fade,
|
|
109
197
|
batchRendering,
|
|
110
198
|
initialRenderBatchSize,
|
|
111
199
|
renderBatchSize,
|
|
@@ -116,12 +204,14 @@
|
|
|
116
204
|
maxLiveNodes,
|
|
117
205
|
liveNodeBuffer,
|
|
118
206
|
allowHtml,
|
|
207
|
+
smoothStreaming,
|
|
208
|
+
smoothStreamingOptions,
|
|
119
209
|
} satisfies NodeRendererProps)
|
|
120
210
|
|
|
121
211
|
$effect.pre(() => {
|
|
122
|
-
if (previousContent !==
|
|
212
|
+
if (previousContent !== renderContent || previousNodes !== nodes) {
|
|
123
213
|
streamRenderVersion += 1
|
|
124
|
-
previousContent =
|
|
214
|
+
previousContent = renderContent
|
|
125
215
|
previousNodes = nodes
|
|
126
216
|
}
|
|
127
217
|
})
|
|
@@ -133,7 +223,7 @@
|
|
|
133
223
|
console.info('[markstream-svelte][perf] parse(sync)', {
|
|
134
224
|
ms: Math.round(performance.now() - start),
|
|
135
225
|
nodes: nextParsedNodes.length,
|
|
136
|
-
contentLength:
|
|
226
|
+
contentLength: renderContent?.length ?? 0,
|
|
137
227
|
})
|
|
138
228
|
}
|
|
139
229
|
return nextParsedNodes
|
|
@@ -167,7 +257,7 @@
|
|
|
167
257
|
void renderBatchDelay
|
|
168
258
|
void renderBatchBudgetMs
|
|
169
259
|
void renderBatchIdleTimeoutMs
|
|
170
|
-
void
|
|
260
|
+
void effectiveFinal
|
|
171
261
|
void renderedNodeCount
|
|
172
262
|
syncRenderedNodeWindow()
|
|
173
263
|
})
|
|
@@ -175,7 +265,7 @@
|
|
|
175
265
|
|
|
176
266
|
$effect(() => {
|
|
177
267
|
void rootEl
|
|
178
|
-
void
|
|
268
|
+
void effectiveFinal
|
|
179
269
|
void parsedNodes
|
|
180
270
|
void renderedNodes
|
|
181
271
|
void isDark
|
|
@@ -203,6 +293,7 @@
|
|
|
203
293
|
enhancementHandle?.dispose()
|
|
204
294
|
enhancementHandle = null
|
|
205
295
|
disposeRenderedHtmlEnhancements(rootEl)
|
|
296
|
+
clearTypewriterCursorTimeout()
|
|
206
297
|
})
|
|
207
298
|
|
|
208
299
|
function toPositiveInteger(value: unknown, fallback: number) {
|
|
@@ -213,7 +304,7 @@
|
|
|
213
304
|
function syncRenderedNodeWindow() {
|
|
214
305
|
const total = parsedNodes.length
|
|
215
306
|
const initialCount = Math.min(total, toPositiveInteger(initialRenderBatchSize, 40))
|
|
216
|
-
const shouldBatch =
|
|
307
|
+
const shouldBatch = effectiveFinal !== false && batchRendering !== false && total > initialCount
|
|
217
308
|
|
|
218
309
|
if (!shouldBatch) {
|
|
219
310
|
cancelRenderBatch()
|
|
@@ -296,7 +387,7 @@
|
|
|
296
387
|
async function scheduleEnhancement() {
|
|
297
388
|
if (!rootEl || typeof window === 'undefined')
|
|
298
389
|
return
|
|
299
|
-
const enhancementFinal = typeof
|
|
390
|
+
const enhancementFinal = typeof effectiveFinal === 'boolean' ? effectiveFinal : !hasLoadingNodes(parsedNodes)
|
|
300
391
|
if (!enhancementFinal) {
|
|
301
392
|
enhancementToken += 1
|
|
302
393
|
enhancementHandle?.dispose()
|
|
@@ -335,6 +426,156 @@
|
|
|
335
426
|
return false
|
|
336
427
|
}
|
|
337
428
|
|
|
429
|
+
// ── Typewriter cursor ──
|
|
430
|
+
let typewriterCursorEl: HTMLSpanElement | null = $state(null)
|
|
431
|
+
let showTypewriterCursor = $state(false)
|
|
432
|
+
let typewriterCursorTimeout: ReturnType<typeof setTimeout> | undefined
|
|
433
|
+
let lastTypewriterContentLength = 0
|
|
434
|
+
const TYPEWRITER_CURSOR_EXCLUDED_NODE_TYPES = new Set(['code_block', 'admonition', 'table', 'math_block', 'html_block', 'image'])
|
|
435
|
+
|
|
436
|
+
function shouldSkipTypewriterCursorForNode(node: unknown) {
|
|
437
|
+
if (!node || typeof node !== 'object')
|
|
438
|
+
return false
|
|
439
|
+
const type = (node as Record<string, unknown>).type
|
|
440
|
+
return typeof type === 'string' && TYPEWRITER_CURSOR_EXCLUDED_NODE_TYPES.has(type)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function shouldShowTypewriterCursorForCurrentNodes() {
|
|
444
|
+
const lastNode = parsedNodes[parsedNodes.length - 1]
|
|
445
|
+
return !shouldSkipTypewriterCursorForNode(lastNode)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function getNodeTextLength(node: unknown): number {
|
|
449
|
+
if (!node || typeof node !== 'object')
|
|
450
|
+
return 0
|
|
451
|
+
const record = node as Record<string, unknown>
|
|
452
|
+
const direct = record.raw ?? record.content ?? record.code
|
|
453
|
+
if (typeof direct === 'string')
|
|
454
|
+
return direct.length
|
|
455
|
+
const children = record.children
|
|
456
|
+
if (Array.isArray(children))
|
|
457
|
+
return children.reduce((total: number, child: unknown) => total + getNodeTextLength(child), 0)
|
|
458
|
+
const items = record.items
|
|
459
|
+
if (Array.isArray(items))
|
|
460
|
+
return items.reduce((total: number, item: unknown) => total + getNodeTextLength(item), 0)
|
|
461
|
+
return 0
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function getTypewriterContentLength() {
|
|
465
|
+
if (nodes?.length)
|
|
466
|
+
return nodes.reduce((total: number, node: unknown) => total + getNodeTextLength(node), 0)
|
|
467
|
+
// Use raw content length, not renderContent (which may be the paced-out
|
|
468
|
+
// visible portion when smooth streaming is active).
|
|
469
|
+
return (content ?? '').length
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function clearTypewriterCursorTimeout() {
|
|
473
|
+
if (!typewriterCursorTimeout)
|
|
474
|
+
return
|
|
475
|
+
clearTimeout(typewriterCursorTimeout)
|
|
476
|
+
typewriterCursorTimeout = undefined
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
function getLastTextNode(root: HTMLElement) {
|
|
480
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
481
|
+
acceptNode(node) {
|
|
482
|
+
const text = node.textContent ?? ''
|
|
483
|
+
if (!text.trim())
|
|
484
|
+
return NodeFilter.FILTER_REJECT
|
|
485
|
+
const parent = node.parentElement
|
|
486
|
+
if (!parent)
|
|
487
|
+
return NodeFilter.FILTER_REJECT
|
|
488
|
+
if (parent.closest('.typewriter-cursor, .height-estimation-probes, [data-node-type="code_block"], [data-node-type="admonition"], [data-node-type="table"], [data-node-type="math_block"], [data-node-type="html_block"], [data-node-type="image"], script, style'))
|
|
489
|
+
return NodeFilter.FILTER_REJECT
|
|
490
|
+
return NodeFilter.FILTER_ACCEPT
|
|
491
|
+
},
|
|
492
|
+
})
|
|
493
|
+
let last: Text | null = null
|
|
494
|
+
let current = walker.nextNode()
|
|
495
|
+
while (current) {
|
|
496
|
+
last = current as Text
|
|
497
|
+
current = walker.nextNode()
|
|
498
|
+
}
|
|
499
|
+
return last
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
function updateTypewriterCursorPosition() {
|
|
503
|
+
if (typeof window === 'undefined' || !showTypewriterCursor || !rootEl || !typewriterCursorEl)
|
|
504
|
+
return
|
|
505
|
+
const root = rootEl
|
|
506
|
+
const cursor = typewriterCursorEl
|
|
507
|
+
const lastText = getLastTextNode(root)
|
|
508
|
+
const rootRect = root.getBoundingClientRect()
|
|
509
|
+
let left = 0
|
|
510
|
+
let top = 0
|
|
511
|
+
let height = 20
|
|
512
|
+
|
|
513
|
+
if (lastText?.textContent) {
|
|
514
|
+
const range = document.createRange()
|
|
515
|
+
const end = lastText.textContent.length
|
|
516
|
+
range.setStart(lastText, Math.max(0, end - 1))
|
|
517
|
+
range.setEnd(lastText, end)
|
|
518
|
+
const rects = typeof range.getClientRects === 'function'
|
|
519
|
+
? range.getClientRects()
|
|
520
|
+
: undefined
|
|
521
|
+
const rect = rects?.[rects.length - 1] ?? lastText.parentElement?.getBoundingClientRect()
|
|
522
|
+
if (rect) {
|
|
523
|
+
left = rect.right - rootRect.left + root.scrollLeft
|
|
524
|
+
top = rect.top - rootRect.top + root.scrollTop
|
|
525
|
+
height = rect.height || height
|
|
526
|
+
}
|
|
527
|
+
range.detach()
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
cursor.style.transform = `translate(${Math.max(0, left)}px, ${Math.max(0, top)}px)`
|
|
531
|
+
cursor.style.height = `${height}px`
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
$effect(() => {
|
|
535
|
+
void renderContent
|
|
536
|
+
void nodes
|
|
537
|
+
void typewriter
|
|
538
|
+
void parsedNodes.length
|
|
539
|
+
void effectiveFinal
|
|
540
|
+
if (typeof window === 'undefined' || hasNodes)
|
|
541
|
+
return
|
|
542
|
+
|
|
543
|
+
// When the stream is final (and effective — smooth streaming has caught up),
|
|
544
|
+
// hide the cursor immediately.
|
|
545
|
+
if (effectiveFinal) {
|
|
546
|
+
showTypewriterCursor = false
|
|
547
|
+
clearTypewriterCursorTimeout()
|
|
548
|
+
return
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const nextLength = getTypewriterContentLength()
|
|
552
|
+
const cursorAllowed = shouldShowTypewriterCursorForCurrentNodes()
|
|
553
|
+
if (typewriter === false || !cursorAllowed || nextLength <= lastTypewriterContentLength) {
|
|
554
|
+
if (typewriter === false || !cursorAllowed)
|
|
555
|
+
showTypewriterCursor = false
|
|
556
|
+
lastTypewriterContentLength = nextLength
|
|
557
|
+
return
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
lastTypewriterContentLength = nextLength
|
|
561
|
+
showTypewriterCursor = true
|
|
562
|
+
clearTypewriterCursorTimeout()
|
|
563
|
+
tick().then(() => {
|
|
564
|
+
updateTypewriterCursorPosition()
|
|
565
|
+
})
|
|
566
|
+
typewriterCursorTimeout = setTimeout(() => {
|
|
567
|
+
showTypewriterCursor = false
|
|
568
|
+
}, 3000)
|
|
569
|
+
})
|
|
570
|
+
|
|
571
|
+
$effect(() => {
|
|
572
|
+
if (!showTypewriterCursor)
|
|
573
|
+
return
|
|
574
|
+
tick().then(() => {
|
|
575
|
+
updateTypewriterCursorPosition()
|
|
576
|
+
})
|
|
577
|
+
})
|
|
578
|
+
|
|
338
579
|
function handleMouseover(event: MouseEvent) {
|
|
339
580
|
const target = event.target as HTMLElement | null
|
|
340
581
|
if (target?.closest('[data-node-index]'))
|
|
@@ -362,9 +603,10 @@
|
|
|
362
603
|
>
|
|
363
604
|
{#each renderedNodes as node, index ((indexKey != null ? String(indexKey) : 'markdown-renderer') + '-' + index)}
|
|
364
605
|
<div class="node-slot" data-node-index={index} data-node-type={(node as any)?.type}>
|
|
365
|
-
<div class:typewriter-node={
|
|
606
|
+
<div class:fade-node={fade !== false && String((node as any)?.type || '') !== 'code_block'} class:typewriter-node={fade !== false && String((node as any)?.type || '') !== 'code_block'} class="node-content" data-node-index={index}>
|
|
366
607
|
<NodeOutlet node={node} context={renderContext} indexKey={(indexKey != null ? String(indexKey) : 'markdown-renderer') + '-' + index} />
|
|
367
608
|
</div>
|
|
368
609
|
</div>
|
|
369
610
|
{/each}
|
|
611
|
+
{#if showTypewriterCursor}<span bind:this={typewriterCursorEl} class="typewriter-cursor" aria-hidden="true"></span>{/if}
|
|
370
612
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { SvelteRenderableNode, SvelteRenderContext } from './shared/node-helpers'
|
|
3
|
+
import { resolveStreamingTextState } from 'markstream-core'
|
|
3
4
|
import { getString } from './shared/node-helpers'
|
|
4
5
|
|
|
5
6
|
interface Props {
|
|
@@ -23,30 +24,27 @@
|
|
|
23
24
|
const content = $derived(getString((node as any)?.content ?? (node as any)?.raw))
|
|
24
25
|
const centered = $derived(Boolean((node as any)?.center))
|
|
25
26
|
const streamKey = $derived(String(context?.customId ?? 'global') + ':' + String(context?.streamRenderVersion ?? 0) + ':' + String(indexKey ?? 'node'))
|
|
27
|
+
const fadeEnabled = $derived(context?.fade !== false)
|
|
26
28
|
|
|
27
29
|
const streamInfo = $derived.by(() => {
|
|
28
30
|
const state = context?.textStreamState
|
|
29
31
|
const previous = streamKey === previousKey ? previousContent : (state?.get(streamKey) ?? '')
|
|
30
|
-
|
|
31
|
-
let stableContent = ''
|
|
32
|
-
let deltaContent = ''
|
|
33
32
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const result = resolveStreamingTextState({
|
|
34
|
+
nextContent: content,
|
|
35
|
+
previousContent: previous,
|
|
36
|
+
typewriterEnabled: fadeEnabled,
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
if (result.appended)
|
|
37
40
|
deltaClass = deltaClass.endsWith('--a') ? 'markstream-svelte-text__stream-delta--b' : 'markstream-svelte-text__stream-delta--a'
|
|
38
|
-
|
|
39
|
-
else {
|
|
40
|
-
stableContent = content
|
|
41
|
-
deltaContent = ''
|
|
42
|
-
}
|
|
43
|
-
|
|
41
|
+
|
|
44
42
|
previousKey = streamKey
|
|
45
43
|
previousContent = content
|
|
46
44
|
state?.set(streamKey, content)
|
|
47
45
|
|
|
48
|
-
return { stableContent, deltaContent, deltaClass }
|
|
46
|
+
return { stableContent: result.settledContent, deltaContent: result.streamedDelta, deltaClass }
|
|
49
47
|
})
|
|
50
48
|
</script>
|
|
51
49
|
|
|
52
|
-
<span data-typewriter={typewriter
|
|
50
|
+
<span data-typewriter={context?.typewriter === true ? '1' : undefined} class:markstream-svelte-text--centered={centered} class="markstream-svelte-text-node text-node">{streamInfo.stableContent}{#if streamInfo.deltaContent}<span class="markstream-svelte-text__stream-delta text-node-stream-delta {streamInfo.deltaClass}">{streamInfo.deltaContent}</span>{/if}</span>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SmoothMarkdownStreamOptions } from 'markstream-core';
|
|
1
2
|
import type { BaseNode, HtmlPolicy, MarkdownIt, ParsedNode, ParseOptions } from 'stream-markdown-parser';
|
|
2
3
|
import type { CustomComponentMap } from '../../customComponents';
|
|
3
4
|
import type { CodeBlockMonacoOptions, CodeBlockMonacoTheme } from '../../types/monaco';
|
|
@@ -99,6 +100,7 @@ export interface NodeRendererProps {
|
|
|
99
100
|
customId?: string;
|
|
100
101
|
indexKey?: number | string;
|
|
101
102
|
typewriter?: boolean;
|
|
103
|
+
fade?: boolean;
|
|
102
104
|
batchRendering?: boolean;
|
|
103
105
|
initialRenderBatchSize?: number;
|
|
104
106
|
renderBatchSize?: number;
|
|
@@ -109,6 +111,8 @@ export interface NodeRendererProps {
|
|
|
109
111
|
maxLiveNodes?: number;
|
|
110
112
|
liveNodeBuffer?: number;
|
|
111
113
|
allowHtml?: boolean;
|
|
114
|
+
smoothStreaming?: boolean | 'auto';
|
|
115
|
+
smoothStreamingOptions?: SmoothMarkdownStreamOptions;
|
|
112
116
|
}
|
|
113
117
|
export interface SvelteRenderContext {
|
|
114
118
|
customId?: string;
|
|
@@ -116,6 +120,7 @@ export interface SvelteRenderContext {
|
|
|
116
120
|
indexKey?: string;
|
|
117
121
|
final?: boolean;
|
|
118
122
|
typewriter?: boolean;
|
|
123
|
+
fade?: boolean;
|
|
119
124
|
textStreamState?: Map<string, string>;
|
|
120
125
|
streamRenderVersion?: number;
|
|
121
126
|
showTooltips?: boolean;
|
|
@@ -29,6 +29,7 @@ export function buildRenderContext(props, events = {}, textStreamState, streamRe
|
|
|
29
29
|
indexKey: props.indexKey != null ? String(props.indexKey) : undefined,
|
|
30
30
|
final: props.final,
|
|
31
31
|
typewriter: props.typewriter,
|
|
32
|
+
fade: props.fade,
|
|
32
33
|
textStreamState,
|
|
33
34
|
streamRenderVersion,
|
|
34
35
|
showTooltips: props.showTooltips,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SmoothMarkdownStreamOptions } from 'markstream-core';
|
|
2
|
+
export type { SmoothMarkdownStreamOptions };
|
|
3
|
+
export interface SmoothMarkdownStreamControllerSvelte {
|
|
4
|
+
source: string;
|
|
5
|
+
visible: string;
|
|
6
|
+
done: boolean;
|
|
7
|
+
caughtUp: boolean;
|
|
8
|
+
final: boolean;
|
|
9
|
+
pendingChars: number;
|
|
10
|
+
enqueue: (chunk: string) => void;
|
|
11
|
+
finish: (options?: {
|
|
12
|
+
flush?: boolean;
|
|
13
|
+
}) => void;
|
|
14
|
+
flush: () => void;
|
|
15
|
+
reset: (initialMarkdown?: string) => void;
|
|
16
|
+
pause: () => void;
|
|
17
|
+
resume: () => void;
|
|
18
|
+
}
|
|
19
|
+
export declare function useSmoothMarkdownStream(options?: SmoothMarkdownStreamOptions): SmoothMarkdownStreamControllerSvelte;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createSmoothMarkdownStream } from 'markstream-core';
|
|
2
|
+
import { onDestroy } from 'svelte';
|
|
3
|
+
export function useSmoothMarkdownStream(options = {}) {
|
|
4
|
+
let source = $state('');
|
|
5
|
+
let visible = $state('');
|
|
6
|
+
let done = $state(false);
|
|
7
|
+
let pendingChars = $state(0);
|
|
8
|
+
let caughtUp = $state(false);
|
|
9
|
+
let final = $state(false);
|
|
10
|
+
const controller = createSmoothMarkdownStream(options);
|
|
11
|
+
const sync = () => {
|
|
12
|
+
const snapshot = controller.getSnapshot();
|
|
13
|
+
source = snapshot.source;
|
|
14
|
+
visible = snapshot.visible;
|
|
15
|
+
done = snapshot.done;
|
|
16
|
+
pendingChars = snapshot.pendingChars;
|
|
17
|
+
caughtUp = snapshot.caughtUp;
|
|
18
|
+
final = snapshot.final;
|
|
19
|
+
};
|
|
20
|
+
const unsubscribe = controller.subscribe(sync);
|
|
21
|
+
sync();
|
|
22
|
+
onDestroy(() => {
|
|
23
|
+
unsubscribe();
|
|
24
|
+
controller.destroy();
|
|
25
|
+
});
|
|
26
|
+
return {
|
|
27
|
+
get source() { return source; },
|
|
28
|
+
get visible() { return visible; },
|
|
29
|
+
get done() { return done; },
|
|
30
|
+
get caughtUp() { return caughtUp; },
|
|
31
|
+
get final() { return final; },
|
|
32
|
+
get pendingChars() { return pendingChars; },
|
|
33
|
+
enqueue: chunk => controller.enqueue(chunk),
|
|
34
|
+
finish: opts => controller.finish(opts),
|
|
35
|
+
flush: () => controller.flush(),
|
|
36
|
+
reset: initialMarkdown => controller.reset(initialMarkdown),
|
|
37
|
+
pause: () => controller.pause(),
|
|
38
|
+
resume: () => controller.resume(),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context key and types for Svelte smooth streaming parent suppression.
|
|
3
|
+
*
|
|
4
|
+
* When a parent NodeRenderer is already pacing content via smooth streaming,
|
|
5
|
+
* nested renderers (e.g. thinking blocks, custom tag content) should suppress
|
|
6
|
+
* their own smooth streaming to avoid double-pacing.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const parentSmoothStreaming = getContext<SmoothStreamingContextValue | undefined>(
|
|
10
|
+
* SMOOTH_STREAMING_CONTEXT,
|
|
11
|
+
* )
|
|
12
|
+
* setContext(SMOOTH_STREAMING_CONTEXT, () => smoothStreamingEnabled)
|
|
13
|
+
*/
|
|
14
|
+
export declare const SMOOTH_STREAMING_CONTEXT = "markstreamSmoothStreaming";
|
|
15
|
+
export type SmoothStreamingContextValue = () => boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context key and types for Svelte smooth streaming parent suppression.
|
|
3
|
+
*
|
|
4
|
+
* When a parent NodeRenderer is already pacing content via smooth streaming,
|
|
5
|
+
* nested renderers (e.g. thinking blocks, custom tag content) should suppress
|
|
6
|
+
* their own smooth streaming to avoid double-pacing.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* const parentSmoothStreaming = getContext<SmoothStreamingContextValue | undefined>(
|
|
10
|
+
* SMOOTH_STREAMING_CONTEXT,
|
|
11
|
+
* )
|
|
12
|
+
* setContext(SMOOTH_STREAMING_CONTEXT, () => smoothStreamingEnabled)
|
|
13
|
+
*/
|
|
14
|
+
export const SMOOTH_STREAMING_CONTEXT = 'markstreamSmoothStreaming';
|
package/dist/index.css
CHANGED
|
@@ -130,9 +130,10 @@ markstream-svelte {
|
|
|
130
130
|
width: 100%;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
.markstream-svelte .typewriter-node
|
|
133
|
+
.markstream-svelte .typewriter-node,
|
|
134
|
+
.markstream-svelte .fade-node {
|
|
134
135
|
opacity: 0;
|
|
135
|
-
animation: typewriter-fade var(--typewriter-fade-duration,
|
|
136
|
+
animation: typewriter-fade var(--fade-duration, var(--typewriter-fade-duration, 280ms)) var(--fade-ease, var(--typewriter-fade-ease, cubic-bezier(0.33, 0, 0.67, 1))) both;
|
|
136
137
|
will-change: opacity;
|
|
137
138
|
}
|
|
138
139
|
|
|
@@ -151,8 +152,8 @@ markstream-svelte {
|
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
.markstream-svelte .markstream-svelte-text__stream-delta {
|
|
154
|
-
animation-duration: var(--stream-update-fade-duration, var(--typewriter-fade-duration,
|
|
155
|
-
animation-timing-function: var(--stream-update-fade-ease, var(--typewriter-fade-ease,
|
|
155
|
+
animation-duration: var(--stream-update-fade-duration, var(--fade-duration, var(--typewriter-fade-duration, 280ms)));
|
|
156
|
+
animation-timing-function: var(--stream-update-fade-ease, var(--fade-ease, var(--typewriter-fade-ease, cubic-bezier(0.33, 0, 0.67, 1))));
|
|
156
157
|
animation-fill-mode: both;
|
|
157
158
|
will-change: opacity;
|
|
158
159
|
}
|
|
@@ -278,6 +279,29 @@ markstream-svelte {
|
|
|
278
279
|
to { opacity: 1; }
|
|
279
280
|
}
|
|
280
281
|
|
|
282
|
+
.markstream-svelte .typewriter-cursor {
|
|
283
|
+
position: absolute;
|
|
284
|
+
left: 0;
|
|
285
|
+
top: 0;
|
|
286
|
+
display: inline-block;
|
|
287
|
+
width: 0.55em;
|
|
288
|
+
height: 1em;
|
|
289
|
+
margin-left: 0.08em;
|
|
290
|
+
vertical-align: -0.12em;
|
|
291
|
+
border-right: 2px solid currentColor;
|
|
292
|
+
pointer-events: none;
|
|
293
|
+
animation: typewriter-cursor-blink 1s steps(1, end) infinite;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
@keyframes typewriter-cursor-blink {
|
|
297
|
+
0%, 49% {
|
|
298
|
+
opacity: 1;
|
|
299
|
+
}
|
|
300
|
+
50%, 100% {
|
|
301
|
+
opacity: 0;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
281
305
|
@media (prefers-reduced-motion: reduce) {
|
|
282
306
|
.markstream-svelte .link-loading-indicator {
|
|
283
307
|
animation: none !important;
|
|
@@ -287,6 +311,16 @@ markstream-svelte {
|
|
|
287
311
|
.markstream-svelte .markstream-svelte-text__stream-delta {
|
|
288
312
|
animation: none !important;
|
|
289
313
|
}
|
|
314
|
+
|
|
315
|
+
.markstream-svelte .typewriter-node,
|
|
316
|
+
.markstream-svelte .fade-node {
|
|
317
|
+
animation: none !important;
|
|
318
|
+
opacity: 1;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.markstream-svelte .typewriter-cursor {
|
|
322
|
+
animation: none !important;
|
|
323
|
+
}
|
|
290
324
|
}
|
|
291
325
|
|
|
292
326
|
.markstream-svelte strong {
|
package/dist/index.d.ts
CHANGED
|
@@ -49,6 +49,10 @@ export { default as TextNode } from './components/TextNode.svelte';
|
|
|
49
49
|
export { default as ThematicBreakNode } from './components/ThematicBreakNode.svelte';
|
|
50
50
|
export { default as Tooltip } from './components/Tooltip.svelte';
|
|
51
51
|
export { default as VmrContainerNode } from './components/VmrContainerNode.svelte';
|
|
52
|
+
export type { SmoothMarkdownStreamControllerSvelte, SmoothMarkdownStreamOptions, } from './composables/useSmoothMarkdownStream.svelte';
|
|
53
|
+
export { useSmoothMarkdownStream, } from './composables/useSmoothMarkdownStream.svelte';
|
|
54
|
+
export { SMOOTH_STREAMING_CONTEXT, } from './context/smoothStreaming';
|
|
55
|
+
export type { SmoothStreamingContextValue, } from './context/smoothStreaming';
|
|
52
56
|
export { clearGlobalCustomComponents, getCustomComponentsRevision, getCustomNodeComponents, removeCustomComponents, setCustomComponents, subscribeCustomComponents, } from './customComponents';
|
|
53
57
|
export type { CustomComponentMap, MarkstreamSvelteComponent } from './customComponents';
|
|
54
58
|
export { disposeRenderedHtmlEnhancements, enhanceRenderedHtml, } from './enhanceRenderedHtml';
|
package/dist/index.js
CHANGED
|
@@ -48,6 +48,8 @@ export { default as TextNode } from './components/TextNode.svelte';
|
|
|
48
48
|
export { default as ThematicBreakNode } from './components/ThematicBreakNode.svelte';
|
|
49
49
|
export { default as Tooltip } from './components/Tooltip.svelte';
|
|
50
50
|
export { default as VmrContainerNode } from './components/VmrContainerNode.svelte';
|
|
51
|
+
export { useSmoothMarkdownStream, } from './composables/useSmoothMarkdownStream.svelte';
|
|
52
|
+
export { SMOOTH_STREAMING_CONTEXT, } from './context/smoothStreaming';
|
|
51
53
|
export { clearGlobalCustomComponents, getCustomComponentsRevision, getCustomNodeComponents, removeCustomComponents, setCustomComponents, subscribeCustomComponents, } from './customComponents';
|
|
52
54
|
export { disposeRenderedHtmlEnhancements, enhanceRenderedHtml, } from './enhanceRenderedHtml';
|
|
53
55
|
export { setDefaultI18nMap, useSafeI18n } from './i18n/useSafeI18n';
|
package/dist/index.px.css
CHANGED
|
@@ -130,9 +130,10 @@ markstream-svelte {
|
|
|
130
130
|
width: 100%;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
.markstream-svelte .typewriter-node
|
|
133
|
+
.markstream-svelte .typewriter-node,
|
|
134
|
+
.markstream-svelte .fade-node {
|
|
134
135
|
opacity: 0;
|
|
135
|
-
animation: typewriter-fade var(--typewriter-fade-duration,
|
|
136
|
+
animation: typewriter-fade var(--fade-duration, var(--typewriter-fade-duration, 280ms)) var(--fade-ease, var(--typewriter-fade-ease, cubic-bezier(0.33, 0, 0.67, 1))) both;
|
|
136
137
|
will-change: opacity;
|
|
137
138
|
}
|
|
138
139
|
|
|
@@ -151,8 +152,8 @@ markstream-svelte {
|
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
.markstream-svelte .markstream-svelte-text__stream-delta {
|
|
154
|
-
animation-duration: var(--stream-update-fade-duration, var(--typewriter-fade-duration,
|
|
155
|
-
animation-timing-function: var(--stream-update-fade-ease, var(--typewriter-fade-ease,
|
|
155
|
+
animation-duration: var(--stream-update-fade-duration, var(--fade-duration, var(--typewriter-fade-duration, 280ms)));
|
|
156
|
+
animation-timing-function: var(--stream-update-fade-ease, var(--fade-ease, var(--typewriter-fade-ease, cubic-bezier(0.33, 0, 0.67, 1))));
|
|
156
157
|
animation-fill-mode: both;
|
|
157
158
|
will-change: opacity;
|
|
158
159
|
}
|
|
@@ -278,6 +279,29 @@ markstream-svelte {
|
|
|
278
279
|
to { opacity: 1; }
|
|
279
280
|
}
|
|
280
281
|
|
|
282
|
+
.markstream-svelte .typewriter-cursor {
|
|
283
|
+
position: absolute;
|
|
284
|
+
left: 0;
|
|
285
|
+
top: 0;
|
|
286
|
+
display: inline-block;
|
|
287
|
+
width: 0.55em;
|
|
288
|
+
height: 1em;
|
|
289
|
+
margin-left: 0.08em;
|
|
290
|
+
vertical-align: -0.12em;
|
|
291
|
+
border-right: 2px solid currentColor;
|
|
292
|
+
pointer-events: none;
|
|
293
|
+
animation: typewriter-cursor-blink 1s steps(1, end) infinite;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
@keyframes typewriter-cursor-blink {
|
|
297
|
+
0%, 49% {
|
|
298
|
+
opacity: 1;
|
|
299
|
+
}
|
|
300
|
+
50%, 100% {
|
|
301
|
+
opacity: 0;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
281
305
|
@media (prefers-reduced-motion: reduce) {
|
|
282
306
|
.markstream-svelte .link-loading-indicator {
|
|
283
307
|
animation: none !important;
|
|
@@ -287,6 +311,16 @@ markstream-svelte {
|
|
|
287
311
|
.markstream-svelte .markstream-svelte-text__stream-delta {
|
|
288
312
|
animation: none !important;
|
|
289
313
|
}
|
|
314
|
+
|
|
315
|
+
.markstream-svelte .typewriter-node,
|
|
316
|
+
.markstream-svelte .fade-node {
|
|
317
|
+
animation: none !important;
|
|
318
|
+
opacity: 1;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.markstream-svelte .typewriter-cursor {
|
|
322
|
+
animation: none !important;
|
|
323
|
+
}
|
|
290
324
|
}
|
|
291
325
|
|
|
292
326
|
.markstream-svelte strong {
|
package/dist/index.tailwind.css
CHANGED
|
@@ -130,9 +130,10 @@ markstream-svelte {
|
|
|
130
130
|
width: 100%;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
.markstream-svelte .typewriter-node
|
|
133
|
+
.markstream-svelte .typewriter-node,
|
|
134
|
+
.markstream-svelte .fade-node {
|
|
134
135
|
opacity: 0;
|
|
135
|
-
animation: typewriter-fade var(--typewriter-fade-duration,
|
|
136
|
+
animation: typewriter-fade var(--fade-duration, var(--typewriter-fade-duration, 280ms)) var(--fade-ease, var(--typewriter-fade-ease, cubic-bezier(0.33, 0, 0.67, 1))) both;
|
|
136
137
|
will-change: opacity;
|
|
137
138
|
}
|
|
138
139
|
|
|
@@ -151,8 +152,8 @@ markstream-svelte {
|
|
|
151
152
|
}
|
|
152
153
|
|
|
153
154
|
.markstream-svelte .markstream-svelte-text__stream-delta {
|
|
154
|
-
animation-duration: var(--stream-update-fade-duration, var(--typewriter-fade-duration,
|
|
155
|
-
animation-timing-function: var(--stream-update-fade-ease, var(--typewriter-fade-ease,
|
|
155
|
+
animation-duration: var(--stream-update-fade-duration, var(--fade-duration, var(--typewriter-fade-duration, 280ms)));
|
|
156
|
+
animation-timing-function: var(--stream-update-fade-ease, var(--fade-ease, var(--typewriter-fade-ease, cubic-bezier(0.33, 0, 0.67, 1))));
|
|
156
157
|
animation-fill-mode: both;
|
|
157
158
|
will-change: opacity;
|
|
158
159
|
}
|
|
@@ -278,6 +279,29 @@ markstream-svelte {
|
|
|
278
279
|
to { opacity: 1; }
|
|
279
280
|
}
|
|
280
281
|
|
|
282
|
+
.markstream-svelte .typewriter-cursor {
|
|
283
|
+
position: absolute;
|
|
284
|
+
left: 0;
|
|
285
|
+
top: 0;
|
|
286
|
+
display: inline-block;
|
|
287
|
+
width: 0.55em;
|
|
288
|
+
height: 1em;
|
|
289
|
+
margin-left: 0.08em;
|
|
290
|
+
vertical-align: -0.12em;
|
|
291
|
+
border-right: 2px solid currentColor;
|
|
292
|
+
pointer-events: none;
|
|
293
|
+
animation: typewriter-cursor-blink 1s steps(1, end) infinite;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
@keyframes typewriter-cursor-blink {
|
|
297
|
+
0%, 49% {
|
|
298
|
+
opacity: 1;
|
|
299
|
+
}
|
|
300
|
+
50%, 100% {
|
|
301
|
+
opacity: 0;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
281
305
|
@media (prefers-reduced-motion: reduce) {
|
|
282
306
|
.markstream-svelte .link-loading-indicator {
|
|
283
307
|
animation: none !important;
|
|
@@ -287,6 +311,16 @@ markstream-svelte {
|
|
|
287
311
|
.markstream-svelte .markstream-svelte-text__stream-delta {
|
|
288
312
|
animation: none !important;
|
|
289
313
|
}
|
|
314
|
+
|
|
315
|
+
.markstream-svelte .typewriter-node,
|
|
316
|
+
.markstream-svelte .fade-node {
|
|
317
|
+
animation: none !important;
|
|
318
|
+
opacity: 1;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.markstream-svelte .typewriter-cursor {
|
|
322
|
+
animation: none !important;
|
|
323
|
+
}
|
|
290
324
|
}
|
|
291
325
|
|
|
292
326
|
.markstream-svelte strong {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markstream-svelte",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.1-beta.
|
|
4
|
+
"version": "0.0.1-beta.5",
|
|
5
5
|
"description": "Svelte Markdown renderer for Markstream, aligned with markstream-vue and markstream-react.",
|
|
6
6
|
"author": "Simon He",
|
|
7
7
|
"license": "MIT",
|
|
@@ -71,7 +71,8 @@
|
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@floating-ui/dom": "^1.7.6",
|
|
74
|
-
"
|
|
74
|
+
"markstream-core": "0.0.1",
|
|
75
|
+
"stream-markdown-parser": "0.0.95"
|
|
75
76
|
},
|
|
76
77
|
"devDependencies": {
|
|
77
78
|
"@sveltejs/package": "^2.5.7",
|
|
@@ -86,10 +87,11 @@
|
|
|
86
87
|
"vite": "^7.3.1"
|
|
87
88
|
},
|
|
88
89
|
"scripts": {
|
|
89
|
-
"build": "svelte-package -i src -o dist && postcss src/index.css -o dist/index.css && cp dist/index.css dist/index.tailwind.css && node ../../scripts/generate-px-css.mjs",
|
|
90
|
+
"build": "pnpm --dir ../markstream-core build && svelte-package -i src -o dist && postcss src/index.css -o dist/index.css && cp dist/index.css dist/index.tailwind.css && node ../../scripts/generate-px-css.mjs",
|
|
90
91
|
"dev": "vite dev",
|
|
91
92
|
"preview": "vite preview",
|
|
92
93
|
"typecheck": "svelte-check --tsconfig ./tsconfig.json",
|
|
93
|
-
"
|
|
94
|
+
"check:core-published": "node ../../scripts/check-core-published.mjs --package-json package.json --core-package-json ../markstream-core/package.json",
|
|
95
|
+
"release": "pnpm run check:core-published && bumpp --commit --no-tag --no-push && pnpm publish --access public && node ../../scripts/tag-package.mjs --package-json package.json --push"
|
|
94
96
|
}
|
|
95
97
|
}
|