@fuzdev/fuz_ui 0.198.1 → 0.200.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.
Files changed (113) hide show
  1. package/dist/ApiDeclarationList.svelte +2 -1
  2. package/dist/ApiDeclarationList.svelte.d.ts.map +1 -1
  3. package/dist/ApiIndex.svelte +2 -2
  4. package/dist/ApiModule.svelte +3 -3
  5. package/dist/DeclarationDetail.svelte +145 -90
  6. package/dist/DeclarationDetail.svelte.d.ts.map +1 -1
  7. package/dist/LibraryDetail.svelte +14 -16
  8. package/dist/LibraryDetail.svelte.d.ts.map +1 -1
  9. package/dist/LibrarySummary.svelte +12 -12
  10. package/dist/Mdz.svelte +2 -2
  11. package/dist/MdzNodeView.svelte +8 -6
  12. package/dist/MdzNodeView.svelte.d.ts.map +1 -1
  13. package/dist/MdzRoot.svelte +30 -0
  14. package/dist/MdzRoot.svelte.d.ts +12 -0
  15. package/dist/MdzRoot.svelte.d.ts.map +1 -0
  16. package/dist/MdzStream.svelte +32 -0
  17. package/dist/MdzStream.svelte.d.ts +12 -0
  18. package/dist/MdzStream.svelte.d.ts.map +1 -0
  19. package/dist/MdzStreamNodeView.svelte +106 -0
  20. package/dist/MdzStreamNodeView.svelte.d.ts +9 -0
  21. package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -0
  22. package/dist/ProjectLinks.svelte +1 -1
  23. package/dist/api_search.svelte.d.ts.map +1 -1
  24. package/dist/api_search.svelte.js +4 -2
  25. package/dist/declaration.svelte.d.ts +11 -0
  26. package/dist/declaration.svelte.d.ts.map +1 -1
  27. package/dist/declaration.svelte.js +15 -3
  28. package/dist/library.svelte.d.ts +9 -70
  29. package/dist/library.svelte.d.ts.map +1 -1
  30. package/dist/library.svelte.js +24 -18
  31. package/dist/library_helpers.d.ts +3 -3
  32. package/dist/library_helpers.js +3 -3
  33. package/dist/mdz.d.ts.map +1 -1
  34. package/dist/mdz.js +37 -29
  35. package/dist/mdz_components.d.ts +37 -23
  36. package/dist/mdz_components.d.ts.map +1 -1
  37. package/dist/mdz_components.js +16 -4
  38. package/dist/mdz_helpers.d.ts +46 -14
  39. package/dist/mdz_helpers.d.ts.map +1 -1
  40. package/dist/mdz_helpers.js +189 -56
  41. package/dist/mdz_lexer.d.ts.map +1 -1
  42. package/dist/mdz_lexer.js +18 -22
  43. package/dist/mdz_opcodes.d.ts +174 -0
  44. package/dist/mdz_opcodes.d.ts.map +1 -0
  45. package/dist/mdz_opcodes.js +14 -0
  46. package/dist/mdz_opcodes_to_nodes.d.ts +20 -0
  47. package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -0
  48. package/dist/mdz_opcodes_to_nodes.js +332 -0
  49. package/dist/mdz_stream_parser.d.ts +80 -0
  50. package/dist/mdz_stream_parser.d.ts.map +1 -0
  51. package/dist/mdz_stream_parser.js +354 -0
  52. package/dist/mdz_stream_parser_block.d.ts +76 -0
  53. package/dist/mdz_stream_parser_block.d.ts.map +1 -0
  54. package/dist/mdz_stream_parser_block.js +458 -0
  55. package/dist/mdz_stream_parser_inline.d.ts +52 -0
  56. package/dist/mdz_stream_parser_inline.d.ts.map +1 -0
  57. package/dist/mdz_stream_parser_inline.js +378 -0
  58. package/dist/mdz_stream_parser_link.d.ts +22 -0
  59. package/dist/mdz_stream_parser_link.d.ts.map +1 -0
  60. package/dist/mdz_stream_parser_link.js +215 -0
  61. package/dist/mdz_stream_parser_state.d.ts +187 -0
  62. package/dist/mdz_stream_parser_state.d.ts.map +1 -0
  63. package/dist/mdz_stream_parser_state.js +398 -0
  64. package/dist/mdz_stream_parser_text.d.ts +14 -0
  65. package/dist/mdz_stream_parser_text.d.ts.map +1 -0
  66. package/dist/mdz_stream_parser_text.js +50 -0
  67. package/dist/mdz_stream_parser_url.d.ts +60 -0
  68. package/dist/mdz_stream_parser_url.d.ts.map +1 -0
  69. package/dist/mdz_stream_parser_url.js +307 -0
  70. package/dist/mdz_stream_state.svelte.d.ts +44 -0
  71. package/dist/mdz_stream_state.svelte.d.ts.map +1 -0
  72. package/dist/mdz_stream_state.svelte.js +357 -0
  73. package/dist/mdz_token_parser.d.ts.map +1 -1
  74. package/dist/mdz_token_parser.js +8 -39
  75. package/dist/module.svelte.d.ts +2 -0
  76. package/dist/module.svelte.d.ts.map +1 -1
  77. package/dist/module.svelte.js +3 -1
  78. package/dist/site.svelte.d.ts +13 -0
  79. package/dist/site.svelte.d.ts.map +1 -1
  80. package/dist/site.svelte.js +8 -2
  81. package/dist/tsdoc_mdz.d.ts +2 -2
  82. package/dist/tsdoc_mdz.js +2 -2
  83. package/dist/vite_plugin_pkg_json.d.ts +63 -0
  84. package/dist/vite_plugin_pkg_json.d.ts.map +1 -0
  85. package/dist/vite_plugin_pkg_json.js +126 -0
  86. package/package.json +8 -7
  87. package/src/lib/api_search.svelte.ts +4 -2
  88. package/src/lib/declaration.svelte.ts +18 -3
  89. package/src/lib/library.svelte.ts +37 -20
  90. package/src/lib/library_helpers.ts +3 -3
  91. package/src/lib/mdz.ts +38 -29
  92. package/src/lib/mdz_components.ts +40 -19
  93. package/src/lib/mdz_helpers.ts +199 -56
  94. package/src/lib/mdz_lexer.ts +18 -20
  95. package/src/lib/mdz_opcodes.ts +205 -0
  96. package/src/lib/mdz_opcodes_to_nodes.ts +375 -0
  97. package/src/lib/mdz_stream_parser.ts +415 -0
  98. package/src/lib/mdz_stream_parser_block.ts +532 -0
  99. package/src/lib/mdz_stream_parser_inline.ts +414 -0
  100. package/src/lib/mdz_stream_parser_link.ts +271 -0
  101. package/src/lib/mdz_stream_parser_state.ts +539 -0
  102. package/src/lib/mdz_stream_parser_text.ts +77 -0
  103. package/src/lib/mdz_stream_parser_url.ts +365 -0
  104. package/src/lib/mdz_stream_state.svelte.ts +387 -0
  105. package/src/lib/mdz_token_parser.ts +13 -40
  106. package/src/lib/module.svelte.ts +5 -1
  107. package/src/lib/site.svelte.ts +17 -2
  108. package/src/lib/tsdoc_mdz.ts +2 -2
  109. package/src/lib/vite_plugin_pkg_json.ts +142 -0
  110. package/dist/package_helpers.d.ts +0 -150
  111. package/dist/package_helpers.d.ts.map +0 -1
  112. package/dist/package_helpers.js +0 -179
  113. package/src/lib/package_helpers.ts +0 -186
@@ -1,22 +1,38 @@
1
1
  import type { Component } from 'svelte';
2
2
  /**
3
- * Context for providing custom mdz components.
4
- * Must be set by the application using mdz.
3
+ * Component registry for custom Svelte components that can be used in mdz content.
4
+ *
5
+ * For example, registering 'Alert' allows using `<Alert>...</Alert>` in mdz content.
6
+ *
7
+ * The Map values are the Svelte component constructors.
8
+ */
9
+ export type MdzComponents = Map<string, Component<any, any>>;
10
+ /**
11
+ * Element registry for HTML elements that can be used in mdz content.
12
+ *
13
+ * For example, registering 'div' allows using `<div>...</div>` in mdz content.
14
+ *
15
+ * The Map values are boolean placeholders for future configuration options.
16
+ */
17
+ export type MdzElements = Map<string, boolean>;
18
+ /**
19
+ * Context for providing custom mdz components via a getter function.
20
+ * Set to a getter (e.g., `() => components`) so changes are reflected reactively.
5
21
  */
6
22
  export declare const mdz_components_context: {
7
- get: (error_message?: string) => MdzComponents;
8
- get_maybe: () => MdzComponents | undefined;
9
- set: (value: MdzComponents) => MdzComponents;
23
+ get: (error_message?: string) => () => MdzComponents | undefined;
24
+ get_maybe: () => (() => MdzComponents | undefined) | undefined;
25
+ set: (value: () => MdzComponents | undefined) => () => MdzComponents | undefined;
10
26
  };
11
27
  /**
12
- * Context for providing allowed HTML elements.
13
- * Must be set by the application using mdz.
28
+ * Context for providing allowed HTML elements via a getter function.
29
+ * Set to a getter (e.g., `() => elements`) so changes are reflected reactively.
14
30
  * By default, no HTML elements are allowed.
15
31
  */
16
32
  export declare const mdz_elements_context: {
17
- get: (error_message?: string) => MdzElements;
18
- get_maybe: () => MdzElements | undefined;
19
- set: (value: MdzElements) => MdzElements;
33
+ get: (error_message?: string) => () => MdzElements | undefined;
34
+ get_maybe: () => (() => MdzElements | undefined) | undefined;
35
+ set: (value: () => MdzElements | undefined) => () => MdzElements | undefined;
20
36
  };
21
37
  /**
22
38
  * Context for providing a base path getter for resolving relative links in mdz content.
@@ -30,20 +46,18 @@ export declare const mdz_base_context: {
30
46
  get_maybe: () => (() => string | undefined) | undefined;
31
47
  set: (value: () => string | undefined) => () => string | undefined;
32
48
  };
49
+ interface MdzGetterContext<T> {
50
+ get_maybe(): (() => T | undefined) | undefined;
51
+ set(value: () => T | undefined): unknown;
52
+ }
33
53
  /**
34
- * Component registry for custom Svelte components that can be used in mdz content.
35
- *
36
- * For example, registering 'Alert' allows using `<Alert>...</Alert>` in mdz content.
37
- *
38
- * The Map values are the Svelte component constructors.
39
- */
40
- export type MdzComponents = Map<string, Component<any, any>>;
41
- /**
42
- * Element registry for HTML elements that can be used in mdz content.
54
+ * Set an mdz context to a getter that prefers the local `value_fn` and falls
55
+ * back to the ancestor's value when `value_fn()` returns `undefined`.
43
56
  *
44
- * For example, registering 'div' allows using `<div>...</div>` in mdz content.
45
- *
46
- * The Map values are boolean placeholders for future configuration options.
57
+ * Pass `value_fn` as a getter (not a snapshot) so prop changes remain reactive.
58
+ * The ancestor lookup happens once at component init — that's intentional, it
59
+ * captures the surrounding scope's context at mount time.
47
60
  */
48
- export type MdzElements = Map<string, boolean>;
61
+ export declare const set_mdz_context_with_fallback: <T>(context: MdzGetterContext<T>, value_fn: () => T | undefined) => void;
62
+ export {};
49
63
  //# sourceMappingURL=mdz_components.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mdz_components.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_components.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,QAAQ,CAAC;AAItC;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;CAAkC,CAAC;AAEtE;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;;;;CAAgC,CAAC;AAElE;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB;2CAAwB,MAAM,GAAG,SAAS;4BAAlB,MAAM,GAAG,SAAS;uBAAlB,MAAM,GAAG,SAAS,WAAlB,MAAM,GAAG,SAAS;CAAG,CAAC;AAE3E;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAE7D;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC"}
1
+ {"version":3,"file":"mdz_components.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_components.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,QAAQ,CAAC;AAItC;;;;;;GAMG;AACH,MAAM,MAAM,aAAa,GAAG,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;AAE7D;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE/C;;;GAGG;AACH,eAAO,MAAM,sBAAsB;2CAAwB,aAAa,GAAG,SAAS;4BAAzB,aAAa,GAAG,SAAS;uBAAzB,aAAa,GAAG,SAAS,WAAzB,aAAa,GAAG,SAAS;CAAG,CAAC;AAExF;;;;GAIG;AACH,eAAO,MAAM,oBAAoB;2CAAwB,WAAW,GAAG,SAAS;4BAAvB,WAAW,GAAG,SAAS;uBAAvB,WAAW,GAAG,SAAS,WAAvB,WAAW,GAAG,SAAS;CAAG,CAAC;AAEpF;;;;;;GAMG;AACH,eAAO,MAAM,gBAAgB;2CAAwB,MAAM,GAAG,SAAS;4BAAlB,MAAM,GAAG,SAAS;uBAAlB,MAAM,GAAG,SAAS,WAAlB,MAAM,GAAG,SAAS;CAAG,CAAC;AAE3E,UAAU,gBAAgB,CAAC,CAAC;IAC3B,SAAS,IAAI,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC,GAAG,SAAS,CAAC;IAC/C,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,GAAG,SAAS,GAAG,OAAO,CAAC;CACzC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,6BAA6B,GAAI,CAAC,EAC9C,SAAS,gBAAgB,CAAC,CAAC,CAAC,EAC5B,UAAU,MAAM,CAAC,GAAG,SAAS,KAC3B,IAGF,CAAC"}
@@ -1,12 +1,12 @@
1
1
  import { create_context } from './context_helpers.js';
2
2
  /**
3
- * Context for providing custom mdz components.
4
- * Must be set by the application using mdz.
3
+ * Context for providing custom mdz components via a getter function.
4
+ * Set to a getter (e.g., `() => components`) so changes are reflected reactively.
5
5
  */
6
6
  export const mdz_components_context = create_context();
7
7
  /**
8
- * Context for providing allowed HTML elements.
9
- * Must be set by the application using mdz.
8
+ * Context for providing allowed HTML elements via a getter function.
9
+ * Set to a getter (e.g., `() => elements`) so changes are reflected reactively.
10
10
  * By default, no HTML elements are allowed.
11
11
  */
12
12
  export const mdz_elements_context = create_context();
@@ -18,3 +18,15 @@ export const mdz_elements_context = create_context();
18
18
  * When not set, relative paths use raw hrefs (browser resolves them).
19
19
  */
20
20
  export const mdz_base_context = create_context();
21
+ /**
22
+ * Set an mdz context to a getter that prefers the local `value_fn` and falls
23
+ * back to the ancestor's value when `value_fn()` returns `undefined`.
24
+ *
25
+ * Pass `value_fn` as a getter (not a snapshot) so prop changes remain reactive.
26
+ * The ancestor lookup happens once at component init — that's intentional, it
27
+ * captures the surrounding scope's context at mount time.
28
+ */
29
+ export const set_mdz_context_with_fallback = (context, value_fn) => {
30
+ const ancestor = context.get_maybe();
31
+ context.set(() => value_fn() ?? ancestor?.());
32
+ };
@@ -47,6 +47,23 @@ export declare const MIN_CODEBLOCK_BACKTICKS = 3;
47
47
  export declare const MAX_HEADING_LEVEL = 6;
48
48
  export declare const HTTPS_PREFIX_LENGTH = 8;
49
49
  export declare const HTTP_PREFIX_LENGTH = 7;
50
+ export declare const H_LOWER = 104;
51
+ export declare const H_UPPER = 72;
52
+ /**
53
+ * Lowercase an ASCII letter code; passes other chars through.
54
+ * Mirrors the `[A-Z] → [a-z]` shift without touching non-ASCII.
55
+ */
56
+ export declare const ascii_to_lower: (char_code: number) => number;
57
+ /**
58
+ * Case-insensitive URL scheme prefix check. Returns the prefix length
59
+ * (8 for `https://`, 7 for `http://`) or 0 if neither matches at `pos`.
60
+ *
61
+ * URI schemes are case-insensitive per RFC 3986 §3.1, and mobile keyboards
62
+ * routinely auto-capitalize the first letter of a sentence, producing
63
+ * `Https://` etc. The original case is preserved in the emitted reference —
64
+ * browsers normalize schemes themselves.
65
+ */
66
+ export declare const match_url_prefix_case_insensitive: (s: string, pos: number) => 0 | 7 | 8;
50
67
  /**
51
68
  * Check if character code is a letter (A-Z, a-z).
52
69
  */
@@ -59,30 +76,26 @@ export declare const is_tag_name_char: (char_code: number) => boolean;
59
76
  * Check if character is part of a word for word boundary detection.
60
77
  * Used to prevent intraword emphasis with `_` and `~` delimiters.
61
78
  *
62
- * Formatting delimiters (`*`, `_`, `~`) are NOT word characters - they're transparent.
63
- * Only alphanumeric characters (A-Z, a-z, 0-9) are considered word characters.
79
+ * Only alphanumeric characters (A-Z, a-z, 0-9) are word characters.
80
+ * Formatting delimiters (`*`, `_`, `~`) fall outside all three ranges,
81
+ * so they're naturally excluded without explicit checks.
64
82
  *
65
83
  * This prevents false positives with snake_case identifiers while allowing
66
84
  * adjacent formatting like `**bold**_italic_`.
67
85
  */
68
86
  export declare const is_word_char: (char_code: number) => boolean;
69
- /**
70
- * Check if character code is valid in URI path per RFC 3986.
71
- * Validates against the `pchar` production plus path/query/fragment separators.
72
- *
73
- * Valid characters:
74
- * - unreserved: A-Z a-z 0-9 - . _ ~
75
- * - sub-delims: ! $ & ' ( ) * + , ; =
76
- * - path allowed: : @
77
- * - separators: / ? #
78
- * - percent-encoding: %
79
- */
80
87
  export declare const is_valid_path_char: (char_code: number) => boolean;
81
88
  /**
82
89
  * Trim trailing punctuation from URL/path per RFC 3986 and GFM rules.
83
90
  * - Trims simple trailing: .,;:!?]
84
91
  * - Balanced logic for () only (valid in path components)
85
- * - Invalid chars like [] {} are already stopped by whitelist, but ] trimmed as fallback
92
+ *
93
+ * Note on `]`: the mdz parsers (`mdz.ts`, `mdz_stream_parser_url.ts`,
94
+ * `mdz_lexer.ts`) scan URL/path chars through `is_valid_path_char`, which
95
+ * already rejects `]` — so it can never reach this function via parser flow.
96
+ * The `]` branch here is for external callers using this helper directly on
97
+ * arbitrary URL-ish input (it is part of the published `@fuzdev/fuz_ui`
98
+ * surface, see `mdz_helpers.test.ts` for direct coverage).
86
99
  *
87
100
  * Optimized to avoid O(n²) string slicing - tracks end index and slices once at the end.
88
101
  */
@@ -114,7 +127,14 @@ export declare const mdz_text_content: (nodes: Array<MdzNode>) => string;
114
127
  * For case-preserving IDs (e.g. API declarations), see `docs_slugify` in `docs_helpers.svelte.ts`.
115
128
  */
116
129
  export declare const mdz_heading_id: (nodes: Array<MdzNode>) => string;
130
+ /**
131
+ * Generates a lowercase slug id for a heading from plain text content.
132
+ * Used by the streaming parser which tracks text content directly
133
+ * rather than building `MdzNode[]` trees.
134
+ */
135
+ export declare const mdz_heading_id_from_text: (text: string) => string;
117
136
  export declare const mdz_is_url: (s: string) => boolean;
137
+ export declare const mdz_is_safe_reference: (reference: string) => boolean;
118
138
  /**
119
139
  * Resolves a relative path (`./` or `../`) against a base path.
120
140
  * The base is treated as a directory regardless of trailing slash
@@ -128,5 +148,17 @@ export declare const mdz_is_url: (s: string) => boolean;
128
148
  * @returns an absolute resolved path (e.g., `'/docs/mdz/grammar'`)
129
149
  */
130
150
  export declare const resolve_relative_path: (reference: string, base: string) => string;
151
+ /**
152
+ * Push a node into a children array, coalescing with the previous Text node.
153
+ * Mutates `prev.content` and `prev.end` when both are Text, avoiding array growth
154
+ * and an extra allocation. Callers must own `dest` and not retain references to
155
+ * the prior last element across the call.
156
+ */
157
+ export declare const mdz_push_merging_text: (dest: Array<MdzNode>, node: MdzNode) => void;
158
+ /**
159
+ * Return a new array with adjacent Text nodes merged into single nodes.
160
+ * Fast path: returns the original array when no merging is needed.
161
+ */
162
+ export declare const mdz_merge_adjacent_text: (nodes: Array<MdzNode>) => Array<MdzNode>;
131
163
  export declare const extract_single_tag: (nodes: Array<MdzNode>) => MdzComponentNode | MdzElementNode | null;
132
164
  //# 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":"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"}
1
+ {"version":3,"file":"mdz_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_helpers.ts"],"names":[],"mappings":"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"}
@@ -51,6 +51,47 @@ export const MIN_CODEBLOCK_BACKTICKS = 3; // Code blocks require minimum 3 backt
51
51
  export const MAX_HEADING_LEVEL = 6; // Headings support levels 1-6
52
52
  export const HTTPS_PREFIX_LENGTH = 8; // Length of "https://"
53
53
  export const HTTP_PREFIX_LENGTH = 7; // Length of "http://"
54
+ export const H_LOWER = 104; // h
55
+ export const H_UPPER = 72; // H
56
+ /**
57
+ * Lowercase an ASCII letter code; passes other chars through.
58
+ * Mirrors the `[A-Z] → [a-z]` shift without touching non-ASCII.
59
+ */
60
+ export const ascii_to_lower = (char_code) => char_code >= A_UPPER && char_code <= Z_UPPER ? char_code + 32 : char_code;
61
+ /**
62
+ * Case-insensitive URL scheme prefix check. Returns the prefix length
63
+ * (8 for `https://`, 7 for `http://`) or 0 if neither matches at `pos`.
64
+ *
65
+ * URI schemes are case-insensitive per RFC 3986 §3.1, and mobile keyboards
66
+ * routinely auto-capitalize the first letter of a sentence, producing
67
+ * `Https://` etc. The original case is preserved in the emitted reference —
68
+ * browsers normalize schemes themselves.
69
+ */
70
+ export const match_url_prefix_case_insensitive = (s, pos) => {
71
+ if (pos + HTTP_PREFIX_LENGTH > s.length)
72
+ return 0;
73
+ // h | H
74
+ if (ascii_to_lower(s.charCodeAt(pos)) !== H_LOWER)
75
+ return 0;
76
+ // 'ttps://' (7 chars) or 'ttp://' (6 chars)
77
+ if (ascii_to_lower(s.charCodeAt(pos + 1)) === 116 /* t */ &&
78
+ ascii_to_lower(s.charCodeAt(pos + 2)) === 116 /* t */ &&
79
+ ascii_to_lower(s.charCodeAt(pos + 3)) === 112 /* p */) {
80
+ if (pos + HTTPS_PREFIX_LENGTH <= s.length &&
81
+ ascii_to_lower(s.charCodeAt(pos + 4)) === 115 /* s */ &&
82
+ s.charCodeAt(pos + 5) === COLON &&
83
+ s.charCodeAt(pos + 6) === SLASH &&
84
+ s.charCodeAt(pos + 7) === SLASH) {
85
+ return HTTPS_PREFIX_LENGTH;
86
+ }
87
+ if (s.charCodeAt(pos + 4) === COLON &&
88
+ s.charCodeAt(pos + 5) === SLASH &&
89
+ s.charCodeAt(pos + 6) === SLASH) {
90
+ return HTTP_PREFIX_LENGTH;
91
+ }
92
+ }
93
+ return 0;
94
+ };
54
95
  /**
55
96
  * Check if character code is a letter (A-Z, a-z).
56
97
  */
@@ -66,22 +107,19 @@ export const is_tag_name_char = (char_code) => is_letter(char_code) ||
66
107
  * Check if character is part of a word for word boundary detection.
67
108
  * Used to prevent intraword emphasis with `_` and `~` delimiters.
68
109
  *
69
- * Formatting delimiters (`*`, `_`, `~`) are NOT word characters - they're transparent.
70
- * Only alphanumeric characters (A-Z, a-z, 0-9) are considered word characters.
110
+ * Only alphanumeric characters (A-Z, a-z, 0-9) are word characters.
111
+ * Formatting delimiters (`*`, `_`, `~`) fall outside all three ranges,
112
+ * so they're naturally excluded without explicit checks.
71
113
  *
72
114
  * This prevents false positives with snake_case identifiers while allowing
73
115
  * adjacent formatting like `**bold**_italic_`.
74
116
  */
75
- export const is_word_char = (char_code) => {
76
- if (char_code === ASTERISK || char_code === UNDERSCORE || char_code === TILDE)
77
- return false;
78
- return ((char_code >= A_UPPER && char_code <= Z_UPPER) ||
79
- (char_code >= A_LOWER && char_code <= Z_LOWER) ||
80
- (char_code >= ZERO && char_code <= NINE));
81
- };
117
+ export const is_word_char = (char_code) => (char_code >= A_UPPER && char_code <= Z_UPPER) ||
118
+ (char_code >= A_LOWER && char_code <= Z_LOWER) ||
119
+ (char_code >= ZERO && char_code <= NINE);
82
120
  /**
83
- * Check if character code is valid in URI path per RFC 3986.
84
- * Validates against the `pchar` production plus path/query/fragment separators.
121
+ * Lookup table for valid URI path characters per RFC 3986.
122
+ * Replaces a 16-comparison chain with a single array access.
85
123
  *
86
124
  * Valid characters:
87
125
  * - unreserved: A-Z a-z 0-9 - . _ ~
@@ -90,41 +128,59 @@ export const is_word_char = (char_code) => {
90
128
  * - separators: / ? #
91
129
  * - percent-encoding: %
92
130
  */
93
- export const is_valid_path_char = (char_code) => (char_code >= A_UPPER && char_code <= Z_UPPER) ||
94
- (char_code >= A_LOWER && char_code <= Z_LOWER) ||
95
- (char_code >= ZERO && char_code <= NINE) ||
96
- char_code === HYPHEN ||
97
- char_code === PERIOD ||
98
- char_code === UNDERSCORE ||
99
- char_code === TILDE ||
100
- char_code === EXCLAMATION ||
101
- char_code === DOLLAR ||
102
- char_code === AMPERSAND ||
103
- char_code === APOSTROPHE ||
104
- char_code === LEFT_PAREN ||
105
- char_code === RIGHT_PAREN ||
106
- char_code === ASTERISK ||
107
- char_code === PLUS ||
108
- char_code === COMMA ||
109
- char_code === SEMICOLON ||
110
- char_code === EQUALS ||
111
- char_code === COLON ||
112
- char_code === AT ||
113
- char_code === SLASH ||
114
- char_code === QUESTION ||
115
- char_code === HASH ||
116
- char_code === PERCENT;
131
+ const PATH_CHAR_TABLE = (() => {
132
+ const t = new Uint8Array(128);
133
+ for (let i = A_UPPER; i <= Z_UPPER; i++)
134
+ t[i] = 1;
135
+ for (let i = A_LOWER; i <= Z_LOWER; i++)
136
+ t[i] = 1;
137
+ for (let i = ZERO; i <= NINE; i++)
138
+ t[i] = 1;
139
+ // unreserved: - . _ ~
140
+ t[HYPHEN] = 1;
141
+ t[PERIOD] = 1;
142
+ t[UNDERSCORE] = 1;
143
+ t[TILDE] = 1;
144
+ // sub-delims: ! $ & ' ( ) * + , ; =
145
+ t[EXCLAMATION] = 1;
146
+ t[DOLLAR] = 1;
147
+ t[AMPERSAND] = 1;
148
+ t[APOSTROPHE] = 1;
149
+ t[LEFT_PAREN] = 1;
150
+ t[RIGHT_PAREN] = 1;
151
+ t[ASTERISK] = 1;
152
+ t[PLUS] = 1;
153
+ t[COMMA] = 1;
154
+ t[SEMICOLON] = 1;
155
+ t[EQUALS] = 1;
156
+ // path allowed: : @
157
+ t[COLON] = 1;
158
+ t[AT] = 1;
159
+ // separators: / ? #
160
+ t[SLASH] = 1;
161
+ t[QUESTION] = 1;
162
+ t[HASH] = 1;
163
+ // percent-encoding: %
164
+ t[PERCENT] = 1;
165
+ return t;
166
+ })();
167
+ export const is_valid_path_char = (char_code) => char_code < 128 && PATH_CHAR_TABLE[char_code] === 1;
117
168
  /**
118
169
  * Trim trailing punctuation from URL/path per RFC 3986 and GFM rules.
119
170
  * - Trims simple trailing: .,;:!?]
120
171
  * - Balanced logic for () only (valid in path components)
121
- * - Invalid chars like [] {} are already stopped by whitelist, but ] trimmed as fallback
172
+ *
173
+ * Note on `]`: the mdz parsers (`mdz.ts`, `mdz_stream_parser_url.ts`,
174
+ * `mdz_lexer.ts`) scan URL/path chars through `is_valid_path_char`, which
175
+ * already rejects `]` — so it can never reach this function via parser flow.
176
+ * The `]` branch here is for external callers using this helper directly on
177
+ * arbitrary URL-ish input (it is part of the published `@fuzdev/fuz_ui`
178
+ * surface, see `mdz_helpers.test.ts` for direct coverage).
122
179
  *
123
180
  * Optimized to avoid O(n²) string slicing - tracks end index and slices once at the end.
124
181
  */
125
182
  export const trim_trailing_punctuation = (url) => {
126
183
  let end = url.length;
127
- // Trim simple trailing punctuation (] as fallback - whitelist should prevent it)
128
184
  while (end > 0) {
129
185
  const last_char = url.charCodeAt(end - 1);
130
186
  if (last_char === PERIOD ||
@@ -141,25 +197,28 @@ export const trim_trailing_punctuation = (url) => {
141
197
  }
142
198
  }
143
199
  // Handle balanced parentheses ONLY (parens are valid in URI path components)
144
- // Count parentheses in the trimmed portion
145
- let open_count = 0;
146
- let close_count = 0;
147
- for (let i = 0; i < end; i++) {
148
- const char = url.charCodeAt(i);
149
- if (char === LEFT_PAREN)
150
- open_count++;
151
- if (char === RIGHT_PAREN)
152
- close_count++;
153
- }
154
- // Trim unmatched trailing closing parens
155
- while (end > 0 && close_count > open_count) {
156
- const last_char = url.charCodeAt(end - 1);
157
- if (last_char === RIGHT_PAREN) {
158
- end--;
159
- close_count--;
200
+ // Only count parens when the trimmed URL ends with ')' — otherwise the
201
+ // trailing-paren loop below can't trim anything.
202
+ if (end > 0 && url.charCodeAt(end - 1) === RIGHT_PAREN) {
203
+ let open_count = 0;
204
+ let close_count = 0;
205
+ for (let i = 0; i < end; i++) {
206
+ const char = url.charCodeAt(i);
207
+ if (char === LEFT_PAREN)
208
+ open_count++;
209
+ if (char === RIGHT_PAREN)
210
+ close_count++;
160
211
  }
161
- else {
162
- break;
212
+ // Trim unmatched trailing closing parens
213
+ while (end > 0 && close_count > open_count) {
214
+ const last_char = url.charCodeAt(end - 1);
215
+ if (last_char === RIGHT_PAREN) {
216
+ end--;
217
+ close_count--;
218
+ }
219
+ else {
220
+ break;
221
+ }
163
222
  }
164
223
  }
165
224
  // Return original string if no trimming, otherwise slice once
@@ -228,13 +287,26 @@ export const mdz_text_content = (nodes) => nodes
228
287
  * For case-preserving IDs (e.g. API declarations), see `docs_slugify` in `docs_helpers.svelte.ts`.
229
288
  */
230
289
  export const mdz_heading_id = (nodes) => slugify(mdz_text_content(nodes), false);
290
+ /**
291
+ * Generates a lowercase slug id for a heading from plain text content.
292
+ * Used by the streaming parser which tracks text content directly
293
+ * rather than building `MdzNode[]` trees.
294
+ */
295
+ export const mdz_heading_id_from_text = (text) => slugify(text, false);
231
296
  /**
232
297
  * Check if a string is a URL (`https://` or `http://`).
233
298
  * Requires at least one valid character after the protocol.
234
299
  * Rejects whitespace and characters that can't start a valid hostname.
235
300
  */
236
- const URL_PATTERN = /^https?:\/\/[^\s)\]}<>.,:/?#!]/;
301
+ const URL_PATTERN = /^https?:\/\/[^\s)\]}<>.,:/?#!]/i;
237
302
  export const mdz_is_url = (s) => URL_PATTERN.test(s);
303
+ /**
304
+ * Check if a link reference is safe to use as an `href` attribute.
305
+ * References without a colon are always safe (paths, fragments, queries).
306
+ * References with a colon must use `http(s)://` — rejects `javascript:`, `data:`, etc.
307
+ */
308
+ const SAFE_PROTOCOL_PATTERN = /^https?:\/\//i;
309
+ export const mdz_is_safe_reference = (reference) => !reference.includes(':') || SAFE_PROTOCOL_PATTERN.test(reference);
238
310
  /**
239
311
  * Resolves a relative path (`./` or `../`) against a base path.
240
312
  * The base is treated as a directory regardless of trailing slash
@@ -269,6 +341,67 @@ export const resolve_relative_path = (reference, base) => {
269
341
  segments.push('');
270
342
  return segments.join('/');
271
343
  };
344
+ /**
345
+ * Push a node into a children array, coalescing with the previous Text node.
346
+ * Mutates `prev.content` and `prev.end` when both are Text, avoiding array growth
347
+ * and an extra allocation. Callers must own `dest` and not retain references to
348
+ * the prior last element across the call.
349
+ */
350
+ export const mdz_push_merging_text = (dest, node) => {
351
+ if (node.type === 'Text') {
352
+ const last = dest[dest.length - 1];
353
+ if (last?.type === 'Text') {
354
+ last.content += node.content;
355
+ last.end = node.end;
356
+ return;
357
+ }
358
+ }
359
+ dest.push(node);
360
+ };
361
+ /**
362
+ * Return a new array with adjacent Text nodes merged into single nodes.
363
+ * Fast path: returns the original array when no merging is needed.
364
+ */
365
+ export const mdz_merge_adjacent_text = (nodes) => {
366
+ if (nodes.length <= 1)
367
+ return nodes;
368
+ let needs_merge = false;
369
+ for (let i = 1; i < nodes.length; i++) {
370
+ if (nodes[i - 1].type === 'Text' && nodes[i].type === 'Text') {
371
+ needs_merge = true;
372
+ break;
373
+ }
374
+ }
375
+ if (!needs_merge)
376
+ return nodes;
377
+ const merged = [];
378
+ let pending = null;
379
+ for (const node of nodes) {
380
+ if (node.type === 'Text') {
381
+ if (pending) {
382
+ pending = {
383
+ type: 'Text',
384
+ content: pending.content + node.content,
385
+ start: pending.start,
386
+ end: node.end,
387
+ };
388
+ }
389
+ else {
390
+ pending = { ...node };
391
+ }
392
+ }
393
+ else {
394
+ if (pending) {
395
+ merged.push(pending);
396
+ pending = null;
397
+ }
398
+ merged.push(node);
399
+ }
400
+ }
401
+ if (pending)
402
+ merged.push(pending);
403
+ return merged;
404
+ };
272
405
  export const extract_single_tag = (nodes) => {
273
406
  let tag = null;
274
407
  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,7 +6,7 @@
6
6
  *
7
7
  * @module
8
8
  */
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';
9
+ import { mdz_is_url, mdz_is_safe_reference, 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, match_url_prefix_case_insensitive, is_at_absolute_path, is_at_relative_path, } from './mdz_helpers.js';
10
10
  //
11
11
  // Lexer
12
12
  //
@@ -480,6 +480,12 @@ export class MdzLexer {
480
480
  return;
481
481
  }
482
482
  }
483
+ // Reject unsafe protocols (javascript:, data:, etc.) — `is_valid_path_char`
484
+ // permits `:` so we need an explicit filter here.
485
+ if (!mdz_is_safe_reference(reference)) {
486
+ this.#revert_tokens_from_link_open(start);
487
+ return;
488
+ }
483
489
  this.#index = close_paren + 1;
484
490
  const link_type = mdz_is_url(reference) ? 'external' : 'internal';
485
491
  this.#tokens.push({
@@ -629,7 +635,7 @@ export class MdzLexer {
629
635
  }
630
636
  }
631
637
  // Check for URL or internal path mid-text (char code guard avoids startsWith on every char)
632
- if ((char_code === 104 /* h */ && this.#is_at_url()) ||
638
+ if (((char_code === 104 /* h */ || char_code === 72) /* H */ && this.#is_at_url()) ||
633
639
  (char_code === SLASH && is_at_absolute_path(this.#text, this.#index)) ||
634
640
  (char_code === PERIOD && is_at_relative_path(this.#text, this.#index))) {
635
641
  break;
@@ -646,13 +652,8 @@ export class MdzLexer {
646
652
  // -- Auto-link tokenizers --
647
653
  #tokenize_auto_link_url() {
648
654
  const start = this.#index;
649
- // Consume protocol
650
- if (this.#match('https://')) {
651
- this.#index += HTTPS_PREFIX_LENGTH;
652
- }
653
- else if (this.#match('http://')) {
654
- this.#index += HTTP_PREFIX_LENGTH;
655
- }
655
+ // Consume protocol (case-insensitive match; original casing preserved in reference)
656
+ this.#index += match_url_prefix_case_insensitive(this.#text, this.#index);
656
657
  // Collect URL characters
657
658
  while (this.#index < this.#text.length) {
658
659
  const char_code = this.#text.charCodeAt(this.#index);
@@ -706,19 +707,14 @@ export class MdzLexer {
706
707
  return false;
707
708
  }
708
709
  #is_at_url() {
709
- if (this.#match('https://')) {
710
- if (this.#index + HTTPS_PREFIX_LENGTH >= this.#text.length)
711
- return false;
712
- const next_char = this.#text.charCodeAt(this.#index + HTTPS_PREFIX_LENGTH);
713
- return next_char !== SPACE && next_char !== NEWLINE;
714
- }
715
- if (this.#match('http://')) {
716
- if (this.#index + HTTP_PREFIX_LENGTH >= this.#text.length)
717
- return false;
718
- const next_char = this.#text.charCodeAt(this.#index + HTTP_PREFIX_LENGTH);
719
- return next_char !== SPACE && next_char !== NEWLINE;
720
- }
721
- return false;
710
+ // Scheme matching is case-insensitive (RFC 3986).
711
+ const prefix_len = match_url_prefix_case_insensitive(this.#text, this.#index);
712
+ if (prefix_len === 0)
713
+ return false;
714
+ if (this.#index + prefix_len >= this.#text.length)
715
+ return false;
716
+ const next_char = this.#text.charCodeAt(this.#index + prefix_len);
717
+ return next_char !== SPACE && next_char !== NEWLINE;
722
718
  }
723
719
  #is_at_word_boundary(index, check_before, check_after) {
724
720
  if (check_before && index > 0) {