@fuzdev/fuz_ui 0.190.0 → 0.191.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.
Files changed (81) hide show
  1. package/dist/ContextmenuRootForSafariCompatibility.svelte +2 -2
  2. package/dist/MdzNodeView.svelte +3 -2
  3. package/dist/MdzNodeView.svelte.d.ts +1 -1
  4. package/dist/MdzNodeView.svelte.d.ts.map +1 -1
  5. package/dist/ProjectLinks.svelte +4 -2
  6. package/dist/ProjectLinks.svelte.d.ts.map +1 -1
  7. package/dist/analysis_context.d.ts +3 -3
  8. package/dist/analysis_context.js +3 -3
  9. package/dist/contextmenu_state.svelte.d.ts +7 -7
  10. package/dist/contextmenu_state.svelte.js +7 -7
  11. package/dist/docs_helpers.svelte.d.ts +7 -5
  12. package/dist/docs_helpers.svelte.d.ts.map +1 -1
  13. package/dist/docs_helpers.svelte.js +7 -5
  14. package/dist/intersect.svelte.d.ts +1 -1
  15. package/dist/intersect.svelte.js +1 -1
  16. package/dist/library_analysis.d.ts +6 -6
  17. package/dist/library_analysis.js +6 -6
  18. package/dist/library_gen.d.ts +4 -4
  19. package/dist/library_gen.js +4 -4
  20. package/dist/library_generate.d.ts +2 -2
  21. package/dist/library_helpers.d.ts +11 -11
  22. package/dist/library_helpers.js +11 -11
  23. package/dist/library_pipeline.d.ts +5 -5
  24. package/dist/library_pipeline.js +5 -5
  25. package/dist/logos.d.ts +7 -0
  26. package/dist/logos.d.ts.map +1 -1
  27. package/dist/logos.js +5 -0
  28. package/dist/mdz.d.ts +1 -14
  29. package/dist/mdz.d.ts.map +1 -1
  30. package/dist/mdz.js +64 -163
  31. package/dist/mdz_helpers.d.ts +26 -2
  32. package/dist/mdz_helpers.d.ts.map +1 -1
  33. package/dist/mdz_helpers.js +59 -5
  34. package/dist/mdz_lexer.d.ts.map +1 -1
  35. package/dist/mdz_lexer.js +18 -9
  36. package/dist/mdz_to_svelte.d.ts +5 -5
  37. package/dist/mdz_to_svelte.d.ts.map +1 -1
  38. package/dist/mdz_to_svelte.js +5 -5
  39. package/dist/mdz_token_parser.js +4 -3
  40. package/dist/module_helpers.d.ts +9 -9
  41. package/dist/module_helpers.js +9 -9
  42. package/dist/package_helpers.d.ts +20 -20
  43. package/dist/package_helpers.js +20 -20
  44. package/dist/storage.d.ts +4 -4
  45. package/dist/storage.js +4 -4
  46. package/dist/svelte_helpers.d.ts +13 -13
  47. package/dist/svelte_helpers.js +13 -13
  48. package/dist/svelte_preprocess_mdz.d.ts +2 -2
  49. package/dist/svelte_preprocess_mdz.js +2 -2
  50. package/dist/tome.d.ts +1 -1
  51. package/dist/tome.js +1 -1
  52. package/dist/ts_helpers.d.ts +24 -24
  53. package/dist/ts_helpers.js +24 -24
  54. package/dist/tsdoc_helpers.d.ts +6 -6
  55. package/dist/tsdoc_helpers.js +6 -6
  56. package/dist/tsdoc_mdz.js +1 -1
  57. package/package.json +8 -7
  58. package/src/lib/analysis_context.ts +3 -3
  59. package/src/lib/contextmenu_state.svelte.ts +7 -7
  60. package/src/lib/docs_helpers.svelte.ts +7 -5
  61. package/src/lib/intersect.svelte.ts +1 -1
  62. package/src/lib/library_analysis.ts +6 -6
  63. package/src/lib/library_gen.ts +4 -4
  64. package/src/lib/library_generate.ts +2 -2
  65. package/src/lib/library_helpers.ts +11 -11
  66. package/src/lib/library_pipeline.ts +5 -5
  67. package/src/lib/logos.ts +6 -0
  68. package/src/lib/mdz.ts +73 -177
  69. package/src/lib/mdz_helpers.ts +60 -5
  70. package/src/lib/mdz_lexer.ts +20 -9
  71. package/src/lib/mdz_to_svelte.ts +6 -5
  72. package/src/lib/mdz_token_parser.ts +4 -3
  73. package/src/lib/module_helpers.ts +9 -9
  74. package/src/lib/package_helpers.ts +20 -20
  75. package/src/lib/storage.ts +4 -4
  76. package/src/lib/svelte_helpers.ts +13 -13
  77. package/src/lib/svelte_preprocess_mdz.ts +2 -2
  78. package/src/lib/tome.ts +1 -1
  79. package/src/lib/ts_helpers.ts +24 -24
  80. package/src/lib/tsdoc_helpers.ts +6 -6
  81. package/src/lib/tsdoc_mdz.ts +1 -1
package/dist/mdz.js CHANGED
@@ -28,7 +28,7 @@
28
28
  *
29
29
  * @module
30
30
  */
31
- import { BACKTICK, ASTERISK, UNDERSCORE, TILDE, NEWLINE, HYPHEN, HASH, SPACE, TAB, LEFT_ANGLE, RIGHT_ANGLE, SLASH, LEFT_BRACKET, RIGHT_BRACKET, LEFT_PAREN, RIGHT_PAREN, A_UPPER, Z_UPPER, HR_HYPHEN_COUNT, MIN_CODEBLOCK_BACKTICKS, MAX_HEADING_LEVEL, HTTPS_PREFIX_LENGTH, HTTP_PREFIX_LENGTH, is_letter, is_tag_name_char, is_word_char, PERIOD, is_valid_path_char, trim_trailing_punctuation, is_at_absolute_path, is_at_relative_path, extract_single_tag, } from './mdz_helpers.js';
31
+ import { BACKTICK, ASTERISK, UNDERSCORE, TILDE, NEWLINE, HYPHEN, HASH, SPACE, TAB, LEFT_ANGLE, RIGHT_ANGLE, SLASH, LEFT_BRACKET, RIGHT_BRACKET, LEFT_PAREN, RIGHT_PAREN, A_UPPER, Z_UPPER, HR_HYPHEN_COUNT, MIN_CODEBLOCK_BACKTICKS, MAX_HEADING_LEVEL, HTTPS_PREFIX_LENGTH, HTTP_PREFIX_LENGTH, is_letter, is_tag_name_char, is_word_char, PERIOD, is_valid_path_char, trim_trailing_punctuation, is_at_absolute_path, is_at_relative_path, extract_single_tag, mdz_heading_id, mdz_is_url, } from './mdz_helpers.js';
32
32
  // TODO design incremental parsing or some system that preserves Svelte components across re-renders when possible
33
33
  /**
34
34
  * Parses text to an array of `MdzNode`.
@@ -507,89 +507,32 @@ export class MdzParser {
507
507
  */
508
508
  #parse_tag() {
509
509
  const start = this.#index;
510
- // Save parent accumulation state to avoid polluting component children with parent's accumulated text
511
- const saved_state = this.#save_accumulation_state();
512
- // Clear accumulation state for parsing component children
513
- this.#accumulated_text = '';
514
- this.#nodes.length = 0;
515
- // Consume <
516
- if (!this.#match('<')) {
517
- // Restore parent state before returning
518
- this.#restore_accumulation_state(saved_state);
519
- const content = this.#template[this.#index];
520
- this.#index++;
521
- return {
522
- type: 'Text',
523
- content,
524
- start,
525
- end: this.#index,
526
- };
527
- }
528
- this.#index++;
529
- // Parse tag name
530
- const tag_name_start = this.#index;
531
- let tag_name_end = this.#index;
510
+ // Phase 1: Validate tag structure using a local scan index.
511
+ // Avoids save/restore of accumulation state on the fast path
512
+ // (most `<` characters are not valid tags).
513
+ let i = start + 1; // skip <
532
514
  // Tag name must start with a letter
533
- if (this.#index >= this.#template.length) {
534
- // Just a `<` at EOF - restore parent state
535
- this.#restore_accumulation_state(saved_state);
536
- return {
537
- type: 'Text',
538
- content: '<',
539
- start,
540
- end: this.#index,
541
- };
542
- }
543
- const first_char = this.#template.charCodeAt(this.#index);
544
- if (!is_letter(first_char)) {
545
- // Not a valid tag, treat as text - restore parent state
546
- this.#restore_accumulation_state(saved_state);
547
- return {
548
- type: 'Text',
549
- content: '<',
550
- start,
551
- end: start + 1,
552
- };
515
+ if (i >= this.#template.length || !is_letter(this.#template.charCodeAt(i))) {
516
+ this.#index = start + 1;
517
+ return this.#make_text_node('<', start);
553
518
  }
554
519
  // Collect tag name (letters, numbers, hyphens, underscores)
555
- while (this.#index < this.#template.length) {
556
- const char_code = this.#template.charCodeAt(this.#index);
557
- if (is_tag_name_char(char_code)) {
558
- this.#index++;
559
- }
560
- else {
561
- break;
562
- }
563
- }
564
- tag_name_end = this.#index;
565
- const tag_name = this.#template.slice(tag_name_start, tag_name_end);
566
- if (tag_name.length === 0) {
567
- // Empty tag name - restore parent state
568
- this.#restore_accumulation_state(saved_state);
569
- return {
570
- type: 'Text',
571
- content: '<',
572
- start,
573
- end: start + 1,
574
- };
520
+ while (i < this.#template.length && is_tag_name_char(this.#template.charCodeAt(i))) {
521
+ i++;
575
522
  }
576
- // Determine if this is a Component (uppercase) or Element (lowercase)
523
+ const tag_name = this.#template.slice(start + 1, i);
577
524
  const first_char_code = tag_name.charCodeAt(0);
578
- const is_component = first_char_code >= A_UPPER && first_char_code <= Z_UPPER;
579
- const node_type = is_component ? 'Component' : 'Element';
525
+ const node_type = first_char_code >= A_UPPER && first_char_code <= Z_UPPER ? 'Component' : 'Element';
580
526
  // Skip whitespace after tag name (for future attribute support)
581
- while (this.#index < this.#template.length &&
582
- this.#template.charCodeAt(this.#index) === SPACE) {
583
- this.#index++;
527
+ while (i < this.#template.length && this.#template.charCodeAt(i) === SPACE) {
528
+ i++;
584
529
  }
585
530
  // TODO: Parse attributes here
586
531
  // Check for self-closing />
587
- if (this.#index + 1 < this.#template.length &&
588
- this.#template.charCodeAt(this.#index) === SLASH &&
589
- this.#template.charCodeAt(this.#index + 1) === RIGHT_ANGLE) {
590
- this.#index += 2;
591
- // Restore parent state before returning
592
- this.#restore_accumulation_state(saved_state);
532
+ if (i + 1 < this.#template.length &&
533
+ this.#template.charCodeAt(i) === SLASH &&
534
+ this.#template.charCodeAt(i + 1) === RIGHT_ANGLE) {
535
+ this.#index = i + 2;
593
536
  return {
594
537
  type: node_type,
595
538
  name: tag_name,
@@ -598,33 +541,38 @@ export class MdzParser {
598
541
  end: this.#index,
599
542
  };
600
543
  }
601
- // Check for closing >
602
- if (this.#index >= this.#template.length ||
603
- this.#template.charCodeAt(this.#index) !== RIGHT_ANGLE) {
604
- // Unclosed opening tag, treat as text - restore parent state
605
- this.#restore_accumulation_state(saved_state);
544
+ // Must have closing >
545
+ if (i >= this.#template.length || this.#template.charCodeAt(i) !== RIGHT_ANGLE) {
606
546
  this.#index = start + 1;
607
- return {
608
- type: 'Text',
609
- content: '<',
610
- start,
611
- end: this.#index,
612
- };
547
+ return this.#make_text_node('<', start);
613
548
  }
614
- // Consume >
615
- this.#index++;
616
- // Parse children until closing tag
549
+ const content_start = i + 1; // past >
550
+ // Bail early if closing tag is missing, past a paragraph break,
551
+ // or past the search boundary (e.g. heading line end)
617
552
  const closing_tag = `</${tag_name}>`;
553
+ const search_limit = Math.min(this.#max_search_index, this.#template.length);
554
+ const close_tag_index = this.#template.indexOf(closing_tag, content_start);
555
+ if (close_tag_index === -1 || close_tag_index >= search_limit) {
556
+ this.#index = start + 1;
557
+ return this.#make_text_node('<', start);
558
+ }
559
+ const para_break = this.#template.indexOf('\n\n', content_start);
560
+ if (para_break !== -1 && para_break < close_tag_index) {
561
+ this.#index = start + 1;
562
+ return this.#make_text_node('<', start);
563
+ }
564
+ // Phase 2: Tag validated — save accumulation state and parse children.
565
+ const saved_state = this.#save_accumulation_state();
566
+ this.#accumulated_text = '';
567
+ this.#nodes.length = 0;
568
+ this.#index = content_start;
618
569
  const children = [];
619
570
  while (this.#index < this.#template.length) {
620
- // Check for closing tag
621
571
  if (this.#match(closing_tag)) {
622
- // Flush any accumulated text from children
623
572
  this.#flush_text();
624
573
  children.push(...this.#nodes);
625
574
  this.#nodes.length = 0;
626
575
  this.#index += closing_tag.length;
627
- // Restore parent state before returning
628
576
  this.#restore_accumulation_state(saved_state);
629
577
  return {
630
578
  type: node_type,
@@ -645,16 +593,10 @@ export class MdzParser {
645
593
  children.push(node);
646
594
  }
647
595
  }
648
- // Unclosed tag - reached EOF without finding closing tag
649
- // Treat the opening tag as text - restore parent state
596
+ // Defensive: pre-check guarantees closing tag exists, but handle EOF gracefully
650
597
  this.#restore_accumulation_state(saved_state);
651
598
  this.#index = start + 1;
652
- return {
653
- type: 'Text',
654
- content: '<',
655
- start,
656
- end: this.#index,
657
- };
599
+ return this.#make_text_node('<', start);
658
600
  }
659
601
  /**
660
602
  * Read-only check if current position matches a block element.
@@ -685,7 +627,7 @@ export class MdzParser {
685
627
  /**
686
628
  * Restore previously saved text accumulation state.
687
629
  * Used to restore parent state when exiting nested structure parsing.
688
- * @param state - State object returned from `#save_accumulation_state()`
630
+ * @param state - state object returned from `#save_accumulation_state()`
689
631
  */
690
632
  #restore_accumulation_state(state) {
691
633
  this.#accumulated_text = state.accumulated_text;
@@ -698,9 +640,9 @@ export class MdzParser {
698
640
  * Word boundary = not surrounded by word characters (A-Z, a-z, 0-9).
699
641
  * Used to prevent intraword emphasis for underscores and tildes.
700
642
  *
701
- * @param index - Position to check
702
- * @param check_before - Whether to check the character before this position
703
- * @param check_after - Whether to check the character after this position
643
+ * @param index - position to check
644
+ * @param check_before - whether to check the character before this position
645
+ * @param check_after - whether to check the character after this position
704
646
  */
705
647
  #is_at_word_boundary(index, check_before, check_after) {
706
648
  if (check_before && index > 0) {
@@ -890,9 +832,9 @@ export class MdzParser {
890
832
  * - Paragraph break (double newline) is encountered (allows block elements to interrupt inline formatting)
891
833
  * - `end_index` boundary is reached
892
834
  *
893
- * @param delimiter - The delimiter string to stop at (e.g., '**', '_', ']')
894
- * @param end_index - Optional maximum index to parse up to (for greedy/bounded parsing)
895
- * @returns Array of parsed nodes (may be empty if delimiter found immediately)
835
+ * @param delimiter - the delimiter string to stop at (e.g., '**', '_', ']')
836
+ * @param end_index - optional maximum index to parse up to (for greedy/bounded parsing)
837
+ * @returns array of parsed nodes (may be empty if delimiter found immediately)
896
838
  */
897
839
  #parse_nodes_until(delimiter, end_index) {
898
840
  const nodes = [];
@@ -1038,29 +980,27 @@ export class MdzParser {
1038
980
  }
1039
981
  // Consume the space after hashes (already verified to exist)
1040
982
  this.#index++;
1041
- // Parse inline content until newline
983
+ // Find end-of-line to bound nested parsers (prevents tag scanner from scanning past heading)
984
+ let eol = this.#template.indexOf('\n', this.#index);
985
+ if (eol === -1)
986
+ eol = this.#template.length;
987
+ const saved_max_search_index = this.#max_search_index;
988
+ this.#max_search_index = eol;
989
+ // Parse inline content until end of line
1042
990
  const content_nodes = [];
1043
- while (this.#index < this.#template.length) {
1044
- const char_code = this.#template.charCodeAt(this.#index);
1045
- if (char_code === NEWLINE) {
1046
- break;
1047
- }
991
+ while (this.#index < eol) {
1048
992
  const node = this.#parse_node();
1049
993
  if (node.type === 'Text') {
1050
- // Check if text node includes a newline (since #parse_text doesn't stop at single newlines)
1051
- // If so, trim it and move index back
1052
- const newline_index = node.content.indexOf('\n');
1053
- if (newline_index !== -1) {
1054
- const trimmed_content = node.content.slice(0, newline_index);
994
+ // Trim if #parse_text overshot past the newline
995
+ if (node.end > eol) {
996
+ const trimmed_content = node.content.slice(0, eol - node.start);
1055
997
  if (trimmed_content) {
1056
998
  this.#accumulate_text(trimmed_content, node.start);
1057
999
  }
1058
- this.#index = node.start + newline_index;
1000
+ this.#index = eol;
1059
1001
  break;
1060
1002
  }
1061
- else {
1062
- this.#accumulate_text(node.content, node.start);
1063
- }
1003
+ this.#accumulate_text(node.content, node.start);
1064
1004
  }
1065
1005
  else {
1066
1006
  this.#flush_text();
@@ -1069,6 +1009,7 @@ export class MdzParser {
1069
1009
  content_nodes.push(node);
1070
1010
  }
1071
1011
  }
1012
+ this.#max_search_index = saved_max_search_index;
1072
1013
  this.#flush_text();
1073
1014
  content_nodes.push(...this.#nodes);
1074
1015
  this.#nodes.length = 0;
@@ -1076,6 +1017,7 @@ export class MdzParser {
1076
1017
  return {
1077
1018
  type: 'Heading',
1078
1019
  level: level,
1020
+ id: mdz_heading_id(content_nodes),
1079
1021
  children: content_nodes,
1080
1022
  start,
1081
1023
  end: this.#index,
@@ -1234,44 +1176,3 @@ export class MdzParser {
1234
1176
  throw Error('Code block not properly closed');
1235
1177
  }
1236
1178
  }
1237
- /**
1238
- * Check if a string is a URL (`https://` or `http://`).
1239
- * Requires at least one valid character after the protocol.
1240
- * Rejects whitespace and characters that can't start a valid hostname.
1241
- */
1242
- const URL_PATTERN = /^https?:\/\/[^\s)\]}<>.,:/?#!]/;
1243
- export const mdz_is_url = (s) => URL_PATTERN.test(s);
1244
- /**
1245
- * Resolves a relative path (`./` or `../`) against a base path.
1246
- * The base is treated as a directory regardless of trailing slash
1247
- * (`'/docs/mdz'` and `'/docs/mdz/'` behave identically).
1248
- * Handles embedded `.` and `..` segments within the reference
1249
- * (e.g., `'./a/../b'` → navigates up then down).
1250
- * Clamps at root — excess `..` segments stop at `/` rather than escaping.
1251
- *
1252
- * @param reference A relative path starting with `./` or `../`.
1253
- * @param base An absolute base path (e.g., `'/docs/mdz/'`). Empty string is treated as root.
1254
- * @returns An absolute resolved path (e.g., `'/docs/mdz/grammar'`).
1255
- */
1256
- export const resolve_relative_path = (reference, base) => {
1257
- const segments = base.split('/');
1258
- // Remove trailing empty from split (e.g., '/docs/mdz/' → ['', 'docs', 'mdz', ''])
1259
- // but keep the root segment ([''] from '' base or ['', ''] from '/').
1260
- if (segments.length > 1 && segments.at(-1) === '')
1261
- segments.pop();
1262
- const trailing = reference.endsWith('/');
1263
- for (const segment of reference.split('/')) {
1264
- if (segment === '.' || segment === '')
1265
- continue;
1266
- if (segment === '..') {
1267
- if (segments.length > 1)
1268
- segments.pop(); // clamp at root
1269
- }
1270
- else {
1271
- segments.push(segment);
1272
- }
1273
- }
1274
- if (trailing)
1275
- segments.push('');
1276
- return segments.join('/');
1277
- };
@@ -95,14 +95,38 @@ export declare const trim_trailing_punctuation: (url: string) => string;
95
95
  /**
96
96
  * Check if position in text is the start of an absolute path (starts with `/`).
97
97
  * Must be preceded by whitespace or be at the start of the string.
98
- * Rejects `//` (comments/protocol-relative) and `/ ` (bare slash).
98
+ * Rejects `//` (comments/protocol-relative) and slash followed by whitespace.
99
99
  */
100
100
  export declare const is_at_absolute_path: (text: string, index: number) => boolean;
101
101
  /**
102
102
  * Check if position in text is the start of a relative path (`./` or `../`).
103
103
  * Must be preceded by whitespace or be at the start of the string.
104
- * Requires at least one path character after the prefix.
104
+ * Rejects prefix followed by whitespace, slash, or end of string.
105
105
  */
106
106
  export declare const is_at_relative_path: (text: string, index: number) => boolean;
107
+ /**
108
+ * Extracts plain text content from an array of mdz nodes, recursing into children.
109
+ */
110
+ export declare const mdz_text_content: (nodes: Array<MdzNode>) => string;
111
+ /**
112
+ * Generates a lowercase slug id for a heading from its child nodes.
113
+ * Follows standard markdown conventions (GitHub, etc.) where heading IDs are lowercased.
114
+ * For case-preserving IDs (e.g. API declarations), see `docs_slugify` in `docs_helpers.svelte.ts`.
115
+ */
116
+ export declare const mdz_heading_id: (nodes: Array<MdzNode>) => string;
117
+ export declare const mdz_is_url: (s: string) => boolean;
118
+ /**
119
+ * Resolves a relative path (`./` or `../`) against a base path.
120
+ * The base is treated as a directory regardless of trailing slash
121
+ * (`'/docs/mdz'` and `'/docs/mdz/'` behave identically).
122
+ * Handles embedded `.` and `..` segments within the reference
123
+ * (e.g., `'./a/../b'` → navigates up then down).
124
+ * Clamps at root — excess `..` segments stop at `/` rather than escaping.
125
+ *
126
+ * @param reference - a relative path starting with `./` or `../`
127
+ * @param base - An absolute base path (e.g., `'/docs/mdz/'`). Empty string is treated as root.
128
+ * @returns an absolute resolved path (e.g., `'/docs/mdz/grammar'`)
129
+ */
130
+ export declare const resolve_relative_path: (reference: string, base: string) => string;
107
131
  export declare const extract_single_tag: (nodes: Array<MdzNode>) => MdzComponentNode | MdzElementNode | null;
108
132
  //# sourceMappingURL=mdz_helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mdz_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_helpers.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAC,MAAM,UAAU,CAAC;AAGxE,eAAO,MAAM,QAAQ,KAAK,CAAC;AAC3B,eAAO,MAAM,QAAQ,KAAK,CAAC;AAC3B,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,KAAK,MAAM,CAAC;AACzB,eAAO,MAAM,OAAO,KAAK,CAAC;AAC1B,eAAO,MAAM,MAAM,KAAK,CAAC;AACzB,eAAO,MAAM,IAAI,KAAK,CAAC;AACvB,eAAO,MAAM,KAAK,KAAK,CAAC;AACxB,eAAO,MAAM,GAAG,IAAI,CAAC;AACrB,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,WAAW,KAAK,CAAC;AAC9B,eAAO,MAAM,KAAK,KAAK,CAAC;AACxB,eAAO,MAAM,YAAY,KAAK,CAAC;AAC/B,eAAO,MAAM,aAAa,KAAK,CAAC;AAChC,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,WAAW,KAAK,CAAC;AAC9B,eAAO,MAAM,KAAK,KAAK,CAAC;AACxB,eAAO,MAAM,MAAM,KAAK,CAAC;AACzB,eAAO,MAAM,KAAK,KAAK,CAAC;AACxB,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,WAAW,KAAK,CAAC;AAC9B,eAAO,MAAM,QAAQ,KAAK,CAAC;AAE3B,eAAO,MAAM,MAAM,KAAK,CAAC;AACzB,eAAO,MAAM,OAAO,KAAK,CAAC;AAC1B,eAAO,MAAM,SAAS,KAAK,CAAC;AAC5B,eAAO,MAAM,UAAU,KAAK,CAAC;AAC7B,eAAO,MAAM,IAAI,KAAK,CAAC;AACvB,eAAO,MAAM,MAAM,KAAK,CAAC;AACzB,eAAO,MAAM,EAAE,KAAK,CAAC;AAErB,eAAO,MAAM,OAAO,KAAK,CAAC;AAC1B,eAAO,MAAM,OAAO,KAAK,CAAC;AAC1B,eAAO,MAAM,OAAO,KAAK,CAAC;AAC1B,eAAO,MAAM,OAAO,MAAM,CAAC;AAC3B,eAAO,MAAM,IAAI,KAAK,CAAC;AACvB,eAAO,MAAM,IAAI,KAAK,CAAC;AAEvB,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,uBAAuB,IAAI,CAAC;AACzC,eAAO,MAAM,iBAAiB,IAAI,CAAC;AACnC,eAAO,MAAM,mBAAmB,IAAI,CAAC;AACrC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAEpC;;GAEG;AACH,eAAO,MAAM,SAAS,GAAI,WAAW,MAAM,KAAG,OACmD,CAAC;AAElG;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,WAAW,MAAM,KAAG,OAI5B,CAAC;AAE1B;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,GAAI,WAAW,MAAM,KAAG,OAOhD,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,kBAAkB,GAAI,WAAW,MAAM,KAAG,OAwBjC,CAAC;AAEvB;;;;;;;GAOG;AACH,eAAO,MAAM,yBAAyB,GAAI,KAAK,MAAM,KAAG,MA4CvD,CAAC;AAEF;;;;GAIG;AACH;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,OASjE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,MAAM,MAAM,EAAE,OAAO,MAAM,KAAG,OAsBjE,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC9B,OAAO,KAAK,CAAC,OAAO,CAAC,KACnB,gBAAgB,GAAG,cAAc,GAAG,IAiBtC,CAAC"}
1
+ {"version":3,"file":"mdz_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_helpers.ts"],"names":[],"mappings":"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"}
@@ -6,6 +6,7 @@
6
6
  *
7
7
  * @module
8
8
  */
9
+ import { slugify } from '@fuzdev/fuz_util/path.js';
9
10
  // Character codes for performance
10
11
  export const BACKTICK = 96; // `
11
12
  export const ASTERISK = 42; // *
@@ -172,7 +173,7 @@ export const trim_trailing_punctuation = (url) => {
172
173
  /**
173
174
  * Check if position in text is the start of an absolute path (starts with `/`).
174
175
  * Must be preceded by whitespace or be at the start of the string.
175
- * Rejects `//` (comments/protocol-relative) and `/ ` (bare slash).
176
+ * Rejects `//` (comments/protocol-relative) and slash followed by whitespace.
176
177
  */
177
178
  export const is_at_absolute_path = (text, index) => {
178
179
  if (text.charCodeAt(index) !== SLASH)
@@ -185,12 +186,12 @@ export const is_at_absolute_path = (text, index) => {
185
186
  if (index + 1 >= text.length)
186
187
  return false;
187
188
  const next_char = text.charCodeAt(index + 1);
188
- return next_char !== SLASH && next_char !== SPACE && next_char !== NEWLINE;
189
+ return next_char !== SLASH && next_char !== SPACE && next_char !== NEWLINE && next_char !== TAB;
189
190
  };
190
191
  /**
191
192
  * Check if position in text is the start of a relative path (`./` or `../`).
192
193
  * Must be preceded by whitespace or be at the start of the string.
193
- * Requires at least one path character after the prefix.
194
+ * Rejects prefix followed by whitespace, slash, or end of string.
194
195
  */
195
196
  export const is_at_relative_path = (text, index) => {
196
197
  if (text.charCodeAt(index) !== PERIOD)
@@ -206,15 +207,68 @@ export const is_at_relative_path = (text, index) => {
206
207
  text.charCodeAt(index + 1) === PERIOD &&
207
208
  text.charCodeAt(index + 2) === SLASH) {
208
209
  const after = text.charCodeAt(index + 3);
209
- return after !== SPACE && after !== NEWLINE && after !== SLASH;
210
+ return after !== SPACE && after !== NEWLINE && after !== TAB && after !== SLASH;
210
211
  }
211
212
  // Check for ./ (at least 3 chars: ./x)
212
213
  if (remaining >= 3 && text.charCodeAt(index + 1) === SLASH) {
213
214
  const after = text.charCodeAt(index + 2);
214
- return after !== SPACE && after !== NEWLINE && after !== SLASH;
215
+ return after !== SPACE && after !== NEWLINE && after !== TAB && after !== SLASH;
215
216
  }
216
217
  return false;
217
218
  };
219
+ /**
220
+ * Extracts plain text content from an array of mdz nodes, recursing into children.
221
+ */
222
+ export const mdz_text_content = (nodes) => nodes
223
+ .map((n) => ('children' in n ? mdz_text_content(n.children) : 'content' in n ? n.content : ''))
224
+ .join('');
225
+ /**
226
+ * Generates a lowercase slug id for a heading from its child nodes.
227
+ * Follows standard markdown conventions (GitHub, etc.) where heading IDs are lowercased.
228
+ * For case-preserving IDs (e.g. API declarations), see `docs_slugify` in `docs_helpers.svelte.ts`.
229
+ */
230
+ export const mdz_heading_id = (nodes) => slugify(mdz_text_content(nodes), false);
231
+ /**
232
+ * Check if a string is a URL (`https://` or `http://`).
233
+ * Requires at least one valid character after the protocol.
234
+ * Rejects whitespace and characters that can't start a valid hostname.
235
+ */
236
+ const URL_PATTERN = /^https?:\/\/[^\s)\]}<>.,:/?#!]/;
237
+ export const mdz_is_url = (s) => URL_PATTERN.test(s);
238
+ /**
239
+ * Resolves a relative path (`./` or `../`) against a base path.
240
+ * The base is treated as a directory regardless of trailing slash
241
+ * (`'/docs/mdz'` and `'/docs/mdz/'` behave identically).
242
+ * Handles embedded `.` and `..` segments within the reference
243
+ * (e.g., `'./a/../b'` → navigates up then down).
244
+ * Clamps at root — excess `..` segments stop at `/` rather than escaping.
245
+ *
246
+ * @param reference - a relative path starting with `./` or `../`
247
+ * @param base - An absolute base path (e.g., `'/docs/mdz/'`). Empty string is treated as root.
248
+ * @returns an absolute resolved path (e.g., `'/docs/mdz/grammar'`)
249
+ */
250
+ export const resolve_relative_path = (reference, base) => {
251
+ const segments = base.split('/');
252
+ // Remove trailing empty from split (e.g., '/docs/mdz/' → ['', 'docs', 'mdz', ''])
253
+ // but keep the root segment ([''] from '' base or ['', ''] from '/').
254
+ if (segments.length > 1 && segments.at(-1) === '')
255
+ segments.pop();
256
+ const trailing = reference.endsWith('/');
257
+ for (const segment of reference.split('/')) {
258
+ if (segment === '.' || segment === '')
259
+ continue;
260
+ if (segment === '..') {
261
+ if (segments.length > 1)
262
+ segments.pop(); // clamp at root
263
+ }
264
+ else {
265
+ segments.push(segment);
266
+ }
267
+ }
268
+ if (trailing)
269
+ segments.push('');
270
+ return segments.join('/');
271
+ };
218
272
  export const extract_single_tag = (nodes) => {
219
273
  let tag = null;
220
274
  for (const node of nodes) {
@@ -1 +1 @@
1
- {"version":3,"file":"mdz_lexer.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_lexer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mdz_lexer.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_lexer.ts"],"names":[],"mappings":"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"}
package/dist/mdz_lexer.js CHANGED
@@ -6,8 +6,7 @@
6
6
  *
7
7
  * @module
8
8
  */
9
- import { mdz_is_url } from './mdz.js';
10
- import { is_letter, is_tag_name_char, is_word_char, is_valid_path_char, trim_trailing_punctuation, BACKTICK, ASTERISK, UNDERSCORE, TILDE, NEWLINE, HYPHEN, HASH, SPACE, TAB, LEFT_ANGLE, RIGHT_ANGLE, SLASH, PERIOD, LEFT_BRACKET, LEFT_PAREN, RIGHT_PAREN, RIGHT_BRACKET, A_UPPER, Z_UPPER, HR_HYPHEN_COUNT, MIN_CODEBLOCK_BACKTICKS, MAX_HEADING_LEVEL, HTTPS_PREFIX_LENGTH, HTTP_PREFIX_LENGTH, is_at_absolute_path, is_at_relative_path, } from './mdz_helpers.js';
9
+ import { mdz_is_url, is_letter, is_tag_name_char, is_word_char, is_valid_path_char, trim_trailing_punctuation, BACKTICK, ASTERISK, UNDERSCORE, TILDE, NEWLINE, HYPHEN, HASH, SPACE, TAB, LEFT_ANGLE, RIGHT_ANGLE, SLASH, PERIOD, LEFT_BRACKET, LEFT_PAREN, RIGHT_PAREN, RIGHT_BRACKET, A_UPPER, Z_UPPER, HR_HYPHEN_COUNT, MIN_CODEBLOCK_BACKTICKS, MAX_HEADING_LEVEL, HTTPS_PREFIX_LENGTH, HTTP_PREFIX_LENGTH, is_at_absolute_path, is_at_relative_path, } from './mdz_helpers.js';
11
10
  //
12
11
  // Lexer
13
12
  //
@@ -99,13 +98,16 @@ export class MdzLexer {
99
98
  start,
100
99
  end: this.#index, // end of "## " prefix
101
100
  });
101
+ // Find end-of-line to bound nested tokenizers (prevents tag scanner from scanning past heading)
102
+ let eol = this.#text.indexOf('\n', this.#index);
103
+ if (eol === -1)
104
+ eol = this.#text.length;
105
+ const saved_max = this.#max_search_index;
106
+ this.#max_search_index = eol;
102
107
  // Tokenize inline content until newline or EOF
103
108
  // tokenize_text may consume a newline as part of block-element lookahead,
104
109
  // so we check emitted text tokens for embedded newlines and trim.
105
- while (this.#index < this.#text.length) {
106
- if (this.#text.charCodeAt(this.#index) === NEWLINE) {
107
- break;
108
- }
110
+ while (this.#index < eol) {
109
111
  const token_count_before = this.#tokens.length;
110
112
  this.#tokenize_inline();
111
113
  // Check if the last emitted token is text containing a newline
@@ -128,6 +130,7 @@ export class MdzLexer {
128
130
  }
129
131
  }
130
132
  }
133
+ this.#max_search_index = saved_max;
131
134
  // Emit heading_end marker so the token parser knows where heading content stops
132
135
  this.#tokens.push({ type: 'heading_end', start: this.#index, end: this.#index });
133
136
  // Skip newlines after heading (like original parser's main loop)
@@ -548,11 +551,17 @@ export class MdzLexer {
548
551
  return;
549
552
  }
550
553
  this.#index++; // consume >
551
- // Check for closing tag existence before committing
554
+ // Check for closing tag existence before committing
555
+ // must exist within search boundary and before any paragraph break
552
556
  const closing_tag = `</${tag_name}>`;
557
+ const search_limit = Math.min(this.#max_search_index, this.#text.length);
553
558
  const closing_tag_pos = this.#text.indexOf(closing_tag, this.#index);
554
- if (closing_tag_pos === -1) {
555
- // No closing tag - revert
559
+ if (closing_tag_pos === -1 || closing_tag_pos >= search_limit) {
560
+ this.#index = start + 1;
561
+ this.#emit_text('<', start);
562
+ return;
563
+ }
564
+ if (this.#has_paragraph_break_between(this.#index, closing_tag_pos)) {
556
565
  this.#index = start + 1;
557
566
  this.#emit_text('<', start);
558
567
  return;
@@ -7,7 +7,7 @@
7
7
  *
8
8
  * @module
9
9
  */
10
- import { type MdzNode } from './mdz.js';
10
+ import type { MdzNode } from './mdz.js';
11
11
  /**
12
12
  * Result of converting `MdzNode` arrays to Svelte markup.
13
13
  */
@@ -28,12 +28,12 @@ export interface MdzToSvelteResult {
28
28
  * Each node type produces output matching what `MdzNodeView.svelte` renders at runtime.
29
29
  * Collects required imports and flags unconfigured component/element references.
30
30
  *
31
- * @param nodes Parsed mdz nodes to render.
32
- * @param components Component name to import path mapping (e.g., `{Alert: '$lib/Alert.svelte'}`).
31
+ * @param nodes - parsed mdz nodes to render
32
+ * @param components - Component name to import path mapping (e.g., `{Alert: '$lib/Alert.svelte'}`)
33
33
  * If content references a component not in this map, `has_unconfigured_tags` is set.
34
- * @param elements Allowed HTML element names (e.g., `new Set(['aside', 'details'])`).
34
+ * @param elements - Allowed HTML element names (e.g., `new Set(['aside', 'details'])`)
35
35
  * If content references an element not in this set, `has_unconfigured_tags` is set.
36
- * @param base Base path for resolving relative links (e.g., `'/docs/mdz/'`).
36
+ * @param base - Base path for resolving relative links (e.g., `'/docs/mdz/'`)
37
37
  * When provided, relative references (`./`, `../`) are resolved to absolute paths
38
38
  * and passed through `resolve()`. Trailing slash recommended.
39
39
  */
@@ -1 +1 @@
1
- {"version":3,"file":"mdz_to_svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_to_svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,EAAC,KAAK,OAAO,EAAwB,MAAM,UAAU,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IAEf,yDAAyD;IACzD,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAA;KAAC,CAAC,CAAC;IAEhE,yEAAyE;IACzE,qBAAqB,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,GACzB,OAAO,KAAK,CAAC,OAAO,CAAC,EACrB,YAAY,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,UAAU,WAAW,CAAC,MAAM,CAAC,EAC7B,OAAO,MAAM,KACX,iBAyFF,CAAC"}
1
+ {"version":3,"file":"mdz_to_svelte.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_to_svelte.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,UAAU,CAAC;AAGtC;;GAEG;AACH,MAAM,WAAW,iBAAiB;IACjC,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IAEf,yDAAyD;IACzD,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAA;KAAC,CAAC,CAAC;IAEhE,yEAAyE;IACzE,qBAAqB,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,aAAa,GACzB,OAAO,KAAK,CAAC,OAAO,CAAC,EACrB,YAAY,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,UAAU,WAAW,CAAC,MAAM,CAAC,EAC7B,OAAO,MAAM,KACX,iBAyFF,CAAC"}
@@ -10,19 +10,19 @@
10
10
  import { UnreachableError } from '@fuzdev/fuz_util/error.js';
11
11
  import { escape_svelte_text } from '@fuzdev/fuz_util/svelte_preprocess_helpers.js';
12
12
  import { escape_js_string } from '@fuzdev/fuz_util/string.js';
13
- import { resolve_relative_path } from './mdz.js';
13
+ import { resolve_relative_path } from './mdz_helpers.js';
14
14
  /**
15
15
  * Converts an array of `MdzNode` to a Svelte markup string.
16
16
  *
17
17
  * Each node type produces output matching what `MdzNodeView.svelte` renders at runtime.
18
18
  * Collects required imports and flags unconfigured component/element references.
19
19
  *
20
- * @param nodes Parsed mdz nodes to render.
21
- * @param components Component name to import path mapping (e.g., `{Alert: '$lib/Alert.svelte'}`).
20
+ * @param nodes - parsed mdz nodes to render
21
+ * @param components - Component name to import path mapping (e.g., `{Alert: '$lib/Alert.svelte'}`)
22
22
  * If content references a component not in this map, `has_unconfigured_tags` is set.
23
- * @param elements Allowed HTML element names (e.g., `new Set(['aside', 'details'])`).
23
+ * @param elements - Allowed HTML element names (e.g., `new Set(['aside', 'details'])`)
24
24
  * If content references an element not in this set, `has_unconfigured_tags` is set.
25
- * @param base Base path for resolving relative links (e.g., `'/docs/mdz/'`).
25
+ * @param base - Base path for resolving relative links (e.g., `'/docs/mdz/'`)
26
26
  * When provided, relative references (`./`, `../`) are resolved to absolute paths
27
27
  * and passed through `resolve()`. Trailing slash recommended.
28
28
  */
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @module
8
8
  */
9
- import { extract_single_tag } from './mdz_helpers.js';
9
+ import { extract_single_tag, mdz_heading_id } from './mdz_helpers.js';
10
10
  import { MdzLexer, } from './mdz_lexer.js';
11
11
  /**
12
12
  * Parses text to an array of `MdzNode` using a two-phase lexer+parser approach.
@@ -91,8 +91,9 @@ class MdzTokenParser {
91
91
  if (node)
92
92
  children.push(node);
93
93
  }
94
- const end = children.length > 0 ? children[children.length - 1].end : start + level + 1;
95
- return { type: 'Heading', level, children, start, end };
94
+ const merged = this.#merge_adjacent_text(children);
95
+ const end = merged.length > 0 ? merged[merged.length - 1].end : start + level + 1;
96
+ return { type: 'Heading', level, id: mdz_heading_id(merged), children: merged, start, end };
96
97
  }
97
98
  #parse_inline() {
98
99
  if (this.#index >= this.#tokens.length)