@incremark/vue 0.0.4 → 0.1.0
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 +180 -1
- package/dist/index.css +17 -0
- package/dist/index.css.map +1 -1
- package/dist/index.js +460 -48
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/components/AutoScrollContainer.vue +164 -0
- package/src/components/Incremark.vue +3 -1
- package/src/components/IncremarkInline.vue +83 -10
- package/src/components/index.ts +3 -0
- package/src/composables/index.ts +4 -1
- package/src/composables/useBlockTransformer.ts +141 -0
- package/src/composables/useIncremark.ts +239 -27
- package/src/index.ts +37 -4
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { ref, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
|
3
|
+
|
|
4
|
+
const props = withDefaults(defineProps<{
|
|
5
|
+
/** 是否启用自动滚动 */
|
|
6
|
+
enabled?: boolean
|
|
7
|
+
/** 触发自动滚动的底部阈值(像素) */
|
|
8
|
+
threshold?: number
|
|
9
|
+
/** 滚动行为 */
|
|
10
|
+
behavior?: ScrollBehavior
|
|
11
|
+
}>(), {
|
|
12
|
+
enabled: true,
|
|
13
|
+
threshold: 50,
|
|
14
|
+
behavior: 'instant'
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const containerRef = ref<HTMLDivElement | null>(null)
|
|
18
|
+
const isUserScrolledUp = ref(false)
|
|
19
|
+
|
|
20
|
+
// 记录上一次滚动位置,用于判断滚动方向
|
|
21
|
+
let lastScrollTop = 0
|
|
22
|
+
let lastScrollHeight = 0
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 检查是否在底部附近
|
|
26
|
+
*/
|
|
27
|
+
function isNearBottom(): boolean {
|
|
28
|
+
const container = containerRef.value
|
|
29
|
+
if (!container) return true
|
|
30
|
+
|
|
31
|
+
const { scrollTop, scrollHeight, clientHeight } = container
|
|
32
|
+
return scrollHeight - scrollTop - clientHeight <= props.threshold
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* 滚动到底部
|
|
37
|
+
*/
|
|
38
|
+
function scrollToBottom(force = false): void {
|
|
39
|
+
const container = containerRef.value
|
|
40
|
+
if (!container) return
|
|
41
|
+
|
|
42
|
+
// 如果用户手动向上滚动了,且不是强制滚动,则不自动滚动
|
|
43
|
+
if (isUserScrolledUp.value && !force) return
|
|
44
|
+
|
|
45
|
+
container.scrollTo({
|
|
46
|
+
top: container.scrollHeight,
|
|
47
|
+
behavior: props.behavior
|
|
48
|
+
})
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* 检查是否有滚动条
|
|
53
|
+
*/
|
|
54
|
+
function hasScrollbar(): boolean {
|
|
55
|
+
const container = containerRef.value
|
|
56
|
+
if (!container) return false
|
|
57
|
+
return container.scrollHeight > container.clientHeight
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 处理滚动事件
|
|
62
|
+
*/
|
|
63
|
+
function handleScroll(): void {
|
|
64
|
+
const container = containerRef.value
|
|
65
|
+
if (!container) return
|
|
66
|
+
|
|
67
|
+
const { scrollTop, scrollHeight, clientHeight } = container
|
|
68
|
+
|
|
69
|
+
// 如果没有滚动条,恢复自动滚动
|
|
70
|
+
if (scrollHeight <= clientHeight) {
|
|
71
|
+
isUserScrolledUp.value = false
|
|
72
|
+
lastScrollTop = 0
|
|
73
|
+
lastScrollHeight = scrollHeight
|
|
74
|
+
return
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 检查用户是否滚动到底部附近
|
|
78
|
+
if (isNearBottom()) {
|
|
79
|
+
// 用户滚动到底部,恢复自动滚动
|
|
80
|
+
isUserScrolledUp.value = false
|
|
81
|
+
} else {
|
|
82
|
+
// 判断是否是用户主动向上滚动
|
|
83
|
+
// 条件:scrollTop 减少(向上滚动)且 scrollHeight 没有变化(不是因为内容增加)
|
|
84
|
+
const isScrollingUp = scrollTop < lastScrollTop
|
|
85
|
+
const isContentUnchanged = scrollHeight === lastScrollHeight
|
|
86
|
+
|
|
87
|
+
if (isScrollingUp && isContentUnchanged) {
|
|
88
|
+
// 用户主动向上滚动,暂停自动滚动
|
|
89
|
+
isUserScrolledUp.value = true
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// 更新记录
|
|
94
|
+
lastScrollTop = scrollTop
|
|
95
|
+
lastScrollHeight = scrollHeight
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// 监听 slot 内容变化(使用 MutationObserver)
|
|
99
|
+
let observer: MutationObserver | null = null
|
|
100
|
+
|
|
101
|
+
onMounted(() => {
|
|
102
|
+
if (!containerRef.value) return
|
|
103
|
+
|
|
104
|
+
// 初始化滚动位置记录
|
|
105
|
+
lastScrollTop = containerRef.value.scrollTop
|
|
106
|
+
lastScrollHeight = containerRef.value.scrollHeight
|
|
107
|
+
|
|
108
|
+
observer = new MutationObserver(() => {
|
|
109
|
+
nextTick(() => {
|
|
110
|
+
if (!containerRef.value) return
|
|
111
|
+
|
|
112
|
+
// 如果没有滚动条,重置状态
|
|
113
|
+
if (!hasScrollbar()) {
|
|
114
|
+
isUserScrolledUp.value = false
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// 更新 scrollHeight 记录(内容变化后)
|
|
118
|
+
lastScrollHeight = containerRef.value.scrollHeight
|
|
119
|
+
|
|
120
|
+
// 自动滚动
|
|
121
|
+
if (props.enabled && !isUserScrolledUp.value) {
|
|
122
|
+
scrollToBottom()
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
observer.observe(containerRef.value, {
|
|
128
|
+
childList: true,
|
|
129
|
+
subtree: true,
|
|
130
|
+
characterData: true
|
|
131
|
+
})
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
onUnmounted(() => {
|
|
135
|
+
observer?.disconnect()
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
// 暴露方法给父组件
|
|
139
|
+
defineExpose({
|
|
140
|
+
/** 强制滚动到底部 */
|
|
141
|
+
scrollToBottom: () => scrollToBottom(true),
|
|
142
|
+
/** 是否用户手动向上滚动了 */
|
|
143
|
+
isUserScrolledUp: () => isUserScrolledUp.value,
|
|
144
|
+
/** 容器元素引用 */
|
|
145
|
+
container: containerRef
|
|
146
|
+
})
|
|
147
|
+
</script>
|
|
148
|
+
|
|
149
|
+
<template>
|
|
150
|
+
<div
|
|
151
|
+
ref="containerRef"
|
|
152
|
+
class="auto-scroll-container"
|
|
153
|
+
@scroll="handleScroll"
|
|
154
|
+
>
|
|
155
|
+
<slot />
|
|
156
|
+
</div>
|
|
157
|
+
</template>
|
|
158
|
+
|
|
159
|
+
<style scoped>
|
|
160
|
+
.auto-scroll-container {
|
|
161
|
+
overflow-y: auto;
|
|
162
|
+
height: 100%;
|
|
163
|
+
}
|
|
164
|
+
</style>
|
|
@@ -17,6 +17,7 @@ export type ComponentMap = Partial<Record<string, Component>>
|
|
|
17
17
|
// 带稳定 ID 的块类型
|
|
18
18
|
export interface BlockWithStableId extends ParsedBlock {
|
|
19
19
|
stableId: string
|
|
20
|
+
isLastPending?: boolean // 是否是最后一个 pending 块
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
const props = withDefaults(
|
|
@@ -73,7 +74,8 @@ function getComponent(type: string): Component {
|
|
|
73
74
|
:class="[
|
|
74
75
|
'incremark-block',
|
|
75
76
|
block.status === 'completed' ? completedClass : pendingClass,
|
|
76
|
-
{ 'incremark-show-status': showBlockStatus }
|
|
77
|
+
{ 'incremark-show-status': showBlockStatus },
|
|
78
|
+
{ 'incremark-last-pending': block.isLastPending }
|
|
77
79
|
]"
|
|
78
80
|
>
|
|
79
81
|
<component :is="getComponent(block.node.type)" :node="block.node" />
|
|
@@ -1,27 +1,83 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
-
import type { PhrasingContent } from 'mdast'
|
|
2
|
+
import type { PhrasingContent, Text, HTML } from 'mdast'
|
|
3
|
+
import type { TextChunk } from '@incremark/core'
|
|
3
4
|
import IncremarkMath from './IncremarkMath.vue'
|
|
4
5
|
|
|
6
|
+
// 扩展的文本节点类型(支持 chunks)
|
|
7
|
+
interface TextNodeWithChunks extends Text {
|
|
8
|
+
stableLength?: number
|
|
9
|
+
chunks?: TextChunk[]
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Math 节点类型
|
|
13
|
+
interface MathNode {
|
|
14
|
+
type: 'math' | 'inlineMath'
|
|
15
|
+
value: string
|
|
16
|
+
}
|
|
17
|
+
|
|
5
18
|
defineProps<{
|
|
6
19
|
nodes: PhrasingContent[]
|
|
7
20
|
}>()
|
|
8
21
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
.
|
|
22
|
+
/**
|
|
23
|
+
* 获取文本节点的稳定部分(不需要动画)
|
|
24
|
+
*/
|
|
25
|
+
function getStableText(node: TextNodeWithChunks): string {
|
|
26
|
+
if (!node.chunks || node.chunks.length === 0) {
|
|
27
|
+
return node.value
|
|
28
|
+
}
|
|
29
|
+
return node.value.slice(0, node.stableLength ?? 0)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 类型守卫:检查是否是带 chunks 的文本节点
|
|
34
|
+
*/
|
|
35
|
+
function hasChunks(node: PhrasingContent): node is TextNodeWithChunks {
|
|
36
|
+
return node.type === 'text' && 'chunks' in node && Array.isArray((node as TextNodeWithChunks).chunks)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 获取节点的 chunks(类型安全)
|
|
41
|
+
*/
|
|
42
|
+
function getChunks(node: PhrasingContent): TextChunk[] | undefined {
|
|
43
|
+
if (hasChunks(node)) {
|
|
44
|
+
return node.chunks
|
|
45
|
+
}
|
|
46
|
+
return undefined
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 类型守卫:检查是否是 HTML 节点
|
|
51
|
+
*/
|
|
52
|
+
function isHtmlNode(node: PhrasingContent): node is HTML {
|
|
53
|
+
return node.type === 'html'
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 类型守卫:检查是否是 inlineMath 节点
|
|
58
|
+
* inlineMath 是 mdast-util-math 扩展的类型,不在标准 PhrasingContent 中
|
|
59
|
+
*/
|
|
60
|
+
function isInlineMath(node: PhrasingContent): node is PhrasingContent & MathNode {
|
|
61
|
+
return (node as unknown as MathNode).type === 'inlineMath'
|
|
15
62
|
}
|
|
16
63
|
</script>
|
|
17
64
|
|
|
18
65
|
<template>
|
|
19
66
|
<template v-for="(node, idx) in nodes" :key="idx">
|
|
20
|
-
<!--
|
|
21
|
-
<template v-if="node.type === 'text'">
|
|
67
|
+
<!-- 文本(支持 chunks 渐入动画) -->
|
|
68
|
+
<template v-if="node.type === 'text'">
|
|
69
|
+
<!-- 稳定文本(已经显示过的部分,无动画) -->
|
|
70
|
+
{{ getStableText(node as TextNodeWithChunks) }}
|
|
71
|
+
<!-- 新增的 chunk 部分(带渐入动画) -->
|
|
72
|
+
<span
|
|
73
|
+
v-for="chunk in getChunks(node)"
|
|
74
|
+
:key="chunk.createdAt"
|
|
75
|
+
class="incremark-fade-in"
|
|
76
|
+
>{{ chunk.text }}</span>
|
|
77
|
+
</template>
|
|
22
78
|
|
|
23
79
|
<!-- 行内公式 -->
|
|
24
|
-
<IncremarkMath v-else-if="(node
|
|
80
|
+
<IncremarkMath v-else-if="isInlineMath(node)" :node="(node as unknown as MathNode)" />
|
|
25
81
|
|
|
26
82
|
<!-- 加粗 -->
|
|
27
83
|
<strong v-else-if="node.type === 'strong'">
|
|
@@ -61,6 +117,9 @@ function escapeHtml(str: string): string {
|
|
|
61
117
|
<del v-else-if="node.type === 'delete'">
|
|
62
118
|
<IncremarkInline :nodes="(node.children as PhrasingContent[])" />
|
|
63
119
|
</del>
|
|
120
|
+
|
|
121
|
+
<!-- 原始 HTML -->
|
|
122
|
+
<span v-else-if="isHtmlNode(node)" v-html="node.value"></span>
|
|
64
123
|
</template>
|
|
65
124
|
</template>
|
|
66
125
|
|
|
@@ -72,4 +131,18 @@ function escapeHtml(str: string): string {
|
|
|
72
131
|
font-family: 'Fira Code', 'SF Mono', Consolas, monospace;
|
|
73
132
|
font-size: 0.9em;
|
|
74
133
|
}
|
|
134
|
+
|
|
135
|
+
/* 渐入动画 */
|
|
136
|
+
.incremark-fade-in {
|
|
137
|
+
animation: incremark-fade-in 0.4s ease-out;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@keyframes incremark-fade-in {
|
|
141
|
+
from {
|
|
142
|
+
opacity: 0;
|
|
143
|
+
}
|
|
144
|
+
to {
|
|
145
|
+
opacity: 1;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
75
148
|
</style>
|
package/src/components/index.ts
CHANGED
|
@@ -16,3 +16,6 @@ export { default as IncremarkThematicBreak } from './IncremarkThematicBreak.vue'
|
|
|
16
16
|
export { default as IncremarkInline } from './IncremarkInline.vue'
|
|
17
17
|
export { default as IncremarkMath } from './IncremarkMath.vue'
|
|
18
18
|
export { default as IncremarkDefault } from './IncremarkDefault.vue'
|
|
19
|
+
|
|
20
|
+
// 工具组件
|
|
21
|
+
export { default as AutoScrollContainer } from './AutoScrollContainer.vue'
|
package/src/composables/index.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export { useIncremark } from './useIncremark'
|
|
2
|
-
export type { UseIncremarkOptions } from './useIncremark'
|
|
2
|
+
export type { UseIncremarkOptions, TypewriterOptions, TypewriterControls } from './useIncremark'
|
|
3
3
|
|
|
4
4
|
export { useStreamRenderer } from './useStreamRenderer'
|
|
5
5
|
export type { UseStreamRendererOptions } from './useStreamRenderer'
|
|
6
6
|
|
|
7
7
|
export { useDevTools } from './useDevTools'
|
|
8
8
|
export type { UseDevToolsOptions } from './useDevTools'
|
|
9
|
+
|
|
10
|
+
export { useBlockTransformer } from './useBlockTransformer'
|
|
11
|
+
export type { UseBlockTransformerOptions, UseBlockTransformerReturn } from './useBlockTransformer'
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { ref, watch, computed, onUnmounted, type Ref, type ComputedRef } from 'vue'
|
|
2
|
+
import {
|
|
3
|
+
BlockTransformer,
|
|
4
|
+
createBlockTransformer,
|
|
5
|
+
type TransformerOptions,
|
|
6
|
+
type DisplayBlock,
|
|
7
|
+
type SourceBlock,
|
|
8
|
+
type AnimationEffect
|
|
9
|
+
} from '@incremark/core'
|
|
10
|
+
|
|
11
|
+
export interface UseBlockTransformerOptions extends Omit<TransformerOptions, 'onChange'> {}
|
|
12
|
+
|
|
13
|
+
export interface UseBlockTransformerReturn<T = unknown> {
|
|
14
|
+
/** 用于渲染的 display blocks */
|
|
15
|
+
displayBlocks: ComputedRef<DisplayBlock<T>[]>
|
|
16
|
+
/** 是否正在处理中 */
|
|
17
|
+
isProcessing: ComputedRef<boolean>
|
|
18
|
+
/** 是否已暂停 */
|
|
19
|
+
isPaused: ComputedRef<boolean>
|
|
20
|
+
/** 当前动画效果 */
|
|
21
|
+
effect: ComputedRef<AnimationEffect>
|
|
22
|
+
/** 跳过所有动画 */
|
|
23
|
+
skip: () => void
|
|
24
|
+
/** 重置状态 */
|
|
25
|
+
reset: () => void
|
|
26
|
+
/** 暂停动画 */
|
|
27
|
+
pause: () => void
|
|
28
|
+
/** 恢复动画 */
|
|
29
|
+
resume: () => void
|
|
30
|
+
/** 动态更新配置 */
|
|
31
|
+
setOptions: (options: Partial<Pick<TransformerOptions, 'charsPerTick' | 'tickInterval' | 'effect' | 'pauseOnHidden'>>) => void
|
|
32
|
+
/** transformer 实例(用于高级用法) */
|
|
33
|
+
transformer: BlockTransformer<T>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Vue 3 Composable: Block Transformer
|
|
38
|
+
*
|
|
39
|
+
* 用于控制 blocks 的逐步显示(打字机效果)
|
|
40
|
+
* 作为解析器和渲染器之间的中间层
|
|
41
|
+
*
|
|
42
|
+
* 特性:
|
|
43
|
+
* - 使用 requestAnimationFrame 实现流畅动画
|
|
44
|
+
* - 支持随机步长 `charsPerTick: [1, 3]`
|
|
45
|
+
* - 支持动画效果 `effect: 'typing'`
|
|
46
|
+
* - 页面不可见时自动暂停
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```vue
|
|
50
|
+
* <script setup>
|
|
51
|
+
* import { useIncremark, useBlockTransformer, defaultPlugins } from '@incremark/vue'
|
|
52
|
+
*
|
|
53
|
+
* const { blocks, completedBlocks, append, finalize } = useIncremark()
|
|
54
|
+
*
|
|
55
|
+
* // 使用 completedBlocks 作为输入(ID 稳定)
|
|
56
|
+
* const sourceBlocks = computed(() => completedBlocks.value.map(b => ({
|
|
57
|
+
* id: b.id,
|
|
58
|
+
* node: b.node,
|
|
59
|
+
* status: b.status
|
|
60
|
+
* })))
|
|
61
|
+
*
|
|
62
|
+
* // 添加打字机效果
|
|
63
|
+
* const { displayBlocks, isProcessing, skip, effect } = useBlockTransformer(sourceBlocks, {
|
|
64
|
+
* charsPerTick: [1, 3], // 随机步长
|
|
65
|
+
* tickInterval: 30,
|
|
66
|
+
* effect: 'typing', // 光标效果
|
|
67
|
+
* plugins: defaultPlugins
|
|
68
|
+
* })
|
|
69
|
+
* </script>
|
|
70
|
+
*
|
|
71
|
+
* <template>
|
|
72
|
+
* <Incremark :blocks="displayBlocks" :class="{ 'typing': effect === 'typing' }" />
|
|
73
|
+
* <button v-if="isProcessing" @click="skip">跳过</button>
|
|
74
|
+
* </template>
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export function useBlockTransformer<T = unknown>(
|
|
78
|
+
sourceBlocks: Ref<SourceBlock<T>[]> | ComputedRef<SourceBlock<T>[]>,
|
|
79
|
+
options: UseBlockTransformerOptions = {}
|
|
80
|
+
): UseBlockTransformerReturn<T> {
|
|
81
|
+
const displayBlocksRef = ref<DisplayBlock<T>[]>([])
|
|
82
|
+
const isProcessingRef = ref(false)
|
|
83
|
+
const isPausedRef = ref(false)
|
|
84
|
+
const effectRef = ref<AnimationEffect>(options.effect ?? 'none')
|
|
85
|
+
|
|
86
|
+
const transformer = createBlockTransformer<T>({
|
|
87
|
+
...options,
|
|
88
|
+
onChange: (blocks) => {
|
|
89
|
+
displayBlocksRef.value = blocks as DisplayBlock<T>[]
|
|
90
|
+
isProcessingRef.value = transformer.isProcessing()
|
|
91
|
+
isPausedRef.value = transformer.isPausedState()
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// 监听源 blocks 变化
|
|
96
|
+
watch(
|
|
97
|
+
sourceBlocks,
|
|
98
|
+
(blocks) => {
|
|
99
|
+
// 推入新 blocks
|
|
100
|
+
transformer.push(blocks)
|
|
101
|
+
|
|
102
|
+
// 处理正在显示的 block 内容更新
|
|
103
|
+
const currentDisplaying = displayBlocksRef.value.find((b) => !b.isDisplayComplete)
|
|
104
|
+
if (currentDisplaying) {
|
|
105
|
+
const updated = blocks.find((b) => b.id === currentDisplaying.id)
|
|
106
|
+
if (updated) {
|
|
107
|
+
transformer.update(updated)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
{ immediate: true, deep: true }
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
onUnmounted(() => {
|
|
115
|
+
transformer.destroy()
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
displayBlocks: computed(() => displayBlocksRef.value) as ComputedRef<DisplayBlock<T>[]>,
|
|
120
|
+
isProcessing: computed(() => isProcessingRef.value),
|
|
121
|
+
isPaused: computed(() => isPausedRef.value),
|
|
122
|
+
effect: computed(() => effectRef.value),
|
|
123
|
+
skip: () => transformer.skip(),
|
|
124
|
+
reset: () => transformer.reset(),
|
|
125
|
+
pause: () => {
|
|
126
|
+
transformer.pause()
|
|
127
|
+
isPausedRef.value = true
|
|
128
|
+
},
|
|
129
|
+
resume: () => {
|
|
130
|
+
transformer.resume()
|
|
131
|
+
isPausedRef.value = false
|
|
132
|
+
},
|
|
133
|
+
setOptions: (opts) => {
|
|
134
|
+
transformer.setOptions(opts)
|
|
135
|
+
if (opts.effect !== undefined) {
|
|
136
|
+
effectRef.value = opts.effect
|
|
137
|
+
}
|
|
138
|
+
},
|
|
139
|
+
transformer
|
|
140
|
+
}
|
|
141
|
+
}
|