@incremark/svelte 0.2.4 → 0.2.6

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.
@@ -6,24 +6,14 @@
6
6
  <script lang="ts">
7
7
  import type { Component } from 'svelte'
8
8
  import type { Readable } from 'svelte/store'
9
- import type { RootContent } from '@incremark/core'
9
+ import type { RootContent, ParsedBlock } from '@incremark/core'
10
10
  import type { HTML } from 'mdast'
11
-
11
+
12
12
  import { getDefinitionsContext } from '../context/definitionsContext'
13
13
  import type { UseIncremarkReturn } from '../stores/useIncremark'
14
14
  import type { ComponentMap, BlockWithStableId } from './types'
15
-
16
- // 导入默认组件
17
- import IncremarkParagraph from './IncremarkParagraph.svelte'
18
- import IncremarkHeading from './IncremarkHeading.svelte'
19
- import IncremarkCode from './IncremarkCode.svelte'
20
- import IncremarkList from './IncremarkList.svelte'
21
- import IncremarkTable from './IncremarkTable.svelte'
22
- import IncremarkBlockquote from './IncremarkBlockquote.svelte'
23
- import IncremarkThematicBreak from './IncremarkThematicBreak.svelte'
24
- import IncremarkMath from './IncremarkMath.svelte'
25
- import IncremarkHtmlElement from './IncremarkHtmlElement.svelte'
26
- import IncremarkDefault from './IncremarkDefault.svelte'
15
+
16
+ // 导入组件
27
17
  import IncremarkFootnotes from './IncremarkFootnotes.svelte'
28
18
  import IncremarkRenderer from './IncremarkRenderer.svelte'
29
19
 
@@ -40,12 +30,17 @@
40
30
  interface Props {
41
31
  /** 要渲染的块列表(来自 useIncremark 的 blocks) */
42
32
  blocks?: BlockWithStableId[] | Readable<BlockWithStableId[]>
33
+ /** 内容是否完全显示完成(用于控制脚注等需要在内容完全显示后才出现的元素)
34
+ * 如果传入了 incremark,则会自动使用 incremark.isDisplayComplete,此 prop 被忽略 */
35
+ isDisplayComplete?: boolean
43
36
  /** 自定义组件映射,key 为节点类型 */
44
37
  components?: ComponentMap
45
38
  /** 自定义容器组件映射,key 为容器名称(如 'warning', 'info') */
46
39
  customContainers?: Record<string, Component<any>>
47
40
  /** 自定义代码块组件映射,key 为代码语言名称(如 'echart', 'mermaid') */
48
41
  customCodeBlocks?: Record<string, Component<any>>
42
+ /** 代码块配置映射,key 为代码语言名称 */
43
+ codeBlockConfigs?: Record<string, { takeOver?: boolean }>
49
44
  /** 待处理块的样式类名 */
50
45
  pendingClass?: string
51
46
  /** 已完成块的样式类名 */
@@ -58,117 +53,76 @@
58
53
 
59
54
  let {
60
55
  blocks = [],
56
+ isDisplayComplete = false,
61
57
  components = {},
62
58
  customContainers = {},
63
59
  customCodeBlocks = {},
60
+ codeBlockConfigs = {},
64
61
  pendingClass = 'incremark-pending',
65
62
  completedClass = 'incremark-completed',
66
63
  showBlockStatus = false,
67
64
  incremark
68
65
  }: Props = $props()
69
66
 
70
- // context 获取 footnoteReferenceOrder(如果有的话)
71
- // 使用 $derived 来确保响应式
72
- const footnoteReferenceOrder = $derived.by(() => {
73
- try {
74
- const context = getDefinitionsContext()
75
- return context.footnoteReferenceOrder
76
- } catch {
77
- // 如果没有 context,返回 null
78
- return null
79
- }
80
- })
81
-
82
- // 计算 isFinalized(当不使用 incremark 时)
83
- const actualIsFinalized = $derived.by(() => {
84
- if (incremark) {
85
- // 如果提供了 incremark,在模板中直接使用 $incremark.isFinalized
86
- return false
87
- }
88
- // 如果手动传入 blocks,自动判断是否所有 block 都是 completed
89
- const blocksArray = Array.isArray(blocks) ? blocks : []
90
- return blocksArray.length > 0 && blocksArray.every(b => b.status === 'completed')
91
- })
92
-
93
- // 默认组件映射
94
- const defaultComponents: ComponentMap = {
95
- paragraph: IncremarkParagraph,
96
- heading: IncremarkHeading,
97
- code: IncremarkCode,
98
- list: IncremarkList,
99
- table: IncremarkTable,
100
- blockquote: IncremarkBlockquote,
101
- thematicBreak: IncremarkThematicBreak,
102
- math: IncremarkMath,
103
- inlineMath: IncremarkMath,
104
- htmlElement: IncremarkHtmlElement
105
- }
106
-
107
- // 合并用户组件和默认组件
108
- const mergedComponents = $derived({
109
- ...defaultComponents,
110
- ...components
111
- })
112
-
113
- // 处理 blocks(可能是 store 或数组)
114
- const blocksArray = $derived.by(() => {
115
- if (incremark) {
116
- // 如果提供了 incremark,在模板中直接使用 incremark.blocks(store)
117
- return []
118
- }
119
- return Array.isArray(blocks) ? blocks : []
120
- })
67
+ const context = getDefinitionsContext();
68
+ const footnoteReferenceOrder = $derived(context?.footnoteReferenceOrder ?? []);
121
69
 
122
- // 提取 incremark stores(如果存在)
123
- const incremarkBlocks = $derived.by(() => incremark?.blocks)
124
- const incremarkIsFinalized = $derived.by(() => incremark?.isFinalized)
70
+ // 当使用 incremark 时,从 incremark 对象中提取 blocks 和 isDisplayComplete
71
+ const incremarkBlocks = $derived(
72
+ incremark ? (incremark as any).blocks : []
73
+ );
74
+ const incremarkIsDisplayComplete = $derived(
75
+ incremark ? (incremark as any).isDisplayComplete : false
76
+ );
125
77
  </script>
126
78
 
127
79
  <div class="incremark">
128
80
  <!-- 主要内容块 -->
129
- {#if incremark && incremarkBlocks}
81
+ {#if incremark}
130
82
  <!-- 使用 incremark 的 blocks store -->
131
- {#each ($incremarkBlocks || []) as block (block.stableId)}
132
- {#if block.node.type !== 'definition' && block.node.type !== 'footnoteDefinition'}
83
+ {#each $incremarkBlocks as block (block.stableId)}
84
+ {#if (block as ParsedBlock).node.type !== 'definition' && (block as ParsedBlock).node.type !== 'footnoteDefinition'}
133
85
  <div
134
- class="incremark-block {block.status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {(block as BlockWithStableId).isLastPending ? 'incremark-last-pending' : ''}"
86
+ class="incremark-block {(block as ParsedBlock).status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {(block as BlockWithStableId).isLastPending ? 'incremark-last-pending' : ''}"
135
87
  >
136
88
  <!-- 使用 IncremarkRenderer,传递 customContainers 和 customCodeBlocks -->
137
- <IncremarkRenderer
138
- node={block.node}
89
+ <IncremarkRenderer
90
+ node={(block as ParsedBlock).node}
139
91
  customContainers={customContainers}
140
92
  customCodeBlocks={customCodeBlocks}
141
- blockStatus={block.status}
93
+ codeBlockConfigs={codeBlockConfigs}
94
+ blockStatus={(block as ParsedBlock).status}
142
95
  />
143
96
  </div>
144
97
  {/if}
145
98
  {/each}
146
99
  {:else}
147
100
  <!-- 使用传入的 blocks 数组 -->
148
- {#each blocksArray as block (block.stableId)}
149
- {#if block.node.type !== 'definition' && block.node.type !== 'footnoteDefinition'}
101
+ {#each (Array.isArray(blocks) ? blocks : []) as block (block.stableId)}
102
+ {#if (block as ParsedBlock).node.type !== 'definition' && (block as ParsedBlock).node.type !== 'footnoteDefinition'}
150
103
  <div
151
- class="incremark-block {block.status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {block.isLastPending ? 'incremark-last-pending' : ''}"
104
+ class="incremark-block {(block as ParsedBlock).status === 'completed' ? completedClass : pendingClass} {showBlockStatus ? 'incremark-show-status' : ''} {block.isLastPending ? 'incremark-last-pending' : ''}"
152
105
  >
153
106
  <!-- 使用 IncremarkRenderer,传递 customContainers 和 customCodeBlocks -->
154
- <IncremarkRenderer
155
- node={block.node}
107
+ <IncremarkRenderer
108
+ node={(block as ParsedBlock).node}
156
109
  customContainers={customContainers}
157
110
  customCodeBlocks={customCodeBlocks}
158
- blockStatus={block.status}
111
+ codeBlockConfigs={codeBlockConfigs}
112
+ blockStatus={(block as ParsedBlock).status}
159
113
  />
160
114
  </div>
161
115
  {/if}
162
116
  {/each}
163
117
  {/if}
164
118
 
165
- <!-- 脚注列表(仅在 finalize 后显示) -->
166
- {#if incremark && incremarkIsFinalized && footnoteReferenceOrder && $incremarkIsFinalized}
119
+ <!-- 脚注列表(仅在内容完全显示后显示) -->
120
+ {#if incremark && $incremarkIsDisplayComplete && $footnoteReferenceOrder}
167
121
  {@const footnoteOrder = $footnoteReferenceOrder ?? []}
168
122
  {#if footnoteOrder.length > 0}
169
123
  <IncremarkFootnotes />
170
124
  {/if}
171
- {:else if !incremark && actualIsFinalized && footnoteReferenceOrder}
125
+ {:else if !incremark && isDisplayComplete && $footnoteReferenceOrder}
172
126
  {@const footnoteOrder = $footnoteReferenceOrder ?? []}
173
127
  {#if footnoteOrder.length > 0}
174
128
  <IncremarkFootnotes />
@@ -8,12 +8,19 @@ import type { ComponentMap, BlockWithStableId } from './types';
8
8
  interface Props {
9
9
  /** 要渲染的块列表(来自 useIncremark 的 blocks) */
10
10
  blocks?: BlockWithStableId[] | Readable<BlockWithStableId[]>;
11
+ /** 内容是否完全显示完成(用于控制脚注等需要在内容完全显示后才出现的元素)
12
+ * 如果传入了 incremark,则会自动使用 incremark.isDisplayComplete,此 prop 被忽略 */
13
+ isDisplayComplete?: boolean;
11
14
  /** 自定义组件映射,key 为节点类型 */
12
15
  components?: ComponentMap;
13
16
  /** 自定义容器组件映射,key 为容器名称(如 'warning', 'info') */
14
17
  customContainers?: Record<string, Component<any>>;
15
18
  /** 自定义代码块组件映射,key 为代码语言名称(如 'echart', 'mermaid') */
16
19
  customCodeBlocks?: Record<string, Component<any>>;
20
+ /** 代码块配置映射,key 为代码语言名称 */
21
+ codeBlockConfigs?: Record<string, {
22
+ takeOver?: boolean;
23
+ }>;
17
24
  /** 待处理块的样式类名 */
18
25
  pendingClass?: string;
19
26
  /** 已完成块的样式类名 */
@@ -1 +1 @@
1
- {"version":3,"file":"Incremark.svelte.d.ts","sourceRoot":"","sources":["../../src/components/Incremark.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAK5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAgB5D;;GAEG;AACH,UAAU,KAAK;IACb,wCAAwC;IACxC,MAAM,CAAC,EAAE,iBAAiB,EAAE,GAAG,QAAQ,CAAC,iBAAiB,EAAE,CAAC,CAAA;IAC5D,wBAAwB;IACxB,UAAU,CAAC,EAAE,YAAY,CAAA;IACzB,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,gBAAgB;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gBAAgB;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,sCAAsC;IACtC,SAAS,CAAC,EAAE,kBAAkB,CAAA;CAC/B;AAgJH,QAAA,MAAM,SAAS,0BAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
1
+ {"version":3,"file":"Incremark.svelte.d.ts","sourceRoot":"","sources":["../../src/components/Incremark.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA;AACvC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAA;AAK5C,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAChE,OAAO,KAAK,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAM5D;;GAEG;AACH,UAAU,KAAK;IACb,wCAAwC;IACxC,MAAM,CAAC,EAAE,iBAAiB,EAAE,GAAG,QAAQ,CAAC,iBAAiB,EAAE,CAAC,CAAA;IAC5D;uEACmE;IACnE,iBAAiB,CAAC,EAAE,OAAO,CAAA;IAC3B,wBAAwB;IACxB,UAAU,CAAC,EAAE,YAAY,CAAA;IACzB,+CAA+C;IAC/C,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,oDAAoD;IACpD,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAA;IACjD,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IACzD,gBAAgB;IAChB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,gBAAgB;IAChB,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,gBAAgB;IAChB,eAAe,CAAC,EAAE,OAAO,CAAA;IACzB,sCAAsC;IACtC,SAAS,CAAC,EAAE,kBAAkB,CAAA;CAC/B;AA2FH,QAAA,MAAM,SAAS,0BAAwC,CAAC;AACxD,KAAK,SAAS,GAAG,UAAU,CAAC,OAAO,SAAS,CAAC,CAAC;AAC9C,eAAe,SAAS,CAAC"}
@@ -1,11 +1,11 @@
1
1
  <!--
2
2
  @file IncremarkBlockquote.svelte - 引用组件
3
- @description 渲染 Markdown 引用块
3
+ @description 渲染 Markdown 引用块,支持所有块级内容
4
4
  -->
5
5
 
6
6
  <script lang="ts">
7
7
  import type { Blockquote } from 'mdast'
8
- import IncremarkParagraph from './IncremarkParagraph.svelte'
8
+ import IncremarkRenderer from './IncremarkRenderer.svelte'
9
9
 
10
10
  /**
11
11
  * 组件 Props
@@ -20,11 +20,7 @@
20
20
 
21
21
  <blockquote class="incremark-blockquote">
22
22
  {#each node.children as child, index (index)}
23
- {#if child.type === 'paragraph'}
24
- <IncremarkParagraph node={child} />
25
- {:else}
26
- <div class="unknown-child">{child.type}</div>
27
- {/if}
23
+ <IncremarkRenderer node={child} />
28
24
  {/each}
29
25
  </blockquote>
30
26
 
@@ -1 +1 @@
1
- {"version":3,"file":"IncremarkBlockquote.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkBlockquote.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAIrC;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,UAAU,CAAA;CACjB;AAwBH,QAAA,MAAM,mBAAmB,2CAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
1
+ {"version":3,"file":"IncremarkBlockquote.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkBlockquote.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAIrC;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,UAAU,CAAA;CACjB;AAoBH,QAAA,MAAM,mBAAmB,2CAAwC,CAAC;AAClE,KAAK,mBAAmB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAClE,eAAe,mBAAmB,CAAC"}
@@ -15,12 +15,16 @@
15
15
  node: Code
16
16
  /** Shiki 主题,默认 github-dark */
17
17
  theme?: string
18
+ /** 默认回退主题(当指定主题加载失败时使用),默认 github-dark */
19
+ fallbackTheme?: string
18
20
  /** 是否禁用代码高亮 */
19
21
  disableHighlight?: boolean
20
22
  /** Mermaid 渲染延迟(毫秒),用于流式输入时防抖 */
21
23
  mermaidDelay?: number
22
24
  /** 自定义代码块组件映射,key 为代码语言名称 */
23
25
  customCodeBlocks?: Record<string, any>
26
+ /** 代码块配置映射,key 为代码语言名称 */
27
+ codeBlockConfigs?: Record<string, { takeOver?: boolean }>
24
28
  /** 块状态,用于判断是否使用自定义组件 */
25
29
  blockStatus?: 'pending' | 'stable' | 'completed'
26
30
  }
@@ -28,9 +32,11 @@
28
32
  let {
29
33
  node,
30
34
  theme = 'github-dark',
35
+ fallbackTheme = 'github-dark',
31
36
  disableHighlight = false,
32
37
  mermaidDelay = 500,
33
38
  customCodeBlocks,
39
+ codeBlockConfigs,
34
40
  blockStatus = 'completed'
35
41
  }: Props = $props()
36
42
 
@@ -63,15 +69,24 @@
63
69
 
64
70
  // 检查是否有自定义代码块组件
65
71
  const CustomCodeBlock = $derived.by(() => {
66
- // 如果代码块还在 pending 状态,不使用自定义组件
67
- if (blockStatus === 'pending') {
72
+ const component = customCodeBlocks?.[language]
73
+ if (!component) return null
74
+
75
+ // 检查该语言的配置
76
+ const config = codeBlockConfigs?.[language]
77
+
78
+ // 如果配置了 takeOver 为 true,则从一开始就使用
79
+ if (config?.takeOver) {
80
+ return component
81
+ }
82
+
83
+ // 否则,默认行为:只在 completed 状态使用
84
+ if (blockStatus !== 'completed') {
68
85
  return null
69
86
  }
70
- return customCodeBlocks?.[language] || null
71
- })
72
87
 
73
- // 是否使用自定义组件
74
- const useCustomComponent = $derived(!!CustomCodeBlock)
88
+ return component
89
+ })
75
90
 
76
91
  /**
77
92
  * 切换 Mermaid 视图模式
@@ -188,7 +203,7 @@
188
203
 
189
204
  const html = highlighter.codeToHtml(code, {
190
205
  lang: loadedLanguages.has(lang) ? lang : 'text',
191
- theme: loadedThemes.has(theme) ? theme : 'github-dark'
206
+ theme: loadedThemes.has(theme) ? theme : fallbackTheme
192
207
  })
193
208
  highlightedHtml = html
194
209
  } catch (e) {
@@ -229,13 +244,9 @@
229
244
  </script>
230
245
 
231
246
  <!-- 自定义代码块组件 -->
232
- {#if useCustomComponent && CustomCodeBlock}
233
- <svelte:component
234
- this={CustomCodeBlock}
235
- codeStr={code}
236
- lang={language}
237
- />
238
- <!-- Mermaid 图表 -->
247
+ {#if CustomCodeBlock}
248
+ {@const Component = CustomCodeBlock}
249
+ <Component codeStr={code} lang={language} completed={blockStatus === 'completed'} takeOver={codeBlockConfigs?.[language]?.takeOver} />
239
250
  {:else if isMermaid}
240
251
  <div class="incremark-mermaid">
241
252
  <div class="mermaid-header">
@@ -7,12 +7,18 @@ interface Props {
7
7
  node: Code;
8
8
  /** Shiki 主题,默认 github-dark */
9
9
  theme?: string;
10
+ /** 默认回退主题(当指定主题加载失败时使用),默认 github-dark */
11
+ fallbackTheme?: string;
10
12
  /** 是否禁用代码高亮 */
11
13
  disableHighlight?: boolean;
12
14
  /** Mermaid 渲染延迟(毫秒),用于流式输入时防抖 */
13
15
  mermaidDelay?: number;
14
16
  /** 自定义代码块组件映射,key 为代码语言名称 */
15
17
  customCodeBlocks?: Record<string, any>;
18
+ /** 代码块配置映射,key 为代码语言名称 */
19
+ codeBlockConfigs?: Record<string, {
20
+ takeOver?: boolean;
21
+ }>;
16
22
  /** 块状态,用于判断是否使用自定义组件 */
17
23
  blockStatus?: 'pending' | 'stable' | 'completed';
18
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"IncremarkCode.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkCode.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAI/B;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,IAAI,CAAA;IACV,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,eAAe;IACf,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,iCAAiC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6BAA6B;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACtC,wBAAwB;IACxB,WAAW,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAA;CACjD;AAmRH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"IncremarkCode.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkCode.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,OAAO,CAAA;AAI/B;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,IAAI,CAAA;IACV,8BAA8B;IAC9B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,0CAA0C;IAC1C,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,eAAe;IACf,gBAAgB,CAAC,EAAE,OAAO,CAAA;IAC1B,iCAAiC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,6BAA6B;IAC7B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACtC,0BAA0B;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IACzD,wBAAwB;IACxB,WAAW,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAA;CACjD;AA8RH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -1,12 +1,12 @@
1
1
  <!--
2
2
  @file IncremarkList.svelte - 列表组件
3
- @description 渲染 Markdown 列表(有序列表和无序列表),支持任务列表和嵌套列表
3
+ @description 渲染 Markdown 列表(有序列表和无序列表),支持任务列表和所有块级内容
4
4
  -->
5
5
 
6
6
  <script lang="ts">
7
- import type { List, ListItem, PhrasingContent, BlockContent } from 'mdast'
7
+ import type { List, ListItem, RootContent } from 'mdast'
8
8
  import IncremarkInline from './IncremarkInline.svelte'
9
- import IncremarkList from './IncremarkList.svelte';
9
+ import IncremarkRenderer from './IncremarkRenderer.svelte'
10
10
 
11
11
  /**
12
12
  * 组件 Props
@@ -36,10 +36,10 @@
36
36
  * @param item - 列表项节点
37
37
  * @returns 行内内容数组
38
38
  */
39
- function getItemInlineContent(item: ListItem): PhrasingContent[] {
39
+ function getItemInlineContent(item: ListItem) {
40
40
  const firstChild = item.children[0]
41
41
  if (firstChild?.type === 'paragraph') {
42
- return firstChild.children as PhrasingContent[]
42
+ return firstChild.children
43
43
  }
44
44
  return []
45
45
  }
@@ -51,14 +51,14 @@
51
51
  * @param item - 列表项节点
52
52
  * @returns 块级子节点数组
53
53
  */
54
- function getItemBlockChildren(item: ListItem): BlockContent[] {
54
+ function getItemBlockChildren(item: ListItem): RootContent[] {
55
55
  return item.children.filter((child, index) => {
56
56
  // 第一个 paragraph 已经被处理为内联内容
57
57
  if (index === 0 && child.type === 'paragraph') {
58
58
  return false
59
59
  }
60
60
  return true
61
- }) as BlockContent[]
61
+ })
62
62
  }
63
63
 
64
64
  /**
@@ -96,12 +96,9 @@
96
96
  </label>
97
97
  {:else}
98
98
  <IncremarkInline nodes={getItemInlineContent(item)} />
99
- <!-- 递归渲染嵌套列表和其他块级内容 -->
99
+ <!-- 递归渲染所有块级内容(嵌套列表、heading、blockquote、code、table 等) -->
100
100
  {#each getItemBlockChildren(item) as child, childIndex (childIndex)}
101
- {#if child.type === 'list'}
102
- <IncremarkList node={child} />
103
- {/if}
104
- <!-- 其他块级内容可以在这里扩展 -->
101
+ <IncremarkRenderer node={child} />
105
102
  {/each}
106
103
  {/if}
107
104
  </li>
@@ -1,5 +1,4 @@
1
1
  import type { List } from 'mdast';
2
- import IncremarkList from './IncremarkList.svelte';
3
2
  /**
4
3
  * 组件 Props
5
4
  */
@@ -1 +1 @@
1
- {"version":3,"file":"IncremarkList.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkList.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAA2C,MAAM,OAAO,CAAA;AAE1E,OAAO,aAAa,MAAM,wBAAwB,CAAC;AAGjD;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,IAAI,CAAA;CACX;AA4FH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
1
+ {"version":3,"file":"IncremarkList.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkList.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAyB,MAAM,OAAO,CAAA;AAKtD;;GAEG;AACH,UAAU,KAAK;IACb,WAAW;IACX,IAAI,EAAE,IAAI,CAAA;CACX;AAyFH,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
@@ -7,7 +7,6 @@
7
7
  import type { RootContent, HTML } from 'mdast'
8
8
  import IncremarkHeading from './IncremarkHeading.svelte'
9
9
  import IncremarkParagraph from './IncremarkParagraph.svelte'
10
- import IncremarkCode from './IncremarkCode.svelte'
11
10
  import IncremarkList from './IncremarkList.svelte'
12
11
  import IncremarkTable from './IncremarkTable.svelte'
13
12
  import IncremarkBlockquote from './IncremarkBlockquote.svelte'
@@ -15,6 +14,7 @@
15
14
  import IncremarkMath from './IncremarkMath.svelte'
16
15
  import IncremarkHtmlElement from './IncremarkHtmlElement.svelte'
17
16
  import IncremarkDefault from './IncremarkDefault.svelte'
17
+ import IncremarkCode from './IncremarkCode.svelte'
18
18
  import IncremarkContainer, { type ContainerNode } from './IncremarkContainer.svelte'
19
19
 
20
20
  /**
@@ -25,23 +25,24 @@
25
25
  node: RootContent | ContainerNode
26
26
  customContainers?: Record<string, any>
27
27
  customCodeBlocks?: Record<string, any>
28
+ codeBlockConfigs?: Record<string, { takeOver?: boolean }>
28
29
  blockStatus?: 'pending' | 'stable' | 'completed'
29
30
  }
30
31
 
31
- let {
32
- node,
32
+ let {
33
+ node,
33
34
  customContainers,
34
35
  customCodeBlocks,
36
+ codeBlockConfigs,
35
37
  blockStatus
36
38
  }: Props = $props()
37
39
 
38
40
  /**
39
- * 组件映射
41
+ * 默认组件映射
40
42
  */
41
- const componentMap: Record<string, any> = {
43
+ const defaultComponentMap: Record<string, any> = {
42
44
  heading: IncremarkHeading,
43
45
  paragraph: IncremarkParagraph,
44
- code: IncremarkCode,
45
46
  list: IncremarkList,
46
47
  table: IncremarkTable,
47
48
  blockquote: IncremarkBlockquote,
@@ -49,13 +50,16 @@
49
50
  math: IncremarkMath,
50
51
  inlineMath: IncremarkMath,
51
52
  htmlElement: IncremarkHtmlElement,
53
+ containerDirective: IncremarkContainer,
54
+ leafDirective: IncremarkContainer,
55
+ textDirective: IncremarkContainer,
52
56
  }
53
57
 
54
58
  /**
55
59
  * 获取组件
56
60
  */
57
61
  function getComponent(type: string): any {
58
- return componentMap[type] || IncremarkDefault
62
+ return defaultComponentMap[type] || IncremarkDefault
59
63
  }
60
64
 
61
65
  /**
@@ -84,16 +88,14 @@
84
88
  node={node}
85
89
  customContainers={customContainers}
86
90
  />
87
- <!-- 代码节点:特殊处理,传递 customCodeBlocks 和 blockStatus -->
91
+ <!-- 代码节点:特殊处理,传递 customCodeBlocks、codeBlockConfigs 和 blockStatus -->
88
92
  {:else if node.type === 'code'}
89
- {@const Component = getComponent('code')}
90
- {#if Component}
91
- <Component
92
- node={node}
93
- customCodeBlocks={customCodeBlocks}
94
- blockStatus={blockStatus}
95
- />
96
- {/if}
93
+ <IncremarkCode
94
+ node={node}
95
+ customCodeBlocks={customCodeBlocks}
96
+ codeBlockConfigs={codeBlockConfigs}
97
+ blockStatus={blockStatus}
98
+ />
97
99
  {:else}
98
100
  <!-- 其他节点:使用对应组件 -->
99
101
  {@const Component = getComponent(node.type)}
@@ -8,6 +8,9 @@ interface Props {
8
8
  node: RootContent | ContainerNode;
9
9
  customContainers?: Record<string, any>;
10
10
  customCodeBlocks?: Record<string, any>;
11
+ codeBlockConfigs?: Record<string, {
12
+ takeOver?: boolean;
13
+ }>;
11
14
  blockStatus?: 'pending' | 'stable' | 'completed';
12
15
  }
13
16
  declare const IncremarkRenderer: import("svelte").Component<Props, {}, "">;
@@ -1 +1 @@
1
- {"version":3,"file":"IncremarkRenderer.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkRenderer.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAQ,MAAM,OAAO,CAAA;AAW9C,OAA2B,EAAE,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAGnF;;GAEG;AACH,UAAU,KAAK;IACb,aAAa;IACb,IAAI,EAAE,WAAW,GAAG,aAAa,CAAA;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACtC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACtC,WAAW,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAA;CACjD;AAwFH,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"IncremarkRenderer.svelte.d.ts","sourceRoot":"","sources":["../../src/components/IncremarkRenderer.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAQ,MAAM,OAAO,CAAA;AAW9C,OAA2B,EAAE,KAAK,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAGnF;;GAEG;AACH,UAAU,KAAK;IACb,aAAa;IACb,IAAI,EAAE,WAAW,GAAG,aAAa,CAAA;IACjC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACtC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IACtC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAA;IACzD,WAAW,CAAC,EAAE,SAAS,GAAG,QAAQ,GAAG,WAAW,CAAA;CACjD;AAwFH,QAAA,MAAM,iBAAiB,2CAAwC,CAAC;AAChE,KAAK,iBAAiB,GAAG,UAAU,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAC9D,eAAe,iBAAiB,CAAC"}
@@ -68,11 +68,19 @@ export interface UseIncremarkReturn {
68
68
  /** 用于渲染的 blocks(根据打字机设置自动处理) */
69
69
  blocks: Readable<Array<ParsedBlock & {
70
70
  stableId: string;
71
+ isLastPending?: boolean;
71
72
  }>>;
72
73
  /** 是否正在加载 */
73
74
  isLoading: Writable<boolean>;
74
75
  /** 是否已完成(finalize) */
75
76
  isFinalized: Writable<boolean>;
77
+ /**
78
+ * 内容是否完全显示完成
79
+ * - 无打字机:等于 isFinalized
80
+ * - 有打字机:isFinalized + 动画播放完成
81
+ * 适用于控制 footnote 等需要在内容完全显示后才出现的元素
82
+ */
83
+ isDisplayComplete: Readable<boolean>;
76
84
  /** 脚注引用的出现顺序 */
77
85
  footnoteReferenceOrder: Writable<string[]>;
78
86
  /** 追加内容 */
@@ -1 +1 @@
1
- {"version":3,"file":"useIncremark.d.ts","sourceRoot":"","sources":["../../src/stores/useIncremark.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useIncremark.d.ts","sourceRoot":"","sources":["../../src/stores/useIncremark.ts"],"names":[],"mappings":"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"}
@@ -60,12 +60,46 @@ export function useIncremark(options = {}) {
60
60
  const markdown = writable('');
61
61
  const isFinalized = writable(false);
62
62
  const footnoteReferenceOrder = writable([]);
63
+ /**
64
+ * 处理解析器更新结果(统一 append 和 finalize 的更新逻辑)
65
+ */
66
+ function handleUpdate(update, isFinalize) {
67
+ markdown.set(parser.getBuffer());
68
+ if (update.completed.length > 0) {
69
+ completedBlocks.update((blocks) => [
70
+ ...blocks,
71
+ ...update.completed
72
+ ]);
73
+ }
74
+ pendingBlocks.set(update.pending);
75
+ if (isFinalize) {
76
+ isLoading.set(false);
77
+ isFinalized.set(true);
78
+ }
79
+ else {
80
+ isLoading.set(true);
81
+ }
82
+ // 更新脚注引用顺序
83
+ footnoteReferenceOrder.set(update.footnoteReferenceOrder);
84
+ setFootnoteReferenceOrder(update.footnoteReferenceOrder);
85
+ }
63
86
  // 使用 useTypewriter store 管理打字机效果
64
- const { blocks, typewriter, transformer } = useTypewriter({
87
+ const { blocks, typewriter, transformer, isAnimationComplete } = useTypewriter({
65
88
  typewriter: options.typewriter,
66
89
  completedBlocks,
67
90
  pendingBlocks
68
91
  });
92
+ // 内容是否完全显示完成
93
+ // 如果没有配置打字机或未启用打字机:解析完成即显示完成
94
+ // 如果启用打字机:解析完成 + 动画完成
95
+ const isDisplayComplete = derived([isFinalized, isAnimationComplete, typewriter.enabled], ([$isFinalized, $isAnimationComplete, $typewriterEnabled]) => {
96
+ // 没有配置打字机,或者打字机未启用:只需判断是否 finalized
97
+ if (!options.typewriter || !$typewriterEnabled) {
98
+ return $isFinalized;
99
+ }
100
+ // 启用了打字机:需要 finalize + 动画完成
101
+ return $isFinalized && $isAnimationComplete;
102
+ });
69
103
  // AST
70
104
  const ast = derived([completedBlocks, pendingBlocks], ([$completedBlocks, $pendingBlocks]) => ({
71
105
  type: 'root',
@@ -81,19 +115,8 @@ export function useIncremark(options = {}) {
81
115
  * @returns 增量更新结果
82
116
  */
83
117
  function append(chunk) {
84
- isLoading.set(true);
85
118
  const update = parser.append(chunk);
86
- markdown.set(parser.getBuffer());
87
- if (update.completed.length > 0) {
88
- completedBlocks.update((blocks) => [
89
- ...blocks,
90
- ...update.completed
91
- ]);
92
- }
93
- pendingBlocks.set(update.pending);
94
- // 更新脚注引用顺序
95
- footnoteReferenceOrder.set(update.footnoteReferenceOrder);
96
- setFootnoteReferenceOrder(update.footnoteReferenceOrder);
119
+ handleUpdate(update, false);
97
120
  return update;
98
121
  }
99
122
  /**
@@ -103,19 +126,7 @@ export function useIncremark(options = {}) {
103
126
  */
104
127
  function finalize() {
105
128
  const update = parser.finalize();
106
- markdown.set(parser.getBuffer());
107
- if (update.completed.length > 0) {
108
- completedBlocks.update((blocks) => [
109
- ...blocks,
110
- ...update.completed
111
- ]);
112
- }
113
- pendingBlocks.set([]);
114
- isLoading.set(false);
115
- isFinalized.set(true);
116
- // 更新脚注引用顺序
117
- footnoteReferenceOrder.set(update.footnoteReferenceOrder);
118
- setFootnoteReferenceOrder(update.footnoteReferenceOrder);
129
+ handleUpdate(update, true);
119
130
  return update;
120
131
  }
121
132
  /**
@@ -165,6 +176,7 @@ export function useIncremark(options = {}) {
165
176
  blocks,
166
177
  isLoading,
167
178
  isFinalized,
179
+ isDisplayComplete,
168
180
  footnoteReferenceOrder,
169
181
  append,
170
182
  finalize,
@@ -23,11 +23,14 @@ export interface UseTypewriterReturn {
23
23
  /** 用于渲染的 blocks(经过打字机处理或原始blocks) */
24
24
  blocks: Readable<Array<ParsedBlock & {
25
25
  stableId: string;
26
+ isLastPending?: boolean;
26
27
  }>>;
27
28
  /** 打字机控制对象 */
28
29
  typewriter: TypewriterControls;
29
30
  /** transformer 实例 */
30
31
  transformer: BlockTransformer<RootContent> | null;
32
+ /** 所有动画是否已完成(队列为空且没有正在处理的 block) */
33
+ isAnimationComplete: Readable<boolean>;
31
34
  }
32
35
  /**
33
36
  * useTypewriter Store
@@ -1 +1 @@
1
- {"version":3,"file":"useTypewriter.d.ts","sourceRoot":"","sources":["../../src/stores/useTypewriter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAqB,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC9E,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,WAAW,EAGhB,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAG3E;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,YAAY;IACZ,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B,oBAAoB;IACpB,eAAe,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;IACxC,oBAAoB;IACpB,aAAa,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,qCAAqC;IACrC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAA;IAC3D,cAAc;IACd,UAAU,EAAE,kBAAkB,CAAA;IAC9B,qBAAqB;IACrB,WAAW,EAAE,gBAAgB,CAAC,WAAW,CAAC,GAAG,IAAI,CAAA;CAClD;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,mBAAmB,CAkKhF"}
1
+ {"version":3,"file":"useTypewriter.d.ts","sourceRoot":"","sources":["../../src/stores/useTypewriter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAqB,KAAK,QAAQ,EAAE,KAAK,QAAQ,EAAE,MAAM,cAAc,CAAA;AAC9E,OAAO,EAGL,KAAK,WAAW,EAChB,KAAK,WAAW,EAGhB,KAAK,gBAAgB,EACtB,MAAM,iBAAiB,CAAA;AACxB,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAG3E;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,YAAY;IACZ,UAAU,CAAC,EAAE,iBAAiB,CAAA;IAC9B,oBAAoB;IACpB,eAAe,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;IACxC,oBAAoB;IACpB,aAAa,EAAE,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAA;CACvC;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,qCAAqC;IACrC,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,WAAW,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,aAAa,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC,CAAA;IACpF,cAAc;IACd,UAAU,EAAE,kBAAkB,CAAA;IAC9B,qBAAqB;IACrB,WAAW,EAAE,gBAAgB,CAAC,WAAW,CAAC,GAAG,IAAI,CAAA;IACjD,oCAAoC;IACpC,mBAAmB,EAAE,QAAQ,CAAC,OAAO,CAAC,CAAA;CACvC;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,mBAAmB,CA8KhF"}
@@ -23,6 +23,7 @@ export function useTypewriter(options) {
23
23
  const isTypewriterPaused = writable(false);
24
24
  const typewriterEffect = writable(typewriterConfig?.effect ?? 'none');
25
25
  const typewriterCursor = writable(typewriterConfig?.cursor ?? '|');
26
+ const isAnimationComplete = writable(true); // 初始为 true(没有动画时视为完成)
26
27
  // 创建 transformer(如果有 typewriter 配置)
27
28
  let transformer = null;
28
29
  if (typewriterConfig) {
@@ -37,6 +38,14 @@ export function useTypewriter(options) {
37
38
  displayBlocks.set(blocks);
38
39
  isTypewriterProcessing.set(transformer?.isProcessing() ?? false);
39
40
  isTypewriterPaused.set(transformer?.isPausedState() ?? false);
41
+ // 有 blocks 正在处理时,动画未完成
42
+ if (transformer?.isProcessing()) {
43
+ isAnimationComplete.set(false);
44
+ }
45
+ },
46
+ onAllComplete: () => {
47
+ // 所有动画完成
48
+ isAnimationComplete.set(true);
40
49
  }
41
50
  });
42
51
  }
@@ -73,9 +82,11 @@ export function useTypewriter(options) {
73
82
  result.push({ ...block, stableId: block.id });
74
83
  }
75
84
  for (let i = 0; i < $pendingBlocks.length; i++) {
85
+ const isLastPending = i === $pendingBlocks.length - 1;
76
86
  result.push({
77
87
  ...$pendingBlocks[i],
78
- stableId: `pending-${i}`
88
+ stableId: `pending-${i}`,
89
+ isLastPending
79
90
  });
80
91
  }
81
92
  return result;
@@ -148,6 +159,7 @@ export function useTypewriter(options) {
148
159
  return {
149
160
  blocks,
150
161
  typewriter: typewriterControls,
151
- transformer
162
+ transformer,
163
+ isAnimationComplete: derived(isAnimationComplete, ($v) => $v)
152
164
  };
153
165
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@incremark/svelte",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Incremark Svelte 5 集成",
5
5
  "type": "module",
6
6
  "svelte": "./dist/index.js",
@@ -24,10 +24,10 @@
24
24
  ],
25
25
  "dependencies": {
26
26
  "shiki": "^3.20.0",
27
- "@incremark/core": "0.2.4",
28
- "@incremark/devtools": "0.2.4",
29
- "@incremark/shared": "0.2.4",
30
- "@incremark/theme": "0.2.4"
27
+ "@incremark/devtools": "0.2.6",
28
+ "@incremark/core": "0.2.6",
29
+ "@incremark/theme": "0.2.6",
30
+ "@incremark/shared": "0.2.6"
31
31
  },
32
32
  "peerDependencies": {
33
33
  "svelte": "^5.0.0",