@stream-mdx/core 0.1.0 → 0.2.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.
@@ -1,4 +1,4 @@
1
- import { InlineNode, MixedContentSegment } from './types.js';
1
+ import { ProtectedRange, InlineNode, MixedContentSegment } from './types.js';
2
2
 
3
3
  interface MixedContentAutoCloseHtmlOptions {
4
4
  autoClose?: boolean;
@@ -13,6 +13,8 @@ interface MixedContentAutoCloseMdxOptions {
13
13
  interface MixedContentOptions {
14
14
  html?: MixedContentAutoCloseHtmlOptions;
15
15
  mdx?: MixedContentAutoCloseMdxOptions;
16
+ protectedRanges?: ReadonlyArray<ProtectedRange>;
17
+ protectedRangeKinds?: ReadonlyArray<ProtectedRange["kind"]>;
16
18
  }
17
19
  declare function extractMixedContentSegments(raw: string, baseOffset: number | undefined, parseInline: (content: string) => InlineNode[], options?: MixedContentOptions): MixedContentSegment[];
18
20
  declare function isLikelyMdxComponent(tagName: string): boolean;
@@ -179,6 +179,8 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
179
179
  const mdxAutoClose = options?.mdx?.autoClose === true;
180
180
  const mdxMaxNewlines = normalizeNewlineLimit(options?.mdx?.maxNewlines);
181
181
  const mdxAllowlist = normalizeComponentAllowlist(options?.mdx?.componentAllowlist);
182
+ const protectedRanges = options?.protectedRanges ?? [];
183
+ const protectedKinds = protectedRanges.length ? new Set(options?.protectedRangeKinds ?? ["math-inline", "math-display", "code-inline", "code-block", "autolink"]) : null;
182
184
  while (match !== null) {
183
185
  const start = match.index;
184
186
  const tagName = match[1];
@@ -193,6 +195,18 @@ function splitByTagSegments(source, baseOffset, parseInline, options) {
193
195
  continue;
194
196
  }
195
197
  let end = tagPattern.lastIndex;
198
+ if (protectedKinds && protectedRanges.length > 0) {
199
+ const absoluteStart = baseIsFinite ? baseOffset + start : start;
200
+ const absoluteEnd = baseIsFinite ? baseOffset + end : end;
201
+ const covered = protectedRanges.some(
202
+ (range) => protectedKinds.has(range.kind) && typeof range.from === "number" && typeof range.to === "number" && range.from <= absoluteStart && range.to >= absoluteEnd
203
+ );
204
+ if (covered) {
205
+ tagPattern.lastIndex = start + 1;
206
+ match = tagPattern.exec(source);
207
+ continue;
208
+ }
209
+ }
196
210
  if (!isSelfClosing && !mdxAllowed) {
197
211
  const closingIndex = findClosingHtmlTag(lowerSource, tagName.toLowerCase(), end);
198
212
  if (closingIndex === -1) {
@@ -140,6 +140,14 @@ function mergeAppendLines(window, startIndex) {
140
140
  }
141
141
  const lines = [...base.lines ?? []];
142
142
  const highlight = Array.isArray(base.highlight) ? [...base.highlight] : [];
143
+ const tokens = Array.isArray(base.tokens) ? [...base.tokens] : [];
144
+ const diffKind = Array.isArray(base.diffKind) ? [...base.diffKind] : [];
145
+ const oldNo = Array.isArray(base.oldNo) ? [...base.oldNo] : [];
146
+ const newNo = Array.isArray(base.newNo) ? [...base.newNo] : [];
147
+ let includeTokens = Array.isArray(base.tokens);
148
+ let includeDiffKind = Array.isArray(base.diffKind);
149
+ let includeOldNo = Array.isArray(base.oldNo);
150
+ let includeNewNo = Array.isArray(base.newNo);
143
151
  const baseStart = base.startIndex;
144
152
  let expectedStart = baseStart + lines.length;
145
153
  let j = startIndex + 1;
@@ -147,6 +155,7 @@ function mergeAppendLines(window, startIndex) {
147
155
  while (j < window.length && mergedCount < APPEND_MERGE_LIMIT) {
148
156
  const next = window[j];
149
157
  if (next.op === "appendLines" && next.at.blockId === base.at.blockId && next.at.nodeId === base.at.nodeId && typeof next.startIndex === "number" && next.startIndex === expectedStart) {
158
+ const priorLineCount = lines.length;
150
159
  lines.push(...next.lines ?? []);
151
160
  const nextHighlights = Array.isArray(next.highlight) ? next.highlight : [];
152
161
  const appendedCount = next.lines?.length ?? 0;
@@ -160,6 +169,50 @@ function mergeAppendLines(window, startIndex) {
160
169
  highlight.push(null);
161
170
  }
162
171
  }
172
+ if (Array.isArray(next.tokens)) {
173
+ if (!includeTokens) {
174
+ tokens.push(...new Array(priorLineCount).fill(null));
175
+ includeTokens = true;
176
+ }
177
+ for (let idx = 0; idx < appendedCount; idx++) {
178
+ tokens.push(idx < next.tokens.length ? next.tokens[idx] ?? null : null);
179
+ }
180
+ } else if (includeTokens) {
181
+ tokens.push(...new Array(appendedCount).fill(null));
182
+ }
183
+ if (Array.isArray(next.diffKind)) {
184
+ if (!includeDiffKind) {
185
+ diffKind.push(...new Array(priorLineCount).fill(null));
186
+ includeDiffKind = true;
187
+ }
188
+ for (let idx = 0; idx < appendedCount; idx++) {
189
+ diffKind.push(idx < next.diffKind.length ? next.diffKind[idx] ?? null : null);
190
+ }
191
+ } else if (includeDiffKind) {
192
+ diffKind.push(...new Array(appendedCount).fill(null));
193
+ }
194
+ if (Array.isArray(next.oldNo)) {
195
+ if (!includeOldNo) {
196
+ oldNo.push(...new Array(priorLineCount).fill(null));
197
+ includeOldNo = true;
198
+ }
199
+ for (let idx = 0; idx < appendedCount; idx++) {
200
+ oldNo.push(idx < next.oldNo.length ? next.oldNo[idx] ?? null : null);
201
+ }
202
+ } else if (includeOldNo) {
203
+ oldNo.push(...new Array(appendedCount).fill(null));
204
+ }
205
+ if (Array.isArray(next.newNo)) {
206
+ if (!includeNewNo) {
207
+ newNo.push(...new Array(priorLineCount).fill(null));
208
+ includeNewNo = true;
209
+ }
210
+ for (let idx = 0; idx < appendedCount; idx++) {
211
+ newNo.push(idx < next.newNo.length ? next.newNo[idx] ?? null : null);
212
+ }
213
+ } else if (includeNewNo) {
214
+ newNo.push(...new Array(appendedCount).fill(null));
215
+ }
163
216
  expectedStart = baseStart + lines.length;
164
217
  mergedCount++;
165
218
  j++;
@@ -170,7 +223,11 @@ function mergeAppendLines(window, startIndex) {
170
223
  const combined = {
171
224
  ...base,
172
225
  lines,
173
- highlight: highlight.length > 0 ? highlight : void 0
226
+ highlight: highlight.length > 0 ? highlight : void 0,
227
+ tokens: includeTokens && tokens.length > 0 ? tokens : void 0,
228
+ diffKind: includeDiffKind && diffKind.length > 0 ? diffKind : void 0,
229
+ oldNo: includeOldNo && oldNo.length > 0 ? oldNo : void 0,
230
+ newNo: includeNewNo && newNo.length > 0 ? newNo : void 0
174
231
  };
175
232
  return { patch: combined, nextIndex: j };
176
233
  }
@@ -112,6 +112,14 @@ function mergeAppendLines(window, startIndex) {
112
112
  }
113
113
  const lines = [...base.lines ?? []];
114
114
  const highlight = Array.isArray(base.highlight) ? [...base.highlight] : [];
115
+ const tokens = Array.isArray(base.tokens) ? [...base.tokens] : [];
116
+ const diffKind = Array.isArray(base.diffKind) ? [...base.diffKind] : [];
117
+ const oldNo = Array.isArray(base.oldNo) ? [...base.oldNo] : [];
118
+ const newNo = Array.isArray(base.newNo) ? [...base.newNo] : [];
119
+ let includeTokens = Array.isArray(base.tokens);
120
+ let includeDiffKind = Array.isArray(base.diffKind);
121
+ let includeOldNo = Array.isArray(base.oldNo);
122
+ let includeNewNo = Array.isArray(base.newNo);
115
123
  const baseStart = base.startIndex;
116
124
  let expectedStart = baseStart + lines.length;
117
125
  let j = startIndex + 1;
@@ -119,6 +127,7 @@ function mergeAppendLines(window, startIndex) {
119
127
  while (j < window.length && mergedCount < APPEND_MERGE_LIMIT) {
120
128
  const next = window[j];
121
129
  if (next.op === "appendLines" && next.at.blockId === base.at.blockId && next.at.nodeId === base.at.nodeId && typeof next.startIndex === "number" && next.startIndex === expectedStart) {
130
+ const priorLineCount = lines.length;
122
131
  lines.push(...next.lines ?? []);
123
132
  const nextHighlights = Array.isArray(next.highlight) ? next.highlight : [];
124
133
  const appendedCount = next.lines?.length ?? 0;
@@ -132,6 +141,50 @@ function mergeAppendLines(window, startIndex) {
132
141
  highlight.push(null);
133
142
  }
134
143
  }
144
+ if (Array.isArray(next.tokens)) {
145
+ if (!includeTokens) {
146
+ tokens.push(...new Array(priorLineCount).fill(null));
147
+ includeTokens = true;
148
+ }
149
+ for (let idx = 0; idx < appendedCount; idx++) {
150
+ tokens.push(idx < next.tokens.length ? next.tokens[idx] ?? null : null);
151
+ }
152
+ } else if (includeTokens) {
153
+ tokens.push(...new Array(appendedCount).fill(null));
154
+ }
155
+ if (Array.isArray(next.diffKind)) {
156
+ if (!includeDiffKind) {
157
+ diffKind.push(...new Array(priorLineCount).fill(null));
158
+ includeDiffKind = true;
159
+ }
160
+ for (let idx = 0; idx < appendedCount; idx++) {
161
+ diffKind.push(idx < next.diffKind.length ? next.diffKind[idx] ?? null : null);
162
+ }
163
+ } else if (includeDiffKind) {
164
+ diffKind.push(...new Array(appendedCount).fill(null));
165
+ }
166
+ if (Array.isArray(next.oldNo)) {
167
+ if (!includeOldNo) {
168
+ oldNo.push(...new Array(priorLineCount).fill(null));
169
+ includeOldNo = true;
170
+ }
171
+ for (let idx = 0; idx < appendedCount; idx++) {
172
+ oldNo.push(idx < next.oldNo.length ? next.oldNo[idx] ?? null : null);
173
+ }
174
+ } else if (includeOldNo) {
175
+ oldNo.push(...new Array(appendedCount).fill(null));
176
+ }
177
+ if (Array.isArray(next.newNo)) {
178
+ if (!includeNewNo) {
179
+ newNo.push(...new Array(priorLineCount).fill(null));
180
+ includeNewNo = true;
181
+ }
182
+ for (let idx = 0; idx < appendedCount; idx++) {
183
+ newNo.push(idx < next.newNo.length ? next.newNo[idx] ?? null : null);
184
+ }
185
+ } else if (includeNewNo) {
186
+ newNo.push(...new Array(appendedCount).fill(null));
187
+ }
135
188
  expectedStart = baseStart + lines.length;
136
189
  mergedCount++;
137
190
  j++;
@@ -142,7 +195,11 @@ function mergeAppendLines(window, startIndex) {
142
195
  const combined = {
143
196
  ...base,
144
197
  lines,
145
- highlight: highlight.length > 0 ? highlight : void 0
198
+ highlight: highlight.length > 0 ? highlight : void 0,
199
+ tokens: includeTokens && tokens.length > 0 ? tokens : void 0,
200
+ diffKind: includeDiffKind && diffKind.length > 0 ? diffKind : void 0,
201
+ oldNo: includeOldNo && oldNo.length > 0 ? oldNo : void 0,
202
+ newNo: includeNewNo && newNo.length > 0 ? newNo : void 0
146
203
  };
147
204
  return { patch: combined, nextIndex: j };
148
205
  }
package/dist/types.d.cts CHANGED
@@ -47,6 +47,8 @@ type FormatAnticipationConfig = boolean | {
47
47
  mdx?: boolean;
48
48
  regex?: boolean;
49
49
  };
50
+ type CodeHighlightingMode = "final" | "incremental" | "live";
51
+ type CodeHighlightOutputMode = "html" | "tokens" | "both";
50
52
  interface InlineHtmlDescriptor {
51
53
  tagName: string;
52
54
  attributes: Record<string, string>;
@@ -109,6 +111,24 @@ type InlineNode = {
109
111
  label: string;
110
112
  number?: number;
111
113
  };
114
+ type TokenStyle = {
115
+ fg?: string;
116
+ bg?: string;
117
+ fs?: number;
118
+ };
119
+ type TokenSpan = {
120
+ t: string;
121
+ v?: {
122
+ dark?: TokenStyle;
123
+ light?: TokenStyle;
124
+ };
125
+ s?: TokenStyle;
126
+ };
127
+ type TokenLineV1 = {
128
+ spans: TokenSpan[];
129
+ };
130
+ type DiffKind = "add" | "remove" | "context" | "hunk" | "meta";
131
+ type LazyTokenizationPriority = "visible" | "prefetch";
112
132
  /**
113
133
  * Worker communication protocol
114
134
  */
@@ -124,8 +144,14 @@ type WorkerIn = {
124
144
  callouts?: boolean;
125
145
  math?: boolean;
126
146
  formatAnticipation?: FormatAnticipationConfig;
147
+ codeHighlighting?: CodeHighlightingMode;
148
+ outputMode?: CodeHighlightOutputMode;
127
149
  liveCodeHighlighting?: boolean;
128
150
  mdxComponentNames?: string[];
151
+ lazyTokenization?: {
152
+ enabled?: boolean;
153
+ thresholdLines?: number;
154
+ };
129
155
  };
130
156
  mdx?: {
131
157
  compileMode?: "server" | "worker";
@@ -135,6 +161,14 @@ type WorkerIn = {
135
161
  text: string;
136
162
  } | {
137
163
  type: "FINALIZE";
164
+ } | {
165
+ type: "DEBUG_STATE";
166
+ } | {
167
+ type: "TOKENIZE_RANGE";
168
+ blockId: string;
169
+ startLine: number;
170
+ endLine: number;
171
+ priority?: LazyTokenizationPriority;
138
172
  } | {
139
173
  type: "MDX_COMPILED";
140
174
  blockId: string;
@@ -167,6 +201,23 @@ type WorkerOut = {
167
201
  } | {
168
202
  type: "METRICS";
169
203
  metrics: PerformanceMetrics;
204
+ } | {
205
+ type: "DEBUG_STATE";
206
+ state: {
207
+ contentLength: number;
208
+ contentTail: string;
209
+ blockCount: number;
210
+ blockTypeCounts: Record<string, number>;
211
+ lastBlockType?: string;
212
+ lastBlockRange?: {
213
+ from: number;
214
+ to: number;
215
+ };
216
+ lastBlockRawTail?: string;
217
+ hasInlineCodeHeading: boolean;
218
+ hasCodeBlocksHeading: boolean;
219
+ hasMediaHeading: boolean;
220
+ };
170
221
  } | {
171
222
  type: "ERROR";
172
223
  phase: WorkerPhase;
@@ -241,6 +292,14 @@ interface PerformanceMetrics {
241
292
  appendLineBatches?: number;
242
293
  appendLineTotalLines?: number;
243
294
  appendLineMaxLines?: number;
295
+ lazyTokenization?: {
296
+ requests: number;
297
+ avgRangeLines: number;
298
+ maxRangeLines: number;
299
+ avgLatencyMs: number;
300
+ maxLatencyMs: number;
301
+ maxQueue: number;
302
+ };
244
303
  }
245
304
  /**
246
305
  * Incremental patch protocol (initial block-level implementation).
@@ -299,6 +358,10 @@ type Patch = {
299
358
  startIndex: number;
300
359
  lines: string[];
301
360
  highlight?: Array<string | null>;
361
+ tokens?: Array<TokenLineV1 | null>;
362
+ diffKind?: Array<DiffKind | null>;
363
+ oldNo?: Array<number | null>;
364
+ newNo?: Array<number | null>;
302
365
  } | {
303
366
  op: "setHTML";
304
367
  at: NodePath;
@@ -334,4 +397,4 @@ interface CoalescingMetrics {
334
397
  insertChildCoalesced: number;
335
398
  }
336
399
 
337
- export { type ASTInlinePlugin, type Block, type CoalescingMetrics, type CompiledMdxModule, type FormatAnticipationConfig, type InlineHtmlDescriptor, type InlineNode, type InlinePlugin, LANGUAGE_ALIASES, type MixedContentSegment, type NodePath, type NodeSnapshot, PATCH_ROOT_ID, type Patch, type PatchMetrics, type PerformanceMetrics, type ProtectedRange, type ProtectedRangeKind, type RegexAnticipationPattern, type RegexInlinePlugin, type SetPropsBatchEntry, type WorkerErrorPayload, type WorkerIn, type WorkerOut, type WorkerPhase };
400
+ export { type ASTInlinePlugin, type Block, type CoalescingMetrics, type CodeHighlightOutputMode, type CodeHighlightingMode, type CompiledMdxModule, type DiffKind, type FormatAnticipationConfig, type InlineHtmlDescriptor, type InlineNode, type InlinePlugin, LANGUAGE_ALIASES, type LazyTokenizationPriority, type MixedContentSegment, type NodePath, type NodeSnapshot, PATCH_ROOT_ID, type Patch, type PatchMetrics, type PerformanceMetrics, type ProtectedRange, type ProtectedRangeKind, type RegexAnticipationPattern, type RegexInlinePlugin, type SetPropsBatchEntry, type TokenLineV1, type TokenSpan, type TokenStyle, type WorkerErrorPayload, type WorkerIn, type WorkerOut, type WorkerPhase };
package/dist/types.d.ts CHANGED
@@ -47,6 +47,8 @@ type FormatAnticipationConfig = boolean | {
47
47
  mdx?: boolean;
48
48
  regex?: boolean;
49
49
  };
50
+ type CodeHighlightingMode = "final" | "incremental" | "live";
51
+ type CodeHighlightOutputMode = "html" | "tokens" | "both";
50
52
  interface InlineHtmlDescriptor {
51
53
  tagName: string;
52
54
  attributes: Record<string, string>;
@@ -109,6 +111,24 @@ type InlineNode = {
109
111
  label: string;
110
112
  number?: number;
111
113
  };
114
+ type TokenStyle = {
115
+ fg?: string;
116
+ bg?: string;
117
+ fs?: number;
118
+ };
119
+ type TokenSpan = {
120
+ t: string;
121
+ v?: {
122
+ dark?: TokenStyle;
123
+ light?: TokenStyle;
124
+ };
125
+ s?: TokenStyle;
126
+ };
127
+ type TokenLineV1 = {
128
+ spans: TokenSpan[];
129
+ };
130
+ type DiffKind = "add" | "remove" | "context" | "hunk" | "meta";
131
+ type LazyTokenizationPriority = "visible" | "prefetch";
112
132
  /**
113
133
  * Worker communication protocol
114
134
  */
@@ -124,8 +144,14 @@ type WorkerIn = {
124
144
  callouts?: boolean;
125
145
  math?: boolean;
126
146
  formatAnticipation?: FormatAnticipationConfig;
147
+ codeHighlighting?: CodeHighlightingMode;
148
+ outputMode?: CodeHighlightOutputMode;
127
149
  liveCodeHighlighting?: boolean;
128
150
  mdxComponentNames?: string[];
151
+ lazyTokenization?: {
152
+ enabled?: boolean;
153
+ thresholdLines?: number;
154
+ };
129
155
  };
130
156
  mdx?: {
131
157
  compileMode?: "server" | "worker";
@@ -135,6 +161,14 @@ type WorkerIn = {
135
161
  text: string;
136
162
  } | {
137
163
  type: "FINALIZE";
164
+ } | {
165
+ type: "DEBUG_STATE";
166
+ } | {
167
+ type: "TOKENIZE_RANGE";
168
+ blockId: string;
169
+ startLine: number;
170
+ endLine: number;
171
+ priority?: LazyTokenizationPriority;
138
172
  } | {
139
173
  type: "MDX_COMPILED";
140
174
  blockId: string;
@@ -167,6 +201,23 @@ type WorkerOut = {
167
201
  } | {
168
202
  type: "METRICS";
169
203
  metrics: PerformanceMetrics;
204
+ } | {
205
+ type: "DEBUG_STATE";
206
+ state: {
207
+ contentLength: number;
208
+ contentTail: string;
209
+ blockCount: number;
210
+ blockTypeCounts: Record<string, number>;
211
+ lastBlockType?: string;
212
+ lastBlockRange?: {
213
+ from: number;
214
+ to: number;
215
+ };
216
+ lastBlockRawTail?: string;
217
+ hasInlineCodeHeading: boolean;
218
+ hasCodeBlocksHeading: boolean;
219
+ hasMediaHeading: boolean;
220
+ };
170
221
  } | {
171
222
  type: "ERROR";
172
223
  phase: WorkerPhase;
@@ -241,6 +292,14 @@ interface PerformanceMetrics {
241
292
  appendLineBatches?: number;
242
293
  appendLineTotalLines?: number;
243
294
  appendLineMaxLines?: number;
295
+ lazyTokenization?: {
296
+ requests: number;
297
+ avgRangeLines: number;
298
+ maxRangeLines: number;
299
+ avgLatencyMs: number;
300
+ maxLatencyMs: number;
301
+ maxQueue: number;
302
+ };
244
303
  }
245
304
  /**
246
305
  * Incremental patch protocol (initial block-level implementation).
@@ -299,6 +358,10 @@ type Patch = {
299
358
  startIndex: number;
300
359
  lines: string[];
301
360
  highlight?: Array<string | null>;
361
+ tokens?: Array<TokenLineV1 | null>;
362
+ diffKind?: Array<DiffKind | null>;
363
+ oldNo?: Array<number | null>;
364
+ newNo?: Array<number | null>;
302
365
  } | {
303
366
  op: "setHTML";
304
367
  at: NodePath;
@@ -334,4 +397,4 @@ interface CoalescingMetrics {
334
397
  insertChildCoalesced: number;
335
398
  }
336
399
 
337
- export { type ASTInlinePlugin, type Block, type CoalescingMetrics, type CompiledMdxModule, type FormatAnticipationConfig, type InlineHtmlDescriptor, type InlineNode, type InlinePlugin, LANGUAGE_ALIASES, type MixedContentSegment, type NodePath, type NodeSnapshot, PATCH_ROOT_ID, type Patch, type PatchMetrics, type PerformanceMetrics, type ProtectedRange, type ProtectedRangeKind, type RegexAnticipationPattern, type RegexInlinePlugin, type SetPropsBatchEntry, type WorkerErrorPayload, type WorkerIn, type WorkerOut, type WorkerPhase };
400
+ export { type ASTInlinePlugin, type Block, type CoalescingMetrics, type CodeHighlightOutputMode, type CodeHighlightingMode, type CompiledMdxModule, type DiffKind, type FormatAnticipationConfig, type InlineHtmlDescriptor, type InlineNode, type InlinePlugin, LANGUAGE_ALIASES, type LazyTokenizationPriority, type MixedContentSegment, type NodePath, type NodeSnapshot, PATCH_ROOT_ID, type Patch, type PatchMetrics, type PerformanceMetrics, type ProtectedRange, type ProtectedRangeKind, type RegexAnticipationPattern, type RegexInlinePlugin, type SetPropsBatchEntry, type TokenLineV1, type TokenSpan, type TokenStyle, type WorkerErrorPayload, type WorkerIn, type WorkerOut, type WorkerPhase };
package/dist/utils.cjs CHANGED
@@ -91,23 +91,33 @@ function getBlockKey(block) {
91
91
  }
92
92
  function detectMDX(content, options) {
93
93
  const inlineCodeRanges = collectInlineCodeRanges(content);
94
+ const protectedRanges = options?.protectedRanges ?? [];
95
+ const baseOffset = typeof options?.baseOffset === "number" ? options.baseOffset : 0;
96
+ const protectedKinds = protectedRanges.length ? /* @__PURE__ */ new Set(["math-inline", "math-display", "code-inline", "code-block", "html-inline", "html-block", "autolink"]) : null;
94
97
  const componentPattern = /<([A-Z][\w-]*)(\s|\/?>)/g;
95
98
  let componentMatch = componentPattern.exec(content);
96
99
  while (componentMatch !== null) {
97
100
  const start = componentMatch.index;
98
101
  const end = start + componentMatch[0].length;
99
- if (!isWithinRanges(start, end, inlineCodeRanges)) {
100
- return true;
102
+ if (isWithinRanges(start, end, inlineCodeRanges)) {
103
+ componentMatch = componentPattern.exec(content);
104
+ continue;
105
+ }
106
+ if (protectedKinds) {
107
+ const absoluteStart = baseOffset + start;
108
+ const absoluteEnd = baseOffset + end;
109
+ const covered = protectedRanges.some((range) => protectedKinds.has(range.kind) && range.from <= absoluteStart && range.to >= absoluteEnd);
110
+ if (covered) {
111
+ componentMatch = componentPattern.exec(content);
112
+ continue;
113
+ }
101
114
  }
102
- componentMatch = componentPattern.exec(content);
115
+ return true;
103
116
  }
104
117
  if (/(^|\n)\s*(import|export)\s/.test(content)) {
105
118
  return true;
106
119
  }
107
120
  const expressionPattern = /\{[^{}]+\}/g;
108
- const protectedRanges = options?.protectedRanges ?? [];
109
- const baseOffset = typeof options?.baseOffset === "number" ? options.baseOffset : 0;
110
- const protectedKinds = protectedRanges.length ? /* @__PURE__ */ new Set(["math-inline", "math-display", "code-inline", "code-block"]) : null;
111
121
  for (let match = expressionPattern.exec(content); match !== null; match = expressionPattern.exec(content)) {
112
122
  const index = match.index;
113
123
  const prev = index > 0 ? content[index - 1] : "";
package/dist/utils.mjs CHANGED
@@ -55,23 +55,33 @@ function getBlockKey(block) {
55
55
  }
56
56
  function detectMDX(content, options) {
57
57
  const inlineCodeRanges = collectInlineCodeRanges(content);
58
+ const protectedRanges = options?.protectedRanges ?? [];
59
+ const baseOffset = typeof options?.baseOffset === "number" ? options.baseOffset : 0;
60
+ const protectedKinds = protectedRanges.length ? /* @__PURE__ */ new Set(["math-inline", "math-display", "code-inline", "code-block", "html-inline", "html-block", "autolink"]) : null;
58
61
  const componentPattern = /<([A-Z][\w-]*)(\s|\/?>)/g;
59
62
  let componentMatch = componentPattern.exec(content);
60
63
  while (componentMatch !== null) {
61
64
  const start = componentMatch.index;
62
65
  const end = start + componentMatch[0].length;
63
- if (!isWithinRanges(start, end, inlineCodeRanges)) {
64
- return true;
66
+ if (isWithinRanges(start, end, inlineCodeRanges)) {
67
+ componentMatch = componentPattern.exec(content);
68
+ continue;
69
+ }
70
+ if (protectedKinds) {
71
+ const absoluteStart = baseOffset + start;
72
+ const absoluteEnd = baseOffset + end;
73
+ const covered = protectedRanges.some((range) => protectedKinds.has(range.kind) && range.from <= absoluteStart && range.to >= absoluteEnd);
74
+ if (covered) {
75
+ componentMatch = componentPattern.exec(content);
76
+ continue;
77
+ }
65
78
  }
66
- componentMatch = componentPattern.exec(content);
79
+ return true;
67
80
  }
68
81
  if (/(^|\n)\s*(import|export)\s/.test(content)) {
69
82
  return true;
70
83
  }
71
84
  const expressionPattern = /\{[^{}]+\}/g;
72
- const protectedRanges = options?.protectedRanges ?? [];
73
- const baseOffset = typeof options?.baseOffset === "number" ? options.baseOffset : 0;
74
- const protectedKinds = protectedRanges.length ? /* @__PURE__ */ new Set(["math-inline", "math-display", "code-inline", "code-block"]) : null;
75
85
  for (let match = expressionPattern.exec(content); match !== null; match = expressionPattern.exec(content)) {
76
86
  const index = match.index;
77
87
  const prev = index > 0 ? content[index - 1] : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stream-mdx/core",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Core types, snapshot utilities, and perf helpers for the Streaming Markdown V2 stack",
5
5
  "license": "MIT",
6
6
  "repository": {