@mui/internal-docs-infra 0.4.1-canary.9 → 0.6.1-canary.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/cli/index.mjs CHANGED
@@ -7,4 +7,4 @@ function getVersion() {
7
7
  }
8
8
  yargs().scriptName('docs-infra').usage('$0 <command> [args]').command(runValidate).demandCommand(1, 'You need at least one command before moving on').strict().help()
9
9
  // MUI_VERSION is set through the code-infra build command.
10
- .version("0.4.0" || getVersion()).parse(hideBin(process.argv));
10
+ .version("0.6.0" || getVersion()).parse(hideBin(process.argv));
@@ -9,7 +9,7 @@ function isNextJsContext() {
9
9
  typeof process.env.__NEXT_PROCESSED_ENV === 'string' ||
10
10
  // Next.js build
11
11
  typeof process.env.NEXT_PHASE === 'string') // Next.js build phase
12
- ;
12
+ ;
13
13
  }
14
14
 
15
15
  /**
@@ -1,3 +1,4 @@
1
+ import type { Metadata } from 'next';
1
2
  /**
2
3
  * Section data structure from sitemap
3
4
  */
@@ -35,6 +36,8 @@ export interface SitemapPage {
35
36
  exports?: Record<string, SitemapExport>;
36
37
  tags?: string[];
37
38
  skipDetailSection?: boolean;
39
+ audience?: Audience;
40
+ index?: boolean;
38
41
  image?: {
39
42
  url: string;
40
43
  alt?: string;
@@ -59,4 +62,36 @@ export type OramaSchemaType = 'string' | 'number' | 'boolean' | 'string[]' | 'nu
59
62
  export interface Sitemap {
60
63
  schema: Record<string, OramaSchemaType>;
61
64
  data: Record<string, SitemapSectionData>;
62
- }
65
+ }
66
+ export type Audience = 'private' | 'introductory' | 'intermediate' | 'advanced' | 'business';
67
+ /**
68
+ * Page metadata type extending Next.js `Metadata`.
69
+ *
70
+ * Adds the `audience` field under `other` using the WHATWG MetaExtensions `audience` meta name.
71
+ * All standard Next.js metadata fields (title, description, openGraph, etc.) remain available.
72
+ *
73
+ * @see https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadata-fields
74
+ */
75
+ export type NextMetadata = Metadata & {
76
+ other?: {
77
+ /**
78
+ * Categorize the principal intended audience for the page.
79
+ * Uses the WHATWG MetaExtensions `audience` meta name.
80
+ *
81
+ * When omitted, the page is public and intended for all audiences.
82
+ *
83
+ * - `'private'`: Internal page, not intended for public consumption.
84
+ * Should be paired with `robots: { index: false }` to exclude from public indexing.
85
+ * - `'introductory'`: Content aimed at beginners.
86
+ * - `'intermediate'`: Content aimed at intermediate users.
87
+ * - `'advanced'`: Content aimed at advanced users.
88
+ * - `'business'`: Content aimed at prospective customers and decision-makers
89
+ * (e.g. marketing pages, pricing, product overviews).
90
+ *
91
+ * @see https://wiki.whatwg.org/wiki/MetaExtensions
92
+ * @see https://brittlebit.org/specifications/html-meta-audience/specification-for-html-meta-element-with-name-value-audience.html
93
+ */
94
+ audience?: Audience;
95
+ [key: string]: unknown;
96
+ };
97
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mui/internal-docs-infra",
3
- "version": "0.4.1-canary.9",
3
+ "version": "0.6.1-canary.0",
4
4
  "author": "MUI Team",
5
5
  "description": "MUI Infra - internal documentation creation tools.",
6
6
  "keywords": [
@@ -22,12 +22,12 @@
22
22
  "homepage": "https://github.com/mui/mui-public/tree/master/packages/docs-infra",
23
23
  "dependencies": {
24
24
  "@babel/runtime": "^7.28.6",
25
- "@babel/standalone": "^7.28.6",
25
+ "@babel/standalone": "^7.29.1",
26
26
  "@orama/orama": "^3.1.18",
27
27
  "@orama/plugin-qps": "^3.1.18",
28
28
  "@orama/stemmers": "^3.1.18",
29
29
  "@orama/stopwords": "^3.1.18",
30
- "@wooorm/starry-night": "^3.8.0",
30
+ "@wooorm/starry-night": "^3.9.0",
31
31
  "chalk": "^5.6.2",
32
32
  "clipboard-copy": "^4.0.1",
33
33
  "fflate": "^0.8.2",
@@ -452,6 +452,16 @@
452
452
  "default": "./pipeline/loadServerSource/index.mjs"
453
453
  }
454
454
  },
455
+ "./pipeline/enhanceCodeEmphasis": {
456
+ "import": {
457
+ "types": "./pipeline/enhanceCodeEmphasis/index.d.mts",
458
+ "default": "./pipeline/enhanceCodeEmphasis/index.mjs"
459
+ },
460
+ "default": {
461
+ "types": "./pipeline/enhanceCodeEmphasis/index.d.mts",
462
+ "default": "./pipeline/enhanceCodeEmphasis/index.mjs"
463
+ }
464
+ },
455
465
  "./pipeline/parseSource": {
456
466
  "import": {
457
467
  "types": "./pipeline/parseSource/index.d.mts",
@@ -506,5 +516,5 @@
506
516
  "bin": {
507
517
  "docs-infra": "./cli/index.mjs"
508
518
  },
509
- "gitSha": "5a397056f9e93982f8e275c0214fc882d8af3b1c"
519
+ "gitSha": "cad146f1682e0cb63baeeab28454423d77ef7c89"
510
520
  }
@@ -0,0 +1,12 @@
1
+ import type { Element } from 'hast';
2
+ /**
3
+ * Calculates the shared indent level for a set of line elements.
4
+ *
5
+ * Finds the minimum leading whitespace across all non-empty lines,
6
+ * then divides by the indent size (2 spaces) and floors to get
7
+ * the indent level.
8
+ *
9
+ * @param lineElements - Array of HAST line elements to analyze
10
+ * @returns The shared indent level (e.g., 2 for 4 leading spaces with 2-space indent)
11
+ */
12
+ export declare function calculateFrameIndent(lineElements: Element[]): number;
@@ -0,0 +1,62 @@
1
+ const INDENT_SIZE = 2;
2
+
3
+ /**
4
+ * Counts leading spaces in an element by walking the HAST tree.
5
+ *
6
+ * Returns the number of leading space characters before the first
7
+ * non-space character, or -1 if the line is empty/whitespace-only.
8
+ * Only counts space characters. Tab indentation is not supported
9
+ * since the input is HAST output from starry-night which uses spaces.
10
+ */
11
+ function countLeadingSpaces(element) {
12
+ let spaces = 0;
13
+ function walk(node) {
14
+ for (const child of node.children) {
15
+ if (child.type === 'text') {
16
+ for (const char of child.value) {
17
+ if (char === ' ') {
18
+ spaces += 1;
19
+ } else {
20
+ return true;
21
+ }
22
+ }
23
+ } else if (child.type === 'element') {
24
+ if (walk(child)) {
25
+ return true;
26
+ }
27
+ }
28
+ }
29
+ return false;
30
+ }
31
+ const foundNonSpace = walk(element);
32
+ return foundNonSpace ? spaces : -1;
33
+ }
34
+
35
+ /**
36
+ * Calculates the shared indent level for a set of line elements.
37
+ *
38
+ * Finds the minimum leading whitespace across all non-empty lines,
39
+ * then divides by the indent size (2 spaces) and floors to get
40
+ * the indent level.
41
+ *
42
+ * @param lineElements - Array of HAST line elements to analyze
43
+ * @returns The shared indent level (e.g., 2 for 4 leading spaces with 2-space indent)
44
+ */
45
+ export function calculateFrameIndent(lineElements) {
46
+ let minLeadingSpaces = Infinity;
47
+ for (const element of lineElements) {
48
+ const leadingSpaces = countLeadingSpaces(element);
49
+
50
+ // Skip empty/whitespace-only lines
51
+ if (leadingSpaces === -1) {
52
+ continue;
53
+ }
54
+ if (leadingSpaces < minLeadingSpaces) {
55
+ minLeadingSpaces = leadingSpaces;
56
+ }
57
+ }
58
+ if (minLeadingSpaces === Infinity) {
59
+ return 0;
60
+ }
61
+ return Math.floor(minLeadingSpaces / INDENT_SIZE);
62
+ }
@@ -1,13 +1,16 @@
1
1
  import type { SourceEnhancer } from "../../CodeHighlighter/types.mjs";
2
+ import type { EnhanceCodeEmphasisOptions } from "../parseSource/calculateFrameRanges.mjs";
3
+ export type { EmphasisMeta, EnhanceCodeEmphasisOptions, FrameRange } from "../parseSource/calculateFrameRanges.mjs";
2
4
  /**
3
5
  * The prefix used to identify emphasis comments in source code.
4
6
  * Comments starting with this prefix will be processed for emphasis.
5
7
  */
6
8
  export declare const EMPHASIS_COMMENT_PREFIX = "@highlight";
7
9
  /**
8
- * Source enhancer that adds emphasis to code lines based on `@highlight` comments.
10
+ * Creates a source enhancer that adds emphasis to code lines based on `@highlight` comments
11
+ * and restructures frames around highlighted regions.
9
12
  *
10
- * Supports four patterns:
13
+ * Supports five patterns:
11
14
  *
12
15
  * 1. **Single line emphasis** - emphasizes the line containing the comment:
13
16
  * ```jsx
@@ -37,12 +40,30 @@ export declare const EMPHASIS_COMMENT_PREFIX = "@highlight";
37
40
  * <h1>Heading 1</h1> {/* @highlight-text "Heading 1" *\/}
38
41
  * ```
39
42
  *
43
+ * 5. **Focus override** - mark a region for padding focus:
44
+ * ```jsx
45
+ * <h1>Heading 1</h1> {/* @highlight @focus *\/}
46
+ * ```
47
+ *
40
48
  * Emphasized lines receive a `data-hl` attribute on their `<span class="line">` element.
49
+ * When highlights exist, frames are restructured with `data-frame-type` attributes
50
+ * (`highlighted`, `padding-top`, `padding-bottom`, or omitted for normal).
51
+ * Highlighted frames also receive `data-frame-indent` with the shared indent level.
52
+ *
53
+ * @param options - Optional configuration for padding frames
54
+ * @returns A `SourceEnhancer` function
55
+ *
56
+ * @example
57
+ * ```ts
58
+ * import { createEnhanceCodeEmphasis } from '@mui/internal-docs-infra/pipeline/enhanceCodeEmphasis';
41
59
  *
42
- * @param root - The HAST root node to enhance
43
- * @param comments - Comments extracted from the source code, keyed by line number
44
- * @param _fileName - The name of the file being processed (unused)
45
- * @returns The enhanced HAST root node with emphasis attributes added
60
+ * const enhancers = [createEnhanceCodeEmphasis({ paddingFrameMaxSize: 5, focusFramesMaxSize: 8 })];
61
+ * ```
62
+ */
63
+ export declare function createEnhanceCodeEmphasis(options?: EnhanceCodeEmphasisOptions): SourceEnhancer;
64
+ /**
65
+ * Default source enhancer that adds emphasis to code lines based on `@highlight` comments.
66
+ * Uses no padding frames by default. Use `createEnhanceCodeEmphasis` for configurable padding.
46
67
  *
47
68
  * @example
48
69
  * ```ts
@@ -1,3 +1,6 @@
1
+ import { calculateFrameRanges } from "../parseSource/calculateFrameRanges.mjs";
2
+ import { calculateFrameIndent } from "./calculateFrameIndent.mjs";
3
+ import { restructureFrames } from "../parseSource/restructureFrames.mjs";
1
4
  /**
2
5
  * The prefix used to identify emphasis comments in source code.
3
6
  * Comments starting with this prefix will be processed for emphasis.
@@ -8,19 +11,15 @@ export const EMPHASIS_COMMENT_PREFIX = '@highlight';
8
11
  * Parsed emphasis directive from a comment.
9
12
  */
10
13
 
11
- /**
12
- * Metadata for an emphasized line.
13
- */
14
-
15
14
  /**
16
15
  * Extracts a quoted string from content.
17
16
  * Supports both double quotes ("...") and single quotes ('...').
17
+ * Escaped quotes within the string are not supported.
18
18
  *
19
19
  * @param content - The content to extract the quoted string from
20
20
  * @returns The extracted string (without quotes) or undefined if no quoted string found
21
21
  */
22
22
  function extractQuotedString(content) {
23
- // Match either double-quoted or single-quoted string
24
23
  const match = content.match(/^["'](.*)["']$/);
25
24
  if (match) {
26
25
  return match[1];
@@ -30,14 +29,40 @@ function extractQuotedString(content) {
30
29
  return anyMatch?.[1];
31
30
  }
32
31
 
32
+ /**
33
+ * Extracts and removes the `@focus` keyword from content.
34
+ *
35
+ * @param content - The content to check for `@focus`
36
+ * @returns An object with `focus` boolean and the `remaining` content with `@focus` removed
37
+ */
38
+ function extractFocus(content) {
39
+ // Match @focus only as a standalone token (not inside quotes)
40
+ const match = content.match(/(^|\s)@focus(\s|$)/);
41
+ if (!match) {
42
+ return {
43
+ focus: false,
44
+ remaining: content
45
+ };
46
+ }
47
+ const start = match.index + match[1].length;
48
+ const remaining = (content.slice(0, start) + content.slice(start + '@focus'.length)).trim();
49
+ return {
50
+ focus: true,
51
+ remaining
52
+ };
53
+ }
54
+
33
55
  /**
34
56
  * Parses emphasis comments and returns structured directives.
35
57
  *
36
58
  * Supported formats:
37
59
  * - Single line: `@highlight` or `@highlight "description"`
60
+ * - Single line focused: `@highlight @focus` or `@highlight @focus "description"`
38
61
  * - Multiline start: `@highlight-start` or `@highlight-start "description"`
62
+ * - Multiline start focused: `@highlight-start @focus` or `@highlight-start @focus "description"`
39
63
  * - Multiline end: `@highlight-end`
40
64
  * - Text highlight: `@highlight-text "text to highlight"`
65
+ * - Text highlight focused: `@highlight-text @focus "text to highlight"`
41
66
  *
42
67
  * @param comments - Source comments keyed by line number
43
68
  * @returns Array of parsed emphasis directives
@@ -63,31 +88,46 @@ function parseEmphasisDirectives(comments) {
63
88
  } else if (content.startsWith('-start')) {
64
89
  // Start of multiline emphasis: @highlight-start or @highlight-start "description"
65
90
  const afterStart = content.slice('-start'.length).trim();
66
- const description = extractQuotedString(afterStart);
91
+ const {
92
+ focus,
93
+ remaining: remainingStart
94
+ } = extractFocus(afterStart);
95
+ const description = extractQuotedString(remainingStart);
67
96
  directives.push({
68
97
  line,
69
98
  type: 'start',
70
- description
99
+ description,
100
+ focus
71
101
  });
72
102
  } else if (content.startsWith('-text')) {
73
103
  // Text highlight: @highlight-text "text to highlight"
74
104
  const afterText = content.slice('-text'.length).trim();
75
- const highlightText = extractQuotedString(afterText);
105
+ const {
106
+ focus,
107
+ remaining: remainingText
108
+ } = extractFocus(afterText);
109
+ const highlightText = extractQuotedString(remainingText);
76
110
  if (highlightText) {
77
111
  directives.push({
78
112
  line,
79
113
  type: 'text',
80
- highlightText
114
+ highlightText,
115
+ focus
81
116
  });
82
117
  }
83
118
  } else {
84
119
  // Single line emphasis: @highlight or @highlight "description"
85
120
  const afterHighlight = content.trim();
86
- const description = extractQuotedString(afterHighlight) || undefined;
121
+ const {
122
+ focus,
123
+ remaining: remainingSingle
124
+ } = extractFocus(afterHighlight);
125
+ const description = extractQuotedString(remainingSingle) || undefined;
87
126
  directives.push({
88
127
  line,
89
128
  type: 'single',
90
- description
129
+ description,
130
+ focus
91
131
  });
92
132
  }
93
133
  }
@@ -231,13 +271,15 @@ function calculateEmphasizedLines(directives, lineElements) {
231
271
  emphasizedLines.set(directive.line, {
232
272
  description,
233
273
  strong,
234
- position: 'single'
274
+ position: 'single',
275
+ focus: directive.focus
235
276
  });
236
277
  } else if (directive.type === 'text') {
237
278
  // Text highlight - emphasize specific text within the line
238
279
  emphasizedLines.set(directive.line, {
239
280
  position: 'single',
240
- highlightText: directive.highlightText
281
+ highlightText: directive.highlightText,
282
+ focus: directive.focus
241
283
  });
242
284
  }
243
285
  }
@@ -286,11 +328,14 @@ function calculateEmphasizedLines(directives, lineElements) {
286
328
  strong: true,
287
329
  // Nested = always strong
288
330
  description: existing.description ?? (line === startLine ? description : undefined),
289
- position: existing.position ?? position // Inner range position takes precedence
331
+ position: existing.position ?? position,
332
+ // Inner range position takes precedence
333
+ focus: existing.focus || startDirective.focus
290
334
  } : {
291
335
  strong,
292
336
  description: line === startLine ? description : undefined,
293
- position
337
+ position,
338
+ focus: startDirective.focus
294
339
  };
295
340
  emphasizedLines.set(line, meta);
296
341
  }
@@ -362,52 +407,113 @@ function wrapTextInHighlightSpan(children, textToHighlight) {
362
407
  }
363
408
 
364
409
  /**
365
- * Recursively finds and modifies line elements in a HAST tree.
410
+ * Single-pass traversal that applies emphasis attributes to line elements
411
+ * AND collects leading whitespace for indent calculation on highlighted lines.
412
+ *
413
+ * This merges what would otherwise be two separate traversals into one.
366
414
  *
367
415
  * @param node - The node to process
368
416
  * @param emphasizedLines - Map of line numbers to their emphasis metadata
417
+ * @returns Array of line elements that are highlighted, grouped by region
369
418
  */
370
- function addEmphasisToLines(node, emphasizedLines) {
371
- if (!('children' in node) || !node.children) {
372
- return;
373
- }
374
- for (let i = 0; i < node.children.length; i += 1) {
375
- const child = node.children[i];
376
- if (child.type !== 'element') {
377
- continue;
419
+ function applyEmphasisAndCollectHighlightedElements(node, emphasizedLines) {
420
+ const highlightedLineElements = [];
421
+ function traverse(n) {
422
+ if (!('children' in n) || !n.children) {
423
+ return;
378
424
  }
425
+ for (let i = 0; i < n.children.length; i += 1) {
426
+ const child = n.children[i];
427
+ if (child.type !== 'element') {
428
+ continue;
429
+ }
379
430
 
380
- // Check if this is a line element
381
- if (child.tagName === 'span' && child.properties?.className === 'line' && typeof child.properties.dataLn === 'number') {
382
- const lineNumber = child.properties.dataLn;
383
- const meta = emphasizedLines.get(lineNumber);
384
- if (meta !== undefined) {
385
- if (meta.highlightText) {
386
- // For text highlight, wrap the specific text in a span with data-hl
387
- // Don't add data-hl to the line itself
388
- child.children = wrapTextInHighlightSpan(child.children, meta.highlightText);
389
- } else {
390
- // Use data-hl with optional "strong" value on the line
391
- child.properties.dataHl = meta.strong ? 'strong' : '';
392
- if (meta.description) {
393
- child.properties.dataHlDescription = meta.description;
394
- }
395
- if (meta.position) {
396
- child.properties.dataHlPosition = meta.position;
431
+ // Check if this is a line element
432
+ if (child.tagName === 'span' && child.properties?.className === 'line' && typeof child.properties.dataLn === 'number') {
433
+ const lineNumber = child.properties.dataLn;
434
+ const meta = emphasizedLines.get(lineNumber);
435
+ if (meta !== undefined) {
436
+ if (meta.highlightText) {
437
+ // For text highlight, wrap the specific text in a span with data-hl
438
+ // Don't add data-hl to the line itself
439
+ child.children = wrapTextInHighlightSpan(child.children, meta.highlightText);
440
+ } else {
441
+ // Use data-hl with optional "strong" value on the line
442
+ child.properties.dataHl = meta.strong ? 'strong' : '';
443
+ if (meta.description) {
444
+ child.properties.dataHlDescription = meta.description;
445
+ }
446
+ if (meta.position) {
447
+ child.properties.dataHlPosition = meta.position;
448
+ }
397
449
  }
450
+
451
+ // Collect this line element for indent calculation
452
+ highlightedLineElements.push(child);
398
453
  }
399
454
  }
455
+
456
+ // Recurse into children (for frames containing lines)
457
+ traverse(child);
458
+ }
459
+ }
460
+ traverse(node);
461
+ return highlightedLineElements;
462
+ }
463
+
464
+ /**
465
+ * Groups highlighted line elements by their highlight regions and calculates
466
+ * the indent level for each region.
467
+ *
468
+ * @param highlightedElements - Line elements that are highlighted, in order
469
+ * @param emphasizedLines - The emphasis metadata map
470
+ * @returns Map from region index to indent level
471
+ */
472
+ function calculateRegionIndentLevels(highlightedElements, emphasizedLines) {
473
+ const regionIndentLevels = new Map();
474
+ if (highlightedElements.length === 0) {
475
+ return regionIndentLevels;
476
+ }
477
+
478
+ // Group elements by consecutive regions
479
+ const sortedLines = Array.from(emphasizedLines.keys()).sort((a, b) => a - b);
480
+ let regionIndex = 0;
481
+ let regionElements = [];
482
+ let prevLine = -1;
483
+
484
+ // Build a quick lookup from lineNumber to element
485
+ const elementByLine = new Map();
486
+ for (const el of highlightedElements) {
487
+ const ln = el.properties?.dataLn;
488
+ elementByLine.set(ln, el);
489
+ }
490
+ for (const line of sortedLines) {
491
+ const el = elementByLine.get(line);
492
+ if (!el) {
493
+ continue;
494
+ }
495
+ if (prevLine >= 0 && line !== prevLine + 1) {
496
+ // Gap: close current region
497
+ regionIndentLevels.set(regionIndex, calculateFrameIndent(regionElements));
498
+ regionIndex += 1;
499
+ regionElements = [];
400
500
  }
501
+ regionElements.push(el);
502
+ prevLine = line;
503
+ }
401
504
 
402
- // Recurse into children (for frames containing lines)
403
- addEmphasisToLines(child, emphasizedLines);
505
+ // Close the last region
506
+ if (regionElements.length > 0) {
507
+ regionIndentLevels.set(regionIndex, calculateFrameIndent(regionElements));
404
508
  }
509
+ return regionIndentLevels;
405
510
  }
406
511
 
407
512
  /**
408
- * Source enhancer that adds emphasis to code lines based on `@highlight` comments.
513
+ * Creates a source enhancer that adds emphasis to code lines based on `@highlight` comments
514
+ * and restructures frames around highlighted regions.
409
515
  *
410
- * Supports four patterns:
516
+ * Supports five patterns:
411
517
  *
412
518
  * 1. **Single line emphasis** - emphasizes the line containing the comment:
413
519
  * ```jsx
@@ -437,12 +543,66 @@ function addEmphasisToLines(node, emphasizedLines) {
437
543
  * <h1>Heading 1</h1> {/* @highlight-text "Heading 1" *\/}
438
544
  * ```
439
545
  *
546
+ * 5. **Focus override** - mark a region for padding focus:
547
+ * ```jsx
548
+ * <h1>Heading 1</h1> {/* @highlight @focus *\/}
549
+ * ```
550
+ *
440
551
  * Emphasized lines receive a `data-hl` attribute on their `<span class="line">` element.
552
+ * When highlights exist, frames are restructured with `data-frame-type` attributes
553
+ * (`highlighted`, `padding-top`, `padding-bottom`, or omitted for normal).
554
+ * Highlighted frames also receive `data-frame-indent` with the shared indent level.
441
555
  *
442
- * @param root - The HAST root node to enhance
443
- * @param comments - Comments extracted from the source code, keyed by line number
444
- * @param _fileName - The name of the file being processed (unused)
445
- * @returns The enhanced HAST root node with emphasis attributes added
556
+ * @param options - Optional configuration for padding frames
557
+ * @returns A `SourceEnhancer` function
558
+ *
559
+ * @example
560
+ * ```ts
561
+ * import { createEnhanceCodeEmphasis } from '@mui/internal-docs-infra/pipeline/enhanceCodeEmphasis';
562
+ *
563
+ * const enhancers = [createEnhanceCodeEmphasis({ paddingFrameMaxSize: 5, focusFramesMaxSize: 8 })];
564
+ * ```
565
+ */
566
+ export function createEnhanceCodeEmphasis(options = {}) {
567
+ return (root, comments) => {
568
+ if (!comments || Object.keys(comments).length === 0) {
569
+ return root;
570
+ }
571
+
572
+ // Step 1: Parse directives from comments (no tree traversal)
573
+ const directives = parseEmphasisDirectives(comments);
574
+ if (directives.length === 0) {
575
+ return root;
576
+ }
577
+
578
+ // Step 2 (Traversal 1): Build line element map
579
+ const lineElements = buildLineElementMap(root);
580
+
581
+ // Step 3: Calculate which lines are emphasized (no tree traversal)
582
+ const emphasizedLines = calculateEmphasizedLines(directives, lineElements);
583
+ if (emphasizedLines.size === 0) {
584
+ return root;
585
+ }
586
+
587
+ // Step 4 (Traversal 2): Apply emphasis attributes AND collect highlighted elements
588
+ const highlightedElements = applyEmphasisAndCollectHighlightedElements(root, emphasizedLines);
589
+
590
+ // Step 5: Calculate indent levels per region (uses collected elements, no tree traversal)
591
+ const regionIndentLevels = calculateRegionIndentLevels(highlightedElements, emphasizedLines);
592
+
593
+ // Step 6: Calculate frame ranges (pure math, no tree traversal)
594
+ const totalLines = root.data?.totalLines ?? lineElements.size;
595
+ const frameRanges = calculateFrameRanges(emphasizedLines, totalLines, options);
596
+
597
+ // Step 7: Restructure frames (flat iteration, not deep recursive traversal)
598
+ restructureFrames(root, frameRanges, regionIndentLevels);
599
+ return root;
600
+ };
601
+ }
602
+
603
+ /**
604
+ * Default source enhancer that adds emphasis to code lines based on `@highlight` comments.
605
+ * Uses no padding frames by default. Use `createEnhanceCodeEmphasis` for configurable padding.
446
606
  *
447
607
  * @example
448
608
  * ```ts
@@ -451,19 +611,4 @@ function addEmphasisToLines(node, emphasizedLines) {
451
611
  * const enhancers = [enhanceCodeEmphasis];
452
612
  * ```
453
613
  */
454
- export const enhanceCodeEmphasis = (root, comments) => {
455
- if (!comments || Object.keys(comments).length === 0) {
456
- return root;
457
- }
458
- const directives = parseEmphasisDirectives(comments);
459
- if (directives.length === 0) {
460
- return root;
461
- }
462
- const lineElements = buildLineElementMap(root);
463
- const emphasizedLines = calculateEmphasizedLines(directives, lineElements);
464
- if (emphasizedLines.size === 0) {
465
- return root;
466
- }
467
- addEmphasisToLines(root, emphasizedLines);
468
- return root;
469
- };
614
+ export const enhanceCodeEmphasis = createEnhanceCodeEmphasis();