@incremark/core 0.1.0 → 0.1.1

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/index.js CHANGED
@@ -5,8 +5,21 @@ import { gfm } from 'micromark-extension-gfm';
5
5
  // src/parser/IncremarkParser.ts
6
6
 
7
7
  // src/detector/index.ts
8
+ var RE_FENCE_START = /^(\s*)((`{3,})|(~{3,}))/;
9
+ var RE_EMPTY_LINE = /^\s*$/;
10
+ var RE_HEADING = /^#{1,6}\s/;
11
+ var RE_THEMATIC_BREAK = /^(\*{3,}|-{3,}|_{3,})\s*$/;
12
+ var RE_UNORDERED_LIST = /^(\s*)([-*+])\s/;
13
+ var RE_ORDERED_LIST = /^(\s*)(\d{1,9})[.)]\s/;
14
+ var RE_BLOCKQUOTE = /^\s{0,3}>/;
15
+ var RE_HTML_BLOCK_1 = /^\s{0,3}<(script|pre|style|textarea|!--|!DOCTYPE|\?|!\[CDATA\[)/i;
16
+ var RE_HTML_BLOCK_2 = /^\s{0,3}<\/?[a-zA-Z][a-zA-Z0-9-]*(\s|>|$)/;
17
+ var RE_TABLE_DELIMITER = /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?$/;
18
+ var RE_ESCAPE_SPECIAL = /[.*+?^${}()|[\]\\]/g;
19
+ var fenceEndPatternCache = /* @__PURE__ */ new Map();
20
+ var containerPatternCache = /* @__PURE__ */ new Map();
8
21
  function detectFenceStart(line) {
9
- const match = line.match(/^(\s*)((`{3,})|(~{3,}))/);
22
+ const match = line.match(RE_FENCE_START);
10
23
  if (match) {
11
24
  const fence = match[2];
12
25
  const char = fence[0];
@@ -18,45 +31,55 @@ function detectFenceEnd(line, context) {
18
31
  if (!context.inFencedCode || !context.fenceChar || !context.fenceLength) {
19
32
  return false;
20
33
  }
21
- const pattern = new RegExp(`^\\s{0,3}${context.fenceChar}{${context.fenceLength},}\\s*$`);
34
+ const cacheKey = `${context.fenceChar}-${context.fenceLength}`;
35
+ let pattern = fenceEndPatternCache.get(cacheKey);
36
+ if (!pattern) {
37
+ pattern = new RegExp(`^\\s{0,3}${context.fenceChar}{${context.fenceLength},}\\s*$`);
38
+ fenceEndPatternCache.set(cacheKey, pattern);
39
+ }
22
40
  return pattern.test(line);
23
41
  }
24
42
  function isEmptyLine(line) {
25
- return /^\s*$/.test(line);
43
+ return RE_EMPTY_LINE.test(line);
26
44
  }
27
45
  function isHeading(line) {
28
- return /^#{1,6}\s/.test(line);
46
+ return RE_HEADING.test(line);
29
47
  }
30
48
  function isThematicBreak(line) {
31
- return /^(\*{3,}|-{3,}|_{3,})\s*$/.test(line.trim());
49
+ return RE_THEMATIC_BREAK.test(line.trim());
32
50
  }
33
51
  function isListItemStart(line) {
34
- const unordered = line.match(/^(\s*)([-*+])\s/);
52
+ const unordered = line.match(RE_UNORDERED_LIST);
35
53
  if (unordered) {
36
54
  return { ordered: false, indent: unordered[1].length };
37
55
  }
38
- const ordered = line.match(/^(\s*)(\d{1,9})[.)]\s/);
56
+ const ordered = line.match(RE_ORDERED_LIST);
39
57
  if (ordered) {
40
58
  return { ordered: true, indent: ordered[1].length };
41
59
  }
42
60
  return null;
43
61
  }
44
62
  function isBlockquoteStart(line) {
45
- return /^\s{0,3}>/.test(line);
63
+ return RE_BLOCKQUOTE.test(line);
46
64
  }
47
65
  function isHtmlBlock(line) {
48
- return /^\s{0,3}<(script|pre|style|textarea|!--|!DOCTYPE|\?|!\[CDATA\[)/i.test(line) || /^\s{0,3}<\/?[a-zA-Z][a-zA-Z0-9-]*(\s|>|$)/.test(line);
66
+ return RE_HTML_BLOCK_1.test(line) || RE_HTML_BLOCK_2.test(line);
49
67
  }
50
68
  function isTableDelimiter(line) {
51
- return /^\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?$/.test(line.trim());
69
+ return RE_TABLE_DELIMITER.test(line.trim());
52
70
  }
53
71
  function detectContainer(line, config) {
54
72
  const marker = config?.marker || ":";
55
73
  const minLength = config?.minMarkerLength || 3;
56
- const escapedMarker = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
57
- const pattern = new RegExp(
58
- `^(\\s*)(${escapedMarker}{${minLength},})(?:\\s+(\\w[\\w-]*))?(?:\\s+(.*))?\\s*$`
59
- );
74
+ const cacheKey = `${marker}-${minLength}`;
75
+ let pattern = containerPatternCache.get(cacheKey);
76
+ if (!pattern) {
77
+ const escapedMarker = marker.replace(RE_ESCAPE_SPECIAL, "\\$&");
78
+ pattern = new RegExp(
79
+ `^(\\s*)(${escapedMarker}{${minLength},})(?:\\s+(\\w[\\w-]*))?(?:\\s+(.*))?\\s*$`
80
+ );
81
+ containerPatternCache.set(cacheKey, pattern);
82
+ }
60
83
  const match = line.match(pattern);
61
84
  if (!match) {
62
85
  return null;
@@ -168,7 +191,7 @@ var IncremarkParser = class {
168
191
  context;
169
192
  options;
170
193
  /** 缓存的容器配置,避免重复计算 */
171
- cachedContainerConfig = null;
194
+ containerConfig;
172
195
  /** 上次 append 返回的 pending blocks,用于 getAst 复用 */
173
196
  lastPendingBlocks = [];
174
197
  constructor(options = {}) {
@@ -177,7 +200,7 @@ var IncremarkParser = class {
177
200
  ...options
178
201
  };
179
202
  this.context = createInitialContext();
180
- this.cachedContainerConfig = this.computeContainerConfig();
203
+ this.containerConfig = this.computeContainerConfig();
181
204
  }
182
205
  generateBlockId() {
183
206
  return `block-${++this.blockIdCounter}`;
@@ -187,9 +210,6 @@ var IncremarkParser = class {
187
210
  if (!containers) return void 0;
188
211
  return containers === true ? {} : containers;
189
212
  }
190
- getContainerConfig() {
191
- return this.cachedContainerConfig ?? void 0;
192
- }
193
213
  parse(text) {
194
214
  const extensions = [];
195
215
  const mdastExtensions = [];
@@ -244,13 +264,12 @@ var IncremarkParser = class {
244
264
  let stableLine = -1;
245
265
  let stableContext = this.context;
246
266
  let tempContext = { ...this.context };
247
- const containerConfig = this.getContainerConfig();
248
267
  for (let i = this.pendingStartLine; i < this.lines.length; i++) {
249
268
  const line = this.lines[i];
250
269
  const wasInFencedCode = tempContext.inFencedCode;
251
270
  const wasInContainer = tempContext.inContainer;
252
271
  const wasContainerDepth = tempContext.containerDepth;
253
- tempContext = updateContext(line, tempContext, containerConfig);
272
+ tempContext = updateContext(line, tempContext, this.containerConfig);
254
273
  if (wasInFencedCode && !tempContext.inFencedCode) {
255
274
  if (i < this.lines.length - 1) {
256
275
  stableLine = i;
@@ -271,7 +290,7 @@ var IncremarkParser = class {
271
290
  if (tempContext.inContainer) {
272
291
  continue;
273
292
  }
274
- const stablePoint = this.checkStability(i, containerConfig);
293
+ const stablePoint = this.checkStability(i);
275
294
  if (stablePoint >= 0) {
276
295
  stableLine = stablePoint;
277
296
  stableContext = { ...tempContext };
@@ -279,7 +298,7 @@ var IncremarkParser = class {
279
298
  }
280
299
  return { line: stableLine, contextAtLine: stableContext };
281
300
  }
282
- checkStability(lineIndex, containerConfig) {
301
+ checkStability(lineIndex) {
283
302
  if (lineIndex === 0) {
284
303
  return -1;
285
304
  }
@@ -304,10 +323,10 @@ var IncremarkParser = class {
304
323
  if (isListItemStart(line) && !isListItemStart(prevLine)) {
305
324
  return lineIndex - 1;
306
325
  }
307
- if (containerConfig !== void 0) {
308
- const container = detectContainer(line, containerConfig);
326
+ if (this.containerConfig !== void 0) {
327
+ const container = detectContainer(line, this.containerConfig);
309
328
  if (container && !container.isEnd) {
310
- const prevContainer = detectContainer(prevLine, containerConfig);
329
+ const prevContainer = detectContainer(prevLine, this.containerConfig);
311
330
  if (!prevContainer || prevContainer.isEnd) {
312
331
  return lineIndex - 1;
313
332
  }
@@ -522,24 +541,25 @@ function joinLines(lines, start, end) {
522
541
 
523
542
  // src/transformer/utils.ts
524
543
  function countChars(node) {
525
- let count = 0;
526
- function traverse(n) {
527
- if (n.value && typeof n.value === "string") {
528
- count += n.value.length;
529
- return;
530
- }
531
- if (n.children && Array.isArray(n.children)) {
532
- for (const child of n.children) {
533
- traverse(child);
534
- }
544
+ return countCharsInNode(node);
545
+ }
546
+ function countCharsInNode(n) {
547
+ if (n.value && typeof n.value === "string") {
548
+ return n.value.length;
549
+ }
550
+ if (n.children && Array.isArray(n.children)) {
551
+ let count = 0;
552
+ for (const child of n.children) {
553
+ count += countCharsInNode(child);
535
554
  }
555
+ return count;
536
556
  }
537
- traverse(node);
538
- return count;
557
+ return 1;
539
558
  }
540
- function sliceAst(node, maxChars, accumulatedChunks) {
559
+ function sliceAst(node, maxChars, accumulatedChunks, skipChars = 0) {
541
560
  if (maxChars <= 0) return null;
542
- let remaining = maxChars;
561
+ if (skipChars >= maxChars) return null;
562
+ let remaining = maxChars - skipChars;
543
563
  let charIndex = 0;
544
564
  const chunkRanges = [];
545
565
  if (accumulatedChunks && accumulatedChunks.chunks.length > 0) {
@@ -556,13 +576,18 @@ function sliceAst(node, maxChars, accumulatedChunks) {
556
576
  function process(n) {
557
577
  if (remaining <= 0) return null;
558
578
  if (n.value && typeof n.value === "string") {
559
- const take = Math.min(n.value.length, remaining);
579
+ const nodeStart = charIndex;
580
+ const nodeEnd = charIndex + n.value.length;
581
+ if (nodeEnd <= skipChars) {
582
+ charIndex = nodeEnd;
583
+ return null;
584
+ }
585
+ const skipInNode = Math.max(0, skipChars - nodeStart);
586
+ const take = Math.min(n.value.length - skipInNode, remaining);
560
587
  remaining -= take;
561
588
  if (take === 0) return null;
562
- const slicedValue = n.value.slice(0, take);
563
- const nodeStart = charIndex;
564
- const nodeEnd = charIndex + take;
565
- charIndex += take;
589
+ const slicedValue = n.value.slice(skipInNode, skipInNode + take);
590
+ charIndex = nodeEnd;
566
591
  const result = {
567
592
  ...n,
568
593
  value: slicedValue
@@ -571,11 +596,11 @@ function sliceAst(node, maxChars, accumulatedChunks) {
571
596
  const nodeChunks = [];
572
597
  let firstChunkLocalStart = take;
573
598
  for (const range of chunkRanges) {
574
- const overlapStart = Math.max(range.start, nodeStart);
575
- const overlapEnd = Math.min(range.end, nodeEnd);
599
+ const overlapStart = Math.max(range.start, nodeStart + skipInNode);
600
+ const overlapEnd = Math.min(range.end, nodeStart + skipInNode + take);
576
601
  if (overlapStart < overlapEnd) {
577
- const localStart = overlapStart - nodeStart;
578
- const localEnd = overlapEnd - nodeStart;
602
+ const localStart = overlapStart - (nodeStart + skipInNode);
603
+ const localEnd = overlapEnd - (nodeStart + skipInNode);
579
604
  const chunkText = slicedValue.slice(localStart, localEnd);
580
605
  if (chunkText.length > 0) {
581
606
  if (nodeChunks.length === 0) {
@@ -597,12 +622,24 @@ function sliceAst(node, maxChars, accumulatedChunks) {
597
622
  }
598
623
  if (n.children && Array.isArray(n.children)) {
599
624
  const newChildren = [];
625
+ let childCharIndex = charIndex;
600
626
  for (const child of n.children) {
601
627
  if (remaining <= 0) break;
628
+ const childChars = countCharsInNode(child);
629
+ const childStart = childCharIndex;
630
+ const childEnd = childCharIndex + childChars;
631
+ if (childEnd <= skipChars) {
632
+ childCharIndex = childEnd;
633
+ continue;
634
+ }
635
+ const savedCharIndex = charIndex;
636
+ charIndex = childStart;
602
637
  const processed = process(child);
638
+ charIndex = savedCharIndex;
603
639
  if (processed) {
604
640
  newChildren.push(processed);
605
641
  }
642
+ childCharIndex = childEnd;
606
643
  }
607
644
  if (newChildren.length === 0) {
608
645
  return null;
@@ -615,8 +652,79 @@ function sliceAst(node, maxChars, accumulatedChunks) {
615
652
  }
616
653
  return process(node);
617
654
  }
655
+ function appendToAst(baseNode, sourceNode, startChars, endChars, accumulatedChunks) {
656
+ if (endChars <= startChars) {
657
+ return baseNode;
658
+ }
659
+ const newPart = sliceAst(sourceNode, endChars, accumulatedChunks, startChars);
660
+ if (!newPart) {
661
+ return baseNode;
662
+ }
663
+ return mergeAstNodes(baseNode, newPart);
664
+ }
665
+ function mergeAstNodes(baseNode, newPart) {
666
+ if (baseNode.type !== newPart.type) {
667
+ return baseNode;
668
+ }
669
+ const base = baseNode;
670
+ const part = newPart;
671
+ if (base.value && typeof base.value === "string" && part.value && typeof part.value === "string") {
672
+ const baseChunks = base.chunks || [];
673
+ const partChunks = part.chunks || [];
674
+ const mergedChunks = [...baseChunks, ...partChunks];
675
+ const mergedValue = base.value + part.value;
676
+ const baseStableLength = base.stableLength ?? 0;
677
+ const result = {
678
+ ...base,
679
+ value: mergedValue,
680
+ stableLength: mergedChunks.length > 0 ? baseStableLength : void 0,
681
+ chunks: mergedChunks.length > 0 ? mergedChunks : void 0
682
+ };
683
+ return result;
684
+ }
685
+ if (base.children && Array.isArray(base.children) && part.children && Array.isArray(part.children)) {
686
+ if (base.children.length > 0 && part.children.length > 0) {
687
+ const lastBaseChild = base.children[base.children.length - 1];
688
+ const firstPartChild = part.children[0];
689
+ if (lastBaseChild.type === firstPartChild.type) {
690
+ const merged = mergeAstNodes(lastBaseChild, firstPartChild);
691
+ return {
692
+ ...base,
693
+ children: [
694
+ ...base.children.slice(0, -1),
695
+ merged,
696
+ ...part.children.slice(1)
697
+ ]
698
+ };
699
+ }
700
+ }
701
+ return {
702
+ ...base,
703
+ children: [...base.children, ...part.children]
704
+ };
705
+ }
706
+ return baseNode;
707
+ }
618
708
  function cloneNode(node) {
619
- return JSON.parse(JSON.stringify(node));
709
+ if (typeof structuredClone === "function") {
710
+ return structuredClone(node);
711
+ }
712
+ return deepClone(node);
713
+ }
714
+ function deepClone(obj) {
715
+ if (obj === null || typeof obj !== "object") {
716
+ return obj;
717
+ }
718
+ if (Array.isArray(obj)) {
719
+ return obj.map((item) => deepClone(item));
720
+ }
721
+ const cloned = {};
722
+ for (const key in obj) {
723
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
724
+ cloned[key] = deepClone(obj[key]);
725
+ }
726
+ }
727
+ return cloned;
620
728
  }
621
729
 
622
730
  // src/transformer/BlockTransformer.ts
@@ -630,6 +738,13 @@ var BlockTransformer = class {
630
738
  chunks = [];
631
739
  // 累积的 chunks(用于 fade-in 动画)
632
740
  visibilityHandler = null;
741
+ // ============ 性能优化:缓存机制 ============
742
+ /** 缓存的已截断 displayNode(稳定的部分,避免重复遍历) */
743
+ cachedDisplayNode = null;
744
+ /** 缓存的字符数(避免重复计算) */
745
+ cachedTotalChars = null;
746
+ /** 当前缓存的进度(对应 cachedDisplayNode) */
747
+ cachedProgress = 0;
633
748
  constructor(options = {}) {
634
749
  this.options = {
635
750
  charsPerTick: options.charsPerTick ?? 1,
@@ -664,7 +779,8 @@ var BlockTransformer = class {
664
779
  if (this.state.currentBlock) {
665
780
  const updated = blocks.find((b) => b.id === this.state.currentBlock.id);
666
781
  if (updated && updated.node !== this.state.currentBlock.node) {
667
- const oldTotal = this.countChars(this.state.currentBlock.node);
782
+ this.clearCache();
783
+ const oldTotal = this.cachedTotalChars ?? this.countChars(this.state.currentBlock.node);
668
784
  const newTotal = this.countChars(updated.node);
669
785
  if (newTotal < oldTotal || newTotal < this.state.currentProgress) {
670
786
  this.state.currentProgress = Math.min(this.state.currentProgress, newTotal);
@@ -683,10 +799,11 @@ var BlockTransformer = class {
683
799
  */
684
800
  update(block) {
685
801
  if (this.state.currentBlock?.id === block.id) {
686
- const oldTotal = this.countChars(this.state.currentBlock.node);
802
+ const oldTotal = this.cachedTotalChars ?? this.countChars(this.state.currentBlock.node);
687
803
  const newTotal = this.countChars(block.node);
688
804
  this.state.currentBlock = block;
689
805
  if (newTotal > oldTotal && !this.rafId && !this.isPaused && this.state.currentProgress >= oldTotal) {
806
+ this.clearCache();
690
807
  this.startIfNeeded();
691
808
  }
692
809
  }
@@ -708,6 +825,7 @@ var BlockTransformer = class {
708
825
  pendingBlocks: []
709
826
  };
710
827
  this.chunks = [];
828
+ this.clearCache();
711
829
  this.emit();
712
830
  }
713
831
  /**
@@ -722,6 +840,7 @@ var BlockTransformer = class {
722
840
  pendingBlocks: []
723
841
  };
724
842
  this.chunks = [];
843
+ this.clearCache();
725
844
  this.emit();
726
845
  }
727
846
  /**
@@ -742,6 +861,7 @@ var BlockTransformer = class {
742
861
  }
743
862
  /**
744
863
  * 获取用于渲染的 display blocks
864
+ * 优化:使用缓存的 displayNode,避免重复遍历已稳定的节点
745
865
  */
746
866
  getDisplayBlocks() {
747
867
  const result = [];
@@ -754,16 +874,13 @@ var BlockTransformer = class {
754
874
  });
755
875
  }
756
876
  if (this.state.currentBlock) {
757
- const total = this.countChars(this.state.currentBlock.node);
758
- const accumulatedChunks = this.options.effect === "fade-in" && this.chunks.length > 0 ? { stableChars: 0, chunks: this.chunks } : void 0;
759
- const displayNode = this.sliceNode(
760
- this.state.currentBlock.node,
761
- this.state.currentProgress,
762
- accumulatedChunks
763
- );
877
+ const total = this.getTotalChars();
878
+ if (this.state.currentProgress !== this.cachedProgress || !this.cachedDisplayNode) {
879
+ this.updateCachedDisplayNode();
880
+ }
764
881
  result.push({
765
882
  ...this.state.currentBlock,
766
- displayNode: displayNode || { type: "paragraph", children: [] },
883
+ displayNode: this.cachedDisplayNode || { type: "paragraph", children: [] },
767
884
  progress: total > 0 ? this.state.currentProgress / total : 1,
768
885
  isDisplayComplete: false
769
886
  });
@@ -863,6 +980,7 @@ var BlockTransformer = class {
863
980
  if (!this.state.currentBlock && this.state.pendingBlocks.length > 0) {
864
981
  this.state.currentBlock = this.state.pendingBlocks.shift();
865
982
  this.state.currentProgress = 0;
983
+ this.clearCache();
866
984
  }
867
985
  if (this.state.currentBlock) {
868
986
  this.isRunning = true;
@@ -893,7 +1011,7 @@ var BlockTransformer = class {
893
1011
  this.processNext();
894
1012
  return;
895
1013
  }
896
- const total = this.countChars(block.node);
1014
+ const total = this.getTotalChars();
897
1015
  const step = this.getStep();
898
1016
  const prevProgress = this.state.currentProgress;
899
1017
  this.state.currentProgress = Math.min(prevProgress + step, total);
@@ -913,6 +1031,7 @@ var BlockTransformer = class {
913
1031
  this.state.currentBlock = null;
914
1032
  this.state.currentProgress = 0;
915
1033
  this.chunks = [];
1034
+ this.clearCache();
916
1035
  this.processNext();
917
1036
  }
918
1037
  }
@@ -958,6 +1077,7 @@ var BlockTransformer = class {
958
1077
  this.state.currentBlock = this.state.pendingBlocks.shift();
959
1078
  this.state.currentProgress = 0;
960
1079
  this.chunks = [];
1080
+ this.clearCache();
961
1081
  this.emit();
962
1082
  } else {
963
1083
  this.isRunning = false;
@@ -1006,6 +1126,72 @@ var BlockTransformer = class {
1006
1126
  }
1007
1127
  }
1008
1128
  }
1129
+ // ============ 缓存管理方法 ============
1130
+ /**
1131
+ * 更新缓存的 displayNode
1132
+ * 使用真正的增量追加模式:只处理新增部分,不重复遍历已稳定的节点
1133
+ */
1134
+ updateCachedDisplayNode() {
1135
+ const block = this.state.currentBlock;
1136
+ if (!block) {
1137
+ this.cachedDisplayNode = null;
1138
+ this.cachedProgress = 0;
1139
+ return;
1140
+ }
1141
+ const currentProgress = this.state.currentProgress;
1142
+ if (currentProgress < this.cachedProgress) {
1143
+ this.cachedDisplayNode = this.sliceNode(
1144
+ block.node,
1145
+ currentProgress,
1146
+ this.getAccumulatedChunks()
1147
+ );
1148
+ this.cachedProgress = currentProgress;
1149
+ return;
1150
+ }
1151
+ if (currentProgress > this.cachedProgress && this.cachedDisplayNode) {
1152
+ this.cachedDisplayNode = appendToAst(
1153
+ this.cachedDisplayNode,
1154
+ block.node,
1155
+ this.cachedProgress,
1156
+ currentProgress,
1157
+ this.getAccumulatedChunks()
1158
+ );
1159
+ this.cachedProgress = currentProgress;
1160
+ } else if (!this.cachedDisplayNode) {
1161
+ this.cachedDisplayNode = this.sliceNode(
1162
+ block.node,
1163
+ currentProgress,
1164
+ this.getAccumulatedChunks()
1165
+ );
1166
+ this.cachedProgress = currentProgress;
1167
+ }
1168
+ }
1169
+ /**
1170
+ * 获取总字符数(带缓存)
1171
+ */
1172
+ getTotalChars() {
1173
+ if (this.cachedTotalChars === null && this.state.currentBlock) {
1174
+ this.cachedTotalChars = this.countChars(this.state.currentBlock.node);
1175
+ }
1176
+ return this.cachedTotalChars ?? 0;
1177
+ }
1178
+ /**
1179
+ * 清除缓存(当 block 切换或内容更新时)
1180
+ */
1181
+ clearCache() {
1182
+ this.cachedDisplayNode = null;
1183
+ this.cachedTotalChars = null;
1184
+ this.cachedProgress = 0;
1185
+ }
1186
+ /**
1187
+ * 获取累积的 chunks(用于 fade-in 效果)
1188
+ */
1189
+ getAccumulatedChunks() {
1190
+ if (this.options.effect === "fade-in" && this.chunks.length > 0) {
1191
+ return { stableChars: 0, chunks: this.chunks };
1192
+ }
1193
+ return void 0;
1194
+ }
1009
1195
  };
1010
1196
  function createBlockTransformer(options) {
1011
1197
  return new BlockTransformer(options);