@fuzdev/fuz_ui 0.185.2 → 0.187.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 (44) hide show
  1. package/dist/ApiModule.svelte +22 -6
  2. package/dist/ApiModule.svelte.d.ts.map +1 -1
  3. package/dist/DeclarationLink.svelte +1 -1
  4. package/dist/DocsLink.svelte +2 -2
  5. package/dist/DocsTertiaryNav.svelte +2 -2
  6. package/dist/Mdz.svelte +5 -0
  7. package/dist/Mdz.svelte.d.ts +1 -0
  8. package/dist/Mdz.svelte.d.ts.map +1 -1
  9. package/dist/MdzNodeView.svelte +19 -8
  10. package/dist/MdzNodeView.svelte.d.ts +1 -1
  11. package/dist/MdzNodeView.svelte.d.ts.map +1 -1
  12. package/dist/ModuleLink.svelte +1 -1
  13. package/dist/TypeLink.svelte +1 -1
  14. package/dist/library.svelte.d.ts +24 -27
  15. package/dist/library.svelte.d.ts.map +1 -1
  16. package/dist/library.svelte.js +16 -16
  17. package/dist/mdz.d.ts +13 -0
  18. package/dist/mdz.d.ts.map +1 -1
  19. package/dist/mdz.js +73 -280
  20. package/dist/mdz_components.d.ts +12 -0
  21. package/dist/mdz_components.d.ts.map +1 -1
  22. package/dist/mdz_components.js +8 -0
  23. package/dist/mdz_helpers.d.ts +108 -0
  24. package/dist/mdz_helpers.d.ts.map +1 -0
  25. package/dist/mdz_helpers.js +237 -0
  26. package/dist/mdz_lexer.d.ts +93 -0
  27. package/dist/mdz_lexer.d.ts.map +1 -0
  28. package/dist/mdz_lexer.js +727 -0
  29. package/dist/mdz_to_svelte.d.ts +5 -2
  30. package/dist/mdz_to_svelte.d.ts.map +1 -1
  31. package/dist/mdz_to_svelte.js +13 -2
  32. package/dist/mdz_token_parser.d.ts +14 -0
  33. package/dist/mdz_token_parser.d.ts.map +1 -0
  34. package/dist/mdz_token_parser.js +374 -0
  35. package/dist/svelte_preprocess_mdz.js +23 -7
  36. package/package.json +10 -9
  37. package/src/lib/library.svelte.ts +36 -35
  38. package/src/lib/mdz.ts +106 -302
  39. package/src/lib/mdz_components.ts +9 -0
  40. package/src/lib/mdz_helpers.ts +251 -0
  41. package/src/lib/mdz_lexer.ts +1003 -0
  42. package/src/lib/mdz_to_svelte.ts +15 -2
  43. package/src/lib/mdz_token_parser.ts +460 -0
  44. package/src/lib/svelte_preprocess_mdz.ts +23 -7
@@ -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
  */
@@ -33,6 +33,9 @@ export interface MdzToSvelteResult {
33
33
  * If content references a component not in this map, `has_unconfigured_tags` is set.
34
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/'`).
37
+ * When provided, relative references (`./`, `../`) are resolved to absolute paths
38
+ * and passed through `resolve()`. Trailing slash recommended.
36
39
  */
37
- export declare const mdz_to_svelte: (nodes: Array<MdzNode>, components: Record<string, string>, elements: ReadonlySet<string>) => MdzToSvelteResult;
40
+ export declare const mdz_to_svelte: (nodes: Array<MdzNode>, components: Record<string, string>, elements: ReadonlySet<string>, base?: string) => MdzToSvelteResult;
38
41
  //# sourceMappingURL=mdz_to_svelte.d.ts.map
@@ -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,KAAK,EAAC,OAAO,EAAC,MAAM,UAAU,CAAC;AAEtC;;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;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,aAAa,GACzB,OAAO,KAAK,CAAC,OAAO,CAAC,EACrB,YAAY,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAClC,UAAU,WAAW,CAAC,MAAM,CAAC,KAC3B,iBAgFF,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,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"}
@@ -10,6 +10,7 @@
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
14
  /**
14
15
  * Converts an array of `MdzNode` to a Svelte markup string.
15
16
  *
@@ -21,8 +22,11 @@ import { escape_js_string } from '@fuzdev/fuz_util/string.js';
21
22
  * If content references a component not in this map, `has_unconfigured_tags` is set.
22
23
  * @param elements Allowed HTML element names (e.g., `new Set(['aside', 'details'])`).
23
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/'`).
26
+ * When provided, relative references (`./`, `../`) are resolved to absolute paths
27
+ * and passed through `resolve()`. Trailing slash recommended.
24
28
  */
25
- export const mdz_to_svelte = (nodes, components, elements) => {
29
+ export const mdz_to_svelte = (nodes, components, elements, base) => {
26
30
  const imports = new Map();
27
31
  let has_unconfigured_tags = false;
28
32
  const render_nodes = (children) => {
@@ -49,7 +53,14 @@ export const mdz_to_svelte = (nodes, components, elements) => {
49
53
  case 'Link': {
50
54
  const children_markup = render_nodes(node.children);
51
55
  if (node.link_type === 'internal') {
52
- if (node.reference.startsWith('#') || node.reference.startsWith('?')) {
56
+ if (node.reference.startsWith('.') && base) {
57
+ const resolved = resolve_relative_path(node.reference, base);
58
+ imports.set('resolve', { path: '$app/paths', kind: 'named' });
59
+ return `<a href={resolve('${escape_js_string(resolved)}')}>${children_markup}</a>`;
60
+ }
61
+ if (node.reference.startsWith('#') ||
62
+ node.reference.startsWith('?') ||
63
+ node.reference.startsWith('.')) {
53
64
  return `<a href={'${escape_js_string(node.reference)}'}>${children_markup}</a>`;
54
65
  }
55
66
  imports.set('resolve', { path: '$app/paths', kind: 'named' });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * mdz token parser — consumes a `MdzToken[]` stream to build the `MdzNode[]` AST.
3
+ *
4
+ * Phase 2 of the two-phase lexer+parser alternative to the single-pass parser
5
+ * in `mdz.ts`. Phase 1 (lexer) is in `mdz_lexer.ts`.
6
+ *
7
+ * @module
8
+ */
9
+ import type { MdzNode } from './mdz.js';
10
+ /**
11
+ * Parses text to an array of `MdzNode` using a two-phase lexer+parser approach.
12
+ */
13
+ export declare const mdz_parse_lexer: (text: string) => Array<MdzNode>;
14
+ //# sourceMappingURL=mdz_token_parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdz_token_parser.d.ts","sourceRoot":"../src/lib/","sources":["../src/lib/mdz_token_parser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACX,OAAO,EASP,MAAM,UAAU,CAAC;AAgBlB;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,KAAK,CAAC,OAAO,CAG3D,CAAC"}
@@ -0,0 +1,374 @@
1
+ /**
2
+ * mdz token parser — consumes a `MdzToken[]` stream to build the `MdzNode[]` AST.
3
+ *
4
+ * Phase 2 of the two-phase lexer+parser alternative to the single-pass parser
5
+ * in `mdz.ts`. Phase 1 (lexer) is in `mdz_lexer.ts`.
6
+ *
7
+ * @module
8
+ */
9
+ import { extract_single_tag } from './mdz_helpers.js';
10
+ import { MdzLexer, } from './mdz_lexer.js';
11
+ /**
12
+ * Parses text to an array of `MdzNode` using a two-phase lexer+parser approach.
13
+ */
14
+ export const mdz_parse_lexer = (text) => {
15
+ const tokens = new MdzLexer(text).tokenize();
16
+ return new MdzTokenParser(tokens).parse();
17
+ };
18
+ class MdzTokenParser {
19
+ #tokens;
20
+ #index = 0;
21
+ constructor(tokens) {
22
+ this.#tokens = tokens;
23
+ }
24
+ parse() {
25
+ const root_nodes = [];
26
+ const paragraph_children = [];
27
+ while (this.#index < this.#tokens.length) {
28
+ const token = this.#tokens[this.#index];
29
+ // Block-level tokens
30
+ if (token.type === 'heading_start') {
31
+ const flushed = this.#flush_paragraph(paragraph_children, true);
32
+ if (flushed)
33
+ root_nodes.push(flushed);
34
+ root_nodes.push(this.#parse_heading());
35
+ continue;
36
+ }
37
+ if (token.type === 'hr') {
38
+ const flushed = this.#flush_paragraph(paragraph_children, true);
39
+ if (flushed)
40
+ root_nodes.push(flushed);
41
+ root_nodes.push({ type: 'Hr', start: token.start, end: token.end });
42
+ this.#index++;
43
+ continue;
44
+ }
45
+ if (token.type === 'codeblock') {
46
+ const flushed = this.#flush_paragraph(paragraph_children, true);
47
+ if (flushed)
48
+ root_nodes.push(flushed);
49
+ root_nodes.push({
50
+ type: 'Codeblock',
51
+ lang: token.lang,
52
+ content: token.content,
53
+ start: token.start,
54
+ end: token.end,
55
+ });
56
+ this.#index++;
57
+ continue;
58
+ }
59
+ if (token.type === 'paragraph_break') {
60
+ const flushed = this.#flush_paragraph(paragraph_children, true);
61
+ if (flushed)
62
+ root_nodes.push(flushed);
63
+ this.#index++;
64
+ continue;
65
+ }
66
+ // Inline tokens → paragraph children
67
+ const node = this.#parse_inline();
68
+ if (node)
69
+ paragraph_children.push(node);
70
+ }
71
+ // Flush remaining content
72
+ const final_paragraph = this.#flush_paragraph(paragraph_children, true);
73
+ if (final_paragraph)
74
+ root_nodes.push(final_paragraph);
75
+ return root_nodes;
76
+ }
77
+ #parse_heading() {
78
+ const token = this.#tokens[this.#index];
79
+ const start = token.start;
80
+ const level = token.level;
81
+ this.#index++;
82
+ const children = [];
83
+ // Collect inline nodes until heading_end marker
84
+ while (this.#index < this.#tokens.length) {
85
+ const t = this.#tokens[this.#index];
86
+ if (t.type === 'heading_end') {
87
+ this.#index++; // consume the marker
88
+ break;
89
+ }
90
+ const node = this.#parse_inline();
91
+ if (node)
92
+ children.push(node);
93
+ }
94
+ const end = children.length > 0 ? children[children.length - 1].end : start + level + 1;
95
+ return { type: 'Heading', level, children, start, end };
96
+ }
97
+ #parse_inline() {
98
+ if (this.#index >= this.#tokens.length)
99
+ return null;
100
+ const token = this.#tokens[this.#index];
101
+ switch (token.type) {
102
+ case 'text':
103
+ this.#index++;
104
+ return { type: 'Text', content: token.content, start: token.start, end: token.end };
105
+ case 'code':
106
+ this.#index++;
107
+ return { type: 'Code', content: token.content, start: token.start, end: token.end };
108
+ case 'bold_open':
109
+ return this.#parse_bold();
110
+ case 'italic_open':
111
+ return this.#parse_italic();
112
+ case 'strikethrough_open':
113
+ return this.#parse_strikethrough();
114
+ case 'link_text_open':
115
+ return this.#parse_link();
116
+ case 'autolink':
117
+ return this.#parse_autolink();
118
+ case 'tag_open':
119
+ return this.#parse_tag_node();
120
+ case 'tag_self_close':
121
+ return this.#parse_self_close_tag();
122
+ // Orphaned close tokens - treat as text
123
+ case 'bold_close':
124
+ this.#index++;
125
+ return { type: 'Text', content: '**', start: token.start, end: token.end };
126
+ case 'italic_close':
127
+ this.#index++;
128
+ return { type: 'Text', content: '_', start: token.start, end: token.end };
129
+ case 'strikethrough_close':
130
+ this.#index++;
131
+ return { type: 'Text', content: '~', start: token.start, end: token.end };
132
+ case 'link_text_close':
133
+ this.#index++;
134
+ return { type: 'Text', content: ']', start: token.start, end: token.end };
135
+ case 'link_ref':
136
+ this.#index++;
137
+ return {
138
+ type: 'Text',
139
+ content: `(${token.reference})`,
140
+ start: token.start,
141
+ end: token.end,
142
+ };
143
+ case 'tag_close':
144
+ this.#index++;
145
+ return {
146
+ type: 'Text',
147
+ content: `</${token.name}>`,
148
+ start: token.start,
149
+ end: token.end,
150
+ };
151
+ default:
152
+ this.#index++;
153
+ return null;
154
+ }
155
+ }
156
+ #parse_bold() {
157
+ const token = this.#tokens[this.#index];
158
+ const start = token.start;
159
+ this.#index++;
160
+ const children = [];
161
+ while (this.#index < this.#tokens.length) {
162
+ const t = this.#tokens[this.#index];
163
+ if (t.type === 'bold_close') {
164
+ this.#index++;
165
+ return { type: 'Bold', children, start, end: t.end };
166
+ }
167
+ if (t.type === 'paragraph_break' ||
168
+ t.type === 'heading_start' ||
169
+ t.type === 'hr' ||
170
+ t.type === 'codeblock') {
171
+ break;
172
+ }
173
+ const node = this.#parse_inline();
174
+ if (node)
175
+ children.push(node);
176
+ }
177
+ // Unclosed - treat as text
178
+ return { type: 'Text', content: '**', start, end: start + 2 };
179
+ }
180
+ #parse_italic() {
181
+ const token = this.#tokens[this.#index];
182
+ const start = token.start;
183
+ this.#index++;
184
+ const children = [];
185
+ while (this.#index < this.#tokens.length) {
186
+ const t = this.#tokens[this.#index];
187
+ if (t.type === 'italic_close') {
188
+ this.#index++;
189
+ return { type: 'Italic', children, start, end: t.end };
190
+ }
191
+ if (t.type === 'paragraph_break' ||
192
+ t.type === 'heading_start' ||
193
+ t.type === 'hr' ||
194
+ t.type === 'codeblock') {
195
+ break;
196
+ }
197
+ const node = this.#parse_inline();
198
+ if (node)
199
+ children.push(node);
200
+ }
201
+ return { type: 'Text', content: '_', start, end: start + 1 };
202
+ }
203
+ #parse_strikethrough() {
204
+ const token = this.#tokens[this.#index];
205
+ const start = token.start;
206
+ this.#index++;
207
+ const children = [];
208
+ while (this.#index < this.#tokens.length) {
209
+ const t = this.#tokens[this.#index];
210
+ if (t.type === 'strikethrough_close') {
211
+ this.#index++;
212
+ return { type: 'Strikethrough', children, start, end: t.end };
213
+ }
214
+ if (t.type === 'paragraph_break' ||
215
+ t.type === 'heading_start' ||
216
+ t.type === 'hr' ||
217
+ t.type === 'codeblock') {
218
+ break;
219
+ }
220
+ const node = this.#parse_inline();
221
+ if (node)
222
+ children.push(node);
223
+ }
224
+ return { type: 'Text', content: '~', start, end: start + 1 };
225
+ }
226
+ #parse_link() {
227
+ const open_token = this.#tokens[this.#index];
228
+ const start = open_token.start;
229
+ this.#index++;
230
+ const children = [];
231
+ while (this.#index < this.#tokens.length) {
232
+ const t = this.#tokens[this.#index];
233
+ if (t.type === 'link_text_close') {
234
+ this.#index++;
235
+ // Expect link_ref next
236
+ if (this.#index < this.#tokens.length && this.#tokens[this.#index].type === 'link_ref') {
237
+ const ref_token = this.#tokens[this.#index];
238
+ this.#index++;
239
+ return {
240
+ type: 'Link',
241
+ reference: ref_token.reference,
242
+ children,
243
+ link_type: ref_token.link_type,
244
+ start,
245
+ end: ref_token.end,
246
+ };
247
+ }
248
+ // No link_ref - treat as text
249
+ return { type: 'Text', content: '[', start, end: start + 1 };
250
+ }
251
+ if (t.type === 'paragraph_break' ||
252
+ t.type === 'heading_start' ||
253
+ t.type === 'hr' ||
254
+ t.type === 'codeblock') {
255
+ break;
256
+ }
257
+ const node = this.#parse_inline();
258
+ if (node)
259
+ children.push(node);
260
+ }
261
+ return { type: 'Text', content: '[', start, end: start + 1 };
262
+ }
263
+ #parse_autolink() {
264
+ const token = this.#tokens[this.#index];
265
+ this.#index++;
266
+ return {
267
+ type: 'Link',
268
+ reference: token.reference,
269
+ children: [{ type: 'Text', content: token.reference, start: token.start, end: token.end }],
270
+ link_type: token.link_type,
271
+ start: token.start,
272
+ end: token.end,
273
+ };
274
+ }
275
+ #parse_tag_node() {
276
+ const open_token = this.#tokens[this.#index];
277
+ const start = open_token.start;
278
+ const tag_name = open_token.name;
279
+ const node_type = open_token.is_component ? 'Component' : 'Element';
280
+ this.#index++;
281
+ const children = [];
282
+ while (this.#index < this.#tokens.length) {
283
+ const t = this.#tokens[this.#index];
284
+ if (t.type === 'tag_close' && t.name === tag_name) {
285
+ this.#index++;
286
+ return { type: node_type, name: tag_name, children, start, end: t.end };
287
+ }
288
+ const node = this.#parse_inline();
289
+ if (node)
290
+ children.push(node);
291
+ }
292
+ // Unclosed tag
293
+ return { type: 'Text', content: '<', start, end: start + 1 };
294
+ }
295
+ #parse_self_close_tag() {
296
+ const token = this.#tokens[this.#index];
297
+ const node_type = token.is_component ? 'Component' : 'Element';
298
+ this.#index++;
299
+ return { type: node_type, name: token.name, children: [], start: token.start, end: token.end };
300
+ }
301
+ // -- Paragraph flushing --
302
+ #flush_paragraph(paragraph_children, trim_trailing = false) {
303
+ if (paragraph_children.length === 0)
304
+ return null;
305
+ if (trim_trailing) {
306
+ // Trim trailing newlines from last text node
307
+ const last = paragraph_children[paragraph_children.length - 1];
308
+ if (last.type === 'Text') {
309
+ const trimmed = last.content.replace(/\n+$/, '');
310
+ if (trimmed) {
311
+ last.content = trimmed;
312
+ last.end = last.start + trimmed.length;
313
+ }
314
+ else {
315
+ paragraph_children.pop();
316
+ }
317
+ }
318
+ // Skip whitespace-only paragraphs
319
+ const has_content = paragraph_children.some((n) => n.type !== 'Text' || n.content.trim().length > 0);
320
+ if (!has_content) {
321
+ paragraph_children.length = 0;
322
+ return null;
323
+ }
324
+ }
325
+ // Single tag extraction (MDX convention)
326
+ const single_tag = extract_single_tag(paragraph_children);
327
+ if (single_tag) {
328
+ paragraph_children.length = 0;
329
+ return single_tag;
330
+ }
331
+ // Merge adjacent text nodes
332
+ const merged = this.#merge_adjacent_text(paragraph_children.slice());
333
+ paragraph_children.length = 0;
334
+ if (merged.length === 0)
335
+ return null;
336
+ return {
337
+ type: 'Paragraph',
338
+ children: merged,
339
+ start: merged[0].start,
340
+ end: merged[merged.length - 1].end,
341
+ };
342
+ }
343
+ #merge_adjacent_text(nodes) {
344
+ if (nodes.length <= 1)
345
+ return nodes;
346
+ const merged = [];
347
+ let pending_text = null;
348
+ for (const node of nodes) {
349
+ if (node.type === 'Text') {
350
+ if (pending_text) {
351
+ pending_text = {
352
+ type: 'Text',
353
+ content: pending_text.content + node.content,
354
+ start: pending_text.start,
355
+ end: node.end,
356
+ };
357
+ }
358
+ else {
359
+ pending_text = { ...node };
360
+ }
361
+ }
362
+ else {
363
+ if (pending_text) {
364
+ merged.push(pending_text);
365
+ pending_text = null;
366
+ }
367
+ merged.push(node);
368
+ }
369
+ }
370
+ if (pending_text)
371
+ merged.push(pending_text);
372
+ return merged;
373
+ }
374
+ }
@@ -161,6 +161,22 @@ const find_mdz_usages = (ast, mdz_names, context) => {
161
161
  const content_attr = find_attribute(node, 'content');
162
162
  if (!content_attr)
163
163
  return;
164
+ // Extract optional static base prop for relative path resolution.
165
+ // If base is present but dynamic, skip precompilation entirely —
166
+ // MdzPrecompiled doesn't resolve relative paths at runtime,
167
+ // so precompiling with unresolved relative links would be wrong.
168
+ const base_attr = find_attribute(node, 'base');
169
+ let base;
170
+ if (base_attr) {
171
+ const base_value = extract_static_string(base_attr.value, context.bindings);
172
+ if (base_value === null)
173
+ return; // dynamic base — fall back to runtime
174
+ base = base_value;
175
+ }
176
+ // Collect attributes to exclude from precompiled output
177
+ const exclude_attrs = new Set([content_attr]);
178
+ if (base_attr)
179
+ exclude_attrs.add(base_attr);
164
180
  // Extract static string value
165
181
  const content_value = extract_static_string(content_attr.value, context.bindings);
166
182
  if (content_value !== null) {
@@ -168,7 +184,7 @@ const find_mdz_usages = (ast, mdz_names, context) => {
168
184
  let result;
169
185
  try {
170
186
  const nodes = mdz_parse(content_value);
171
- result = mdz_to_svelte(nodes, context.components, context.elements);
187
+ result = mdz_to_svelte(nodes, context.components, context.elements, base);
172
188
  }
173
189
  catch (error) {
174
190
  handle_preprocess_error(error, '[fuz-mdz]', context.filename, context.on_error);
@@ -178,7 +194,7 @@ const find_mdz_usages = (ast, mdz_names, context) => {
178
194
  if (result.has_unconfigured_tags)
179
195
  return;
180
196
  const consumed = collect_consumed_bindings(content_attr.value, context.bindings);
181
- const replacement = build_replacement(node, content_attr, result.markup, context.source);
197
+ const replacement = build_replacement(node, exclude_attrs, result.markup, context.source);
182
198
  transformed_usages.set(node.name, (transformed_usages.get(node.name) ?? 0) + 1);
183
199
  transformations.push({
184
200
  start: node.start,
@@ -199,7 +215,7 @@ const find_mdz_usages = (ast, mdz_names, context) => {
199
215
  try {
200
216
  for (const branch of chain) {
201
217
  const nodes = mdz_parse(branch.value);
202
- const result = mdz_to_svelte(nodes, context.components, context.elements);
218
+ const result = mdz_to_svelte(nodes, context.components, context.elements, base);
203
219
  if (result.has_unconfigured_tags)
204
220
  return;
205
221
  branch_results.push({ markup: result.markup, imports: result.imports });
@@ -225,7 +241,7 @@ const find_mdz_usages = (ast, mdz_names, context) => {
225
241
  }
226
242
  }
227
243
  children_markup += '{/if}';
228
- const replacement = build_replacement(node, content_attr, children_markup, context.source);
244
+ const replacement = build_replacement(node, exclude_attrs, children_markup, context.source);
229
245
  // Merge imports from all branches
230
246
  const merged_imports = new Map();
231
247
  for (const result of branch_results) {
@@ -317,11 +333,11 @@ const remove_dead_const_bindings = (s, ast, transformations, source) => {
317
333
  * Reconstructs the opening tag as `<MdzPrecompiled` with all attributes except `content`,
318
334
  * using source text slicing to preserve exact formatting and dynamic expressions.
319
335
  */
320
- const build_replacement = (node, content_attr, children_markup, source) => {
321
- // Collect source ranges of all attributes EXCEPT content
336
+ const build_replacement = (node, exclude_attrs, children_markup, source) => {
337
+ // Collect source ranges of all attributes except excluded ones (content, base when resolved)
322
338
  const other_attr_ranges = [];
323
339
  for (const attr of node.attributes) {
324
- if (attr === content_attr)
340
+ if (exclude_attrs.has(attr))
325
341
  continue;
326
342
  other_attr_ranges.push({ start: attr.start, end: attr.end });
327
343
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_ui",
3
- "version": "0.185.2",
3
+ "version": "0.187.0",
4
4
  "description": "Svelte UI library",
5
5
  "motto": "friendly user zystem",
6
6
  "glyph": "🧶",
@@ -27,7 +27,8 @@
27
27
  "test": "gro test",
28
28
  "fixtures:update": "gro src/test/fixtures/update",
29
29
  "preview": "vite preview",
30
- "deploy": "gro deploy"
30
+ "deploy": "gro deploy",
31
+ "benchmark_mdz": "gro run src/benchmarks/mdz.benchmark.ts"
31
32
  },
32
33
  "type": "module",
33
34
  "engines": {
@@ -75,13 +76,13 @@
75
76
  "devDependencies": {
76
77
  "@changesets/changelog-git": "^0.2.1",
77
78
  "@fuzdev/fuz_code": "^0.45.1",
78
- "@fuzdev/fuz_css": "^0.53.0",
79
- "@fuzdev/fuz_util": "^0.52.0",
80
- "@fuzdev/gro": "^0.195.0",
79
+ "@fuzdev/fuz_css": "^0.55.0",
80
+ "@fuzdev/fuz_util": "^0.53.4",
81
+ "@fuzdev/gro": "^0.197.0",
81
82
  "@jridgewell/trace-mapping": "^0.3.31",
82
- "@ryanatkn/eslint-config": "^0.9.0",
83
+ "@ryanatkn/eslint-config": "^0.10.1",
83
84
  "@sveltejs/adapter-static": "^3.0.10",
84
- "@sveltejs/kit": "^2.50.1",
85
+ "@sveltejs/kit": "^2.53.4",
85
86
  "@sveltejs/package": "^2.5.7",
86
87
  "@sveltejs/vite-plugin-svelte": "^6.2.4",
87
88
  "@types/estree": "^1.0.8",
@@ -94,8 +95,8 @@
94
95
  "magic-string": "^0.30.21",
95
96
  "prettier": "^3.7.4",
96
97
  "prettier-plugin-svelte": "^3.4.1",
97
- "svelte": "^5.49.1",
98
- "svelte-check": "^4.3.6",
98
+ "svelte": "^5.53.7",
99
+ "svelte-check": "^4.4.5",
99
100
  "svelte2tsx": "^0.7.47",
100
101
  "tslib": "^2.8.1",
101
102
  "typescript": "^5.9.3",