@vectojs/markdown 0.20.3 → 0.21.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.
@@ -83,6 +83,23 @@ export interface MarkdownOptions {
83
83
  * download behaviour to observe.
84
84
  */
85
85
  saveFile?: (filename: string, content: string, mimeType: string) => void;
86
+ /**
87
+ * Materialize only the top-level blocks near the viewport of a very large
88
+ * document, representing off-screen height as numeric offsets rather than
89
+ * entities. Off by default; pass `true` or `{ overscan }` to opt in.
90
+ *
91
+ * The host must drive it with {@link Markdown.setVisibleRange} each scroll
92
+ * frame (a `ScrollView` whose content is this `Markdown` does so
93
+ * automatically). Off-screen height is estimated from the token source and
94
+ * refined to the exact measured height when a block first mounts, so the
95
+ * scrollbar stays correct while only a window of blocks exists as entities.
96
+ *
97
+ * Not supported together with streaming (`createStream` / `appendMarkdown`):
98
+ * a document that virtualizes must be rendered whole.
99
+ */
100
+ virtualize?: boolean | {
101
+ overscan?: number;
102
+ };
86
103
  }
87
104
  /**
88
105
  * Renders Markdown content into a VectoJS entity tree using {@link marked}.
@@ -138,6 +155,19 @@ export declare class Markdown extends UIComponent {
138
155
  /** File saver used by the download controls. */
139
156
  saveFile: (filename: string, content: string, mimeType: string) => void;
140
157
  private activeBlockMetrics;
158
+ /** Whether `opts.virtualize` enabled viewport-culled block materialization. */
159
+ private readonly virtualizeBlocks;
160
+ /** Overscan margin (px) added above/below the visible window before mounting. */
161
+ private readonly virtualOverscan;
162
+ /** Top-level tokens that produce a child entity, in document order. */
163
+ private virtualTokens;
164
+ /** Fenwick tree over per-block strides (height + blockGap) when virtualizing. */
165
+ private virtualHeights;
166
+ /** token index → mounted entity, for blocks currently inside `content`. */
167
+ private readonly virtualMounted;
168
+ /** Visible window top (scroll offset) and height, last set via setVisibleRange. */
169
+ private virtualScrollY;
170
+ private virtualViewportH;
141
171
  /**
142
172
  * Called after this entity's own `width`/`height` changed because the document
143
173
  * was re-laid-out. **This is the hook to wire up when a host has to move or
@@ -388,6 +418,32 @@ export declare class Markdown extends UIComponent {
388
418
  */
389
419
  get frontMatterFields(): Readonly<Record<string, string>>;
390
420
  private renderMarkdown;
421
+ /**
422
+ * Drive the virtualized window from the host's scroll state.
423
+ *
424
+ * Only the blocks intersecting `[scrollY − overscan, scrollY + viewportHeight +
425
+ * overscan]` stay materialized; every other block is a numeric offset in the
426
+ * height tree, so the scrollbar still reports the full document height while
427
+ * only a window of blocks exists as entities. A no-op when `virtualize` was
428
+ * not enabled.
429
+ */
430
+ setVisibleRange(scrollY: number, viewportHeight: number): void;
431
+ /**
432
+ * Cheap per-block height estimate from the token source, refined to the exact
433
+ * measured height when the block first mounts. Coarse by design: the estimate
434
+ * only has to keep the scrollbar plausible before a block is first seen; the
435
+ * Fenwick tree corrects the rest.
436
+ */
437
+ private estimateBlockHeight;
438
+ /** Estimate a wrapped line count from raw character length against the max width. */
439
+ private estimateTextHeight;
440
+ /**
441
+ * Mount the blocks in the current window and unmount the rest. The `content`
442
+ * Stack lays the mounted window contiguously from y = 0; `content.y` is then
443
+ * set to the skipped prefix sum so the first mounted block lands at its true
444
+ * y — a numeric spacer, not a wrapper entity.
445
+ */
446
+ private reconcileVirtual;
391
447
  /** Create a frame-coalesced stream bound to this Markdown instance. */
392
448
  createStream(options?: StreamControllerOptions): StreamController;
393
449
  /**
package/dist/index.js CHANGED
@@ -3499,6 +3499,19 @@ var Markdown = class _Markdown extends import_ui4.UIComponent {
3499
3499
  /** File saver used by the download controls. */
3500
3500
  saveFile;
3501
3501
  activeBlockMetrics = null;
3502
+ /** Whether `opts.virtualize` enabled viewport-culled block materialization. */
3503
+ virtualizeBlocks;
3504
+ /** Overscan margin (px) added above/below the visible window before mounting. */
3505
+ virtualOverscan;
3506
+ /** Top-level tokens that produce a child entity, in document order. */
3507
+ virtualTokens = null;
3508
+ /** Fenwick tree over per-block strides (height + blockGap) when virtualizing. */
3509
+ virtualHeights = null;
3510
+ /** token index → mounted entity, for blocks currently inside `content`. */
3511
+ virtualMounted = /* @__PURE__ */ new Map();
3512
+ /** Visible window top (scroll offset) and height, last set via setVisibleRange. */
3513
+ virtualScrollY = 0;
3514
+ virtualViewportH = 0;
3502
3515
  /**
3503
3516
  * Called after this entity's own `width`/`height` changed because the document
3504
3517
  * was re-laid-out. **This is the hook to wire up when a host has to move or
@@ -3784,9 +3797,13 @@ var Markdown = class _Markdown extends import_ui4.UIComponent {
3784
3797
  this.showCodeLanguage = opts.showCodeLanguage ?? false;
3785
3798
  this.writeClipboard = opts.writeClipboard ?? defaultWriteClipboard;
3786
3799
  this.saveFile = opts.saveFile ?? defaultSaveFile;
3800
+ const virt = opts.virtualize;
3801
+ this.virtualizeBlocks = virt === true || typeof virt === "object" && virt !== null;
3802
+ this.virtualOverscan = typeof virt === "object" && virt !== null && typeof virt.overscan === "number" ? virt.overscan : 800;
3787
3803
  this.content = new import_ui4.Stack({
3788
3804
  direction: "vertical",
3789
- gap: this.theme.blockGap
3805
+ gap: this.theme.blockGap,
3806
+ cullOffscreenChildren: true
3790
3807
  });
3791
3808
  this.add(this.content);
3792
3809
  this.rawMarkdown = "";
@@ -3896,6 +3913,20 @@ var Markdown = class _Markdown extends import_ui4.UIComponent {
3896
3913
  const tokens = lexMarkdown(text, this._userTiming);
3897
3914
  this.setTokens(tokens);
3898
3915
  this.abbreviations = collectAbbreviations(tokens);
3916
+ if (this.virtualizeBlocks) {
3917
+ this.virtualTokens = tokens.filter((token) => this.producesEntity(token));
3918
+ const n = this.virtualTokens.length;
3919
+ this.virtualHeights = new import_ui4.RowHeights(n, 0);
3920
+ for (let i = 0; i < n; i++) {
3921
+ this.virtualHeights.set(
3922
+ i,
3923
+ this.estimateBlockHeight(this.virtualTokens[i]) + this.theme.blockGap
3924
+ );
3925
+ }
3926
+ this.virtualMounted.clear();
3927
+ this.reconcileVirtual();
3928
+ return;
3929
+ }
3899
3930
  for (const token of tokens) {
3900
3931
  const el = this.renderToken(token);
3901
3932
  if (el) {
@@ -3905,8 +3936,123 @@ var Markdown = class _Markdown extends import_ui4.UIComponent {
3905
3936
  this.width = this.content.width;
3906
3937
  this.height = this.content.height;
3907
3938
  }
3939
+ /**
3940
+ * Drive the virtualized window from the host's scroll state.
3941
+ *
3942
+ * Only the blocks intersecting `[scrollY − overscan, scrollY + viewportHeight +
3943
+ * overscan]` stay materialized; every other block is a numeric offset in the
3944
+ * height tree, so the scrollbar still reports the full document height while
3945
+ * only a window of blocks exists as entities. A no-op when `virtualize` was
3946
+ * not enabled.
3947
+ */
3948
+ setVisibleRange(scrollY, viewportHeight) {
3949
+ if (!this.virtualizeBlocks) return;
3950
+ this.virtualScrollY = Math.max(0, scrollY);
3951
+ this.virtualViewportH = Math.max(0, viewportHeight);
3952
+ this.reconcileVirtual();
3953
+ }
3954
+ /**
3955
+ * Cheap per-block height estimate from the token source, refined to the exact
3956
+ * measured height when the block first mounts. Coarse by design: the estimate
3957
+ * only has to keep the scrollbar plausible before a block is first seen; the
3958
+ * Fenwick tree corrects the rest.
3959
+ */
3960
+ estimateBlockHeight(token) {
3961
+ const t = this.theme;
3962
+ switch (token.type) {
3963
+ case "heading": {
3964
+ const size = headingSize(t, token.depth);
3965
+ return Math.ceil(size * 1.5) + 8;
3966
+ }
3967
+ case "paragraph":
3968
+ return this.estimateTextHeight(token.text, t.bodyLineHeight);
3969
+ case "code": {
3970
+ const raw = token.text;
3971
+ const lines = Math.max(1, raw.split("\n").length - (raw.endsWith("\n") ? 1 : 0));
3972
+ let h = lines * t.codeLineHeight + t.codePadding * 2;
3973
+ if (this.showCodeLanguage) h += t.codeLangFontSize + 8;
3974
+ return h;
3975
+ }
3976
+ case "hr":
3977
+ return 24;
3978
+ case "blockquote":
3979
+ case "container": {
3980
+ const inner = token.tokens ?? [];
3981
+ let sum = 0;
3982
+ for (const tk of inner) sum += this.estimateBlockHeight(tk) + t.blockGap;
3983
+ return sum + t.quoteInnerGap * 2 + 8;
3984
+ }
3985
+ case "list": {
3986
+ const items = token.items ?? [];
3987
+ let sum = 0;
3988
+ for (const item of items) {
3989
+ for (const tk of item.tokens ?? []) sum += this.estimateBlockHeight(tk) + t.listItemGap;
3990
+ }
3991
+ return sum + t.listGap;
3992
+ }
3993
+ case "table": {
3994
+ const table = token;
3995
+ const rows = (table.rows?.length ?? 0) + (table.header?.length ?? 0);
3996
+ return rows * (t.tableFontSize + 16) + 16;
3997
+ }
3998
+ case "footnoteDef":
3999
+ return this.estimateTextHeight(token.text ?? "", t.bodyLineHeight);
4000
+ case "html":
4001
+ return 200;
4002
+ default:
4003
+ return t.bodyLineHeight;
4004
+ }
4005
+ }
4006
+ /** Estimate a wrapped line count from raw character length against the max width. */
4007
+ estimateTextHeight(text, lineHeight) {
4008
+ const charsPerLine = Math.max(20, Math.floor(this.maxWidth / (this.theme.fontSize * 0.5)));
4009
+ const lines = Math.max(1, Math.ceil(text.length / charsPerLine));
4010
+ return lines * lineHeight;
4011
+ }
4012
+ /**
4013
+ * Mount the blocks in the current window and unmount the rest. The `content`
4014
+ * Stack lays the mounted window contiguously from y = 0; `content.y` is then
4015
+ * set to the skipped prefix sum so the first mounted block lands at its true
4016
+ * y — a numeric spacer, not a wrapper entity.
4017
+ */
4018
+ reconcileVirtual() {
4019
+ const tree = this.virtualHeights;
4020
+ const tokens = this.virtualTokens;
4021
+ if (!tree || !tokens) return;
4022
+ const first = tree.indexAt(Math.max(0, this.virtualScrollY - this.virtualOverscan));
4023
+ const last = tree.indexAt(
4024
+ Math.max(0, this.virtualScrollY + this.virtualViewportH + this.virtualOverscan)
4025
+ );
4026
+ let changed = false;
4027
+ for (const [idx, el] of this.virtualMounted) {
4028
+ if (idx < first || idx > last) {
4029
+ el.destroy();
4030
+ this.virtualMounted.delete(idx);
4031
+ changed = true;
4032
+ }
4033
+ }
4034
+ for (let i = first; i <= last; i++) {
4035
+ if (this.virtualMounted.has(i)) continue;
4036
+ const el = this.renderToken(tokens[i]);
4037
+ if (!el) continue;
4038
+ this.content.add(el);
4039
+ tree.set(i, el.height + this.theme.blockGap);
4040
+ this.virtualMounted.set(i, el);
4041
+ changed = true;
4042
+ }
4043
+ if (changed) this.content.layout();
4044
+ this.content.y = tree.prefix(first);
4045
+ const nextHeight = Math.max(0, tree.total() - this.theme.blockGap);
4046
+ const heightChanged = nextHeight !== this.height;
4047
+ this.width = this.content.width;
4048
+ this.height = nextHeight;
4049
+ if (heightChanged) this.notifyLayoutUpdated();
4050
+ }
3908
4051
  /** Create a frame-coalesced stream bound to this Markdown instance. */
3909
4052
  createStream(options = {}) {
4053
+ if (this.virtualizeBlocks) {
4054
+ throw new Error("Markdown.createStream is not supported when virtualize is enabled");
4055
+ }
3910
4056
  if (this.streamController) {
3911
4057
  throw new Error("Markdown already has an active StreamController");
3912
4058
  }
@@ -3979,6 +4125,18 @@ var Markdown = class _Markdown extends import_ui4.UIComponent {
3979
4125
  const next = Math.max(0, maxWidth);
3980
4126
  if (next === this.maxWidth) return this;
3981
4127
  this.maxWidth = next;
4128
+ if (this.virtualizeBlocks && this.virtualTokens && this.virtualHeights) {
4129
+ const tokens = this.virtualTokens;
4130
+ const tree = this.virtualHeights;
4131
+ for (const el of this.virtualMounted.values()) el.destroy();
4132
+ this.virtualMounted.clear();
4133
+ for (let i = 0; i < tokens.length; i++) {
4134
+ tree.set(i, this.estimateBlockHeight(tokens[i]) + this.theme.blockGap);
4135
+ }
4136
+ this.reconcileVirtual();
4137
+ this.scene?.markDirty();
4138
+ return this;
4139
+ }
3982
4140
  let childIndex = 0;
3983
4141
  const children = this.content.children;
3984
4142
  for (const token of this.tokens) {
@@ -4513,6 +4671,9 @@ var Markdown = class _Markdown extends import_ui4.UIComponent {
4513
4671
  return this.appendMarkdownCore(chunk);
4514
4672
  }
4515
4673
  appendMarkdownCore(chunk) {
4674
+ if (this.virtualizeBlocks) {
4675
+ throw new Error("Markdown.appendMarkdown is not supported when virtualize is enabled");
4676
+ }
4516
4677
  const before = this.rawMarkdown.length;
4517
4678
  this.consumeFrontMatter(chunk);
4518
4679
  this.streamStats.appends++;
package/dist/index.mjs CHANGED
@@ -2948,7 +2948,15 @@ function renderFencedBlock(source, lang, options) {
2948
2948
  }
2949
2949
 
2950
2950
  // src/Markdown.ts
2951
- import { RichText as RichText2, Stack, Table, Text, Image, UIComponent as UIComponent3 } from "@vectojs/ui";
2951
+ import {
2952
+ RichText as RichText2,
2953
+ RowHeights,
2954
+ Stack,
2955
+ Table,
2956
+ Text,
2957
+ Image,
2958
+ UIComponent as UIComponent3
2959
+ } from "@vectojs/ui";
2952
2960
 
2953
2961
  // src/blockAffordances.ts
2954
2962
  import { Button, measureText as measureText2, UIComponent as UIComponent2 } from "@vectojs/ui";
@@ -3445,6 +3453,19 @@ var Markdown = class _Markdown extends UIComponent3 {
3445
3453
  /** File saver used by the download controls. */
3446
3454
  saveFile;
3447
3455
  activeBlockMetrics = null;
3456
+ /** Whether `opts.virtualize` enabled viewport-culled block materialization. */
3457
+ virtualizeBlocks;
3458
+ /** Overscan margin (px) added above/below the visible window before mounting. */
3459
+ virtualOverscan;
3460
+ /** Top-level tokens that produce a child entity, in document order. */
3461
+ virtualTokens = null;
3462
+ /** Fenwick tree over per-block strides (height + blockGap) when virtualizing. */
3463
+ virtualHeights = null;
3464
+ /** token index → mounted entity, for blocks currently inside `content`. */
3465
+ virtualMounted = /* @__PURE__ */ new Map();
3466
+ /** Visible window top (scroll offset) and height, last set via setVisibleRange. */
3467
+ virtualScrollY = 0;
3468
+ virtualViewportH = 0;
3448
3469
  /**
3449
3470
  * Called after this entity's own `width`/`height` changed because the document
3450
3471
  * was re-laid-out. **This is the hook to wire up when a host has to move or
@@ -3730,9 +3751,13 @@ var Markdown = class _Markdown extends UIComponent3 {
3730
3751
  this.showCodeLanguage = opts.showCodeLanguage ?? false;
3731
3752
  this.writeClipboard = opts.writeClipboard ?? defaultWriteClipboard;
3732
3753
  this.saveFile = opts.saveFile ?? defaultSaveFile;
3754
+ const virt = opts.virtualize;
3755
+ this.virtualizeBlocks = virt === true || typeof virt === "object" && virt !== null;
3756
+ this.virtualOverscan = typeof virt === "object" && virt !== null && typeof virt.overscan === "number" ? virt.overscan : 800;
3733
3757
  this.content = new Stack({
3734
3758
  direction: "vertical",
3735
- gap: this.theme.blockGap
3759
+ gap: this.theme.blockGap,
3760
+ cullOffscreenChildren: true
3736
3761
  });
3737
3762
  this.add(this.content);
3738
3763
  this.rawMarkdown = "";
@@ -3842,6 +3867,20 @@ var Markdown = class _Markdown extends UIComponent3 {
3842
3867
  const tokens = lexMarkdown(text, this._userTiming);
3843
3868
  this.setTokens(tokens);
3844
3869
  this.abbreviations = collectAbbreviations(tokens);
3870
+ if (this.virtualizeBlocks) {
3871
+ this.virtualTokens = tokens.filter((token) => this.producesEntity(token));
3872
+ const n = this.virtualTokens.length;
3873
+ this.virtualHeights = new RowHeights(n, 0);
3874
+ for (let i = 0; i < n; i++) {
3875
+ this.virtualHeights.set(
3876
+ i,
3877
+ this.estimateBlockHeight(this.virtualTokens[i]) + this.theme.blockGap
3878
+ );
3879
+ }
3880
+ this.virtualMounted.clear();
3881
+ this.reconcileVirtual();
3882
+ return;
3883
+ }
3845
3884
  for (const token of tokens) {
3846
3885
  const el = this.renderToken(token);
3847
3886
  if (el) {
@@ -3851,8 +3890,123 @@ var Markdown = class _Markdown extends UIComponent3 {
3851
3890
  this.width = this.content.width;
3852
3891
  this.height = this.content.height;
3853
3892
  }
3893
+ /**
3894
+ * Drive the virtualized window from the host's scroll state.
3895
+ *
3896
+ * Only the blocks intersecting `[scrollY − overscan, scrollY + viewportHeight +
3897
+ * overscan]` stay materialized; every other block is a numeric offset in the
3898
+ * height tree, so the scrollbar still reports the full document height while
3899
+ * only a window of blocks exists as entities. A no-op when `virtualize` was
3900
+ * not enabled.
3901
+ */
3902
+ setVisibleRange(scrollY, viewportHeight) {
3903
+ if (!this.virtualizeBlocks) return;
3904
+ this.virtualScrollY = Math.max(0, scrollY);
3905
+ this.virtualViewportH = Math.max(0, viewportHeight);
3906
+ this.reconcileVirtual();
3907
+ }
3908
+ /**
3909
+ * Cheap per-block height estimate from the token source, refined to the exact
3910
+ * measured height when the block first mounts. Coarse by design: the estimate
3911
+ * only has to keep the scrollbar plausible before a block is first seen; the
3912
+ * Fenwick tree corrects the rest.
3913
+ */
3914
+ estimateBlockHeight(token) {
3915
+ const t = this.theme;
3916
+ switch (token.type) {
3917
+ case "heading": {
3918
+ const size = headingSize(t, token.depth);
3919
+ return Math.ceil(size * 1.5) + 8;
3920
+ }
3921
+ case "paragraph":
3922
+ return this.estimateTextHeight(token.text, t.bodyLineHeight);
3923
+ case "code": {
3924
+ const raw = token.text;
3925
+ const lines = Math.max(1, raw.split("\n").length - (raw.endsWith("\n") ? 1 : 0));
3926
+ let h = lines * t.codeLineHeight + t.codePadding * 2;
3927
+ if (this.showCodeLanguage) h += t.codeLangFontSize + 8;
3928
+ return h;
3929
+ }
3930
+ case "hr":
3931
+ return 24;
3932
+ case "blockquote":
3933
+ case "container": {
3934
+ const inner = token.tokens ?? [];
3935
+ let sum = 0;
3936
+ for (const tk of inner) sum += this.estimateBlockHeight(tk) + t.blockGap;
3937
+ return sum + t.quoteInnerGap * 2 + 8;
3938
+ }
3939
+ case "list": {
3940
+ const items = token.items ?? [];
3941
+ let sum = 0;
3942
+ for (const item of items) {
3943
+ for (const tk of item.tokens ?? []) sum += this.estimateBlockHeight(tk) + t.listItemGap;
3944
+ }
3945
+ return sum + t.listGap;
3946
+ }
3947
+ case "table": {
3948
+ const table = token;
3949
+ const rows = (table.rows?.length ?? 0) + (table.header?.length ?? 0);
3950
+ return rows * (t.tableFontSize + 16) + 16;
3951
+ }
3952
+ case "footnoteDef":
3953
+ return this.estimateTextHeight(token.text ?? "", t.bodyLineHeight);
3954
+ case "html":
3955
+ return 200;
3956
+ default:
3957
+ return t.bodyLineHeight;
3958
+ }
3959
+ }
3960
+ /** Estimate a wrapped line count from raw character length against the max width. */
3961
+ estimateTextHeight(text, lineHeight) {
3962
+ const charsPerLine = Math.max(20, Math.floor(this.maxWidth / (this.theme.fontSize * 0.5)));
3963
+ const lines = Math.max(1, Math.ceil(text.length / charsPerLine));
3964
+ return lines * lineHeight;
3965
+ }
3966
+ /**
3967
+ * Mount the blocks in the current window and unmount the rest. The `content`
3968
+ * Stack lays the mounted window contiguously from y = 0; `content.y` is then
3969
+ * set to the skipped prefix sum so the first mounted block lands at its true
3970
+ * y — a numeric spacer, not a wrapper entity.
3971
+ */
3972
+ reconcileVirtual() {
3973
+ const tree = this.virtualHeights;
3974
+ const tokens = this.virtualTokens;
3975
+ if (!tree || !tokens) return;
3976
+ const first = tree.indexAt(Math.max(0, this.virtualScrollY - this.virtualOverscan));
3977
+ const last = tree.indexAt(
3978
+ Math.max(0, this.virtualScrollY + this.virtualViewportH + this.virtualOverscan)
3979
+ );
3980
+ let changed = false;
3981
+ for (const [idx, el] of this.virtualMounted) {
3982
+ if (idx < first || idx > last) {
3983
+ el.destroy();
3984
+ this.virtualMounted.delete(idx);
3985
+ changed = true;
3986
+ }
3987
+ }
3988
+ for (let i = first; i <= last; i++) {
3989
+ if (this.virtualMounted.has(i)) continue;
3990
+ const el = this.renderToken(tokens[i]);
3991
+ if (!el) continue;
3992
+ this.content.add(el);
3993
+ tree.set(i, el.height + this.theme.blockGap);
3994
+ this.virtualMounted.set(i, el);
3995
+ changed = true;
3996
+ }
3997
+ if (changed) this.content.layout();
3998
+ this.content.y = tree.prefix(first);
3999
+ const nextHeight = Math.max(0, tree.total() - this.theme.blockGap);
4000
+ const heightChanged = nextHeight !== this.height;
4001
+ this.width = this.content.width;
4002
+ this.height = nextHeight;
4003
+ if (heightChanged) this.notifyLayoutUpdated();
4004
+ }
3854
4005
  /** Create a frame-coalesced stream bound to this Markdown instance. */
3855
4006
  createStream(options = {}) {
4007
+ if (this.virtualizeBlocks) {
4008
+ throw new Error("Markdown.createStream is not supported when virtualize is enabled");
4009
+ }
3856
4010
  if (this.streamController) {
3857
4011
  throw new Error("Markdown already has an active StreamController");
3858
4012
  }
@@ -3925,6 +4079,18 @@ var Markdown = class _Markdown extends UIComponent3 {
3925
4079
  const next = Math.max(0, maxWidth);
3926
4080
  if (next === this.maxWidth) return this;
3927
4081
  this.maxWidth = next;
4082
+ if (this.virtualizeBlocks && this.virtualTokens && this.virtualHeights) {
4083
+ const tokens = this.virtualTokens;
4084
+ const tree = this.virtualHeights;
4085
+ for (const el of this.virtualMounted.values()) el.destroy();
4086
+ this.virtualMounted.clear();
4087
+ for (let i = 0; i < tokens.length; i++) {
4088
+ tree.set(i, this.estimateBlockHeight(tokens[i]) + this.theme.blockGap);
4089
+ }
4090
+ this.reconcileVirtual();
4091
+ this.scene?.markDirty();
4092
+ return this;
4093
+ }
3928
4094
  let childIndex = 0;
3929
4095
  const children = this.content.children;
3930
4096
  for (const token of this.tokens) {
@@ -4459,6 +4625,9 @@ var Markdown = class _Markdown extends UIComponent3 {
4459
4625
  return this.appendMarkdownCore(chunk);
4460
4626
  }
4461
4627
  appendMarkdownCore(chunk) {
4628
+ if (this.virtualizeBlocks) {
4629
+ throw new Error("Markdown.appendMarkdown is not supported when virtualize is enabled");
4630
+ }
4462
4631
  const before = this.rawMarkdown.length;
4463
4632
  this.consumeFrontMatter(chunk);
4464
4633
  this.streamStats.appends++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectojs/markdown",
3
- "version": "0.20.3",
3
+ "version": "0.21.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },