@fuzdev/fuz_ui 0.181.1 → 0.182.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.
@@ -0,0 +1,513 @@
1
+ /**
2
+ * Svelte preprocessor that compiles static `Mdz` content to Svelte markup at build time.
3
+ *
4
+ * Detects `Mdz` components with static string `content` props, parses the mdz content,
5
+ * renders each `MdzNode` to equivalent Svelte markup via `mdz_to_svelte`, and replaces
6
+ * the `Mdz` with `MdzPrecompiled` containing pre-rendered children.
7
+ *
8
+ * Also handles ternary chains (`content={a ? 'x' : b ? 'y' : 'z'}`) where all leaf
9
+ * values are statically resolvable strings, emitting `{#if a}markup_x{:else if b}markup_y{:else}markup_z{/if}`
10
+ * as children of a single `MdzPrecompiled`.
11
+ *
12
+ * Truly dynamic `content` props are left untouched.
13
+ *
14
+ * @module
15
+ */
16
+ import { parse } from 'svelte/compiler';
17
+ import MagicString from 'magic-string';
18
+ import { walk } from 'zimmerframe';
19
+ import { should_exclude_path } from '@fuzdev/fuz_util/path.js';
20
+ import { find_attribute, extract_static_string, try_extract_conditional_chain, build_static_bindings, resolve_component_names, has_identifier_in_tree, find_import_insert_position, generate_import_lines, remove_variable_declaration, remove_import_declaration, remove_import_specifier, handle_preprocess_error, } from '@fuzdev/fuz_util/svelte_preprocess_helpers.js';
21
+ import { mdz_parse } from './mdz.js';
22
+ import { mdz_to_svelte } from './mdz_to_svelte.js';
23
+ const PRECOMPILED_NAME = 'MdzPrecompiled';
24
+ /**
25
+ * Creates a Svelte preprocessor that compiles static `Mdz` content at build time.
26
+ *
27
+ * @param options Configuration for component/element resolution and file filtering.
28
+ * @returns A Svelte `PreprocessorGroup` for use in `svelte.config.js`.
29
+ */
30
+ export const svelte_preprocess_mdz = (options = {}) => {
31
+ const { exclude = [], components = {}, elements: elements_array = [], component_imports = ['@fuzdev/fuz_ui/Mdz.svelte'], compiled_component_import = '@fuzdev/fuz_ui/MdzPrecompiled.svelte', on_error = process.env.CI === 'true' ? 'throw' : 'log', } = options;
32
+ const elements = new Set(elements_array);
33
+ return {
34
+ name: 'fuz-mdz',
35
+ markup: ({ content, filename }) => {
36
+ if (should_exclude_path(filename, exclude)) {
37
+ return { code: content };
38
+ }
39
+ // Quick bail: does file mention any known Mdz import source?
40
+ if (!component_imports.some((source) => content.includes(source))) {
41
+ return { code: content };
42
+ }
43
+ const ast = parse(content, { filename, modern: true });
44
+ // Resolve which local names map to the Mdz component
45
+ const mdz_names = resolve_component_names(ast, component_imports);
46
+ if (mdz_names.size === 0) {
47
+ return { code: content };
48
+ }
49
+ // Check for MdzPrecompiled name collision
50
+ if (has_name_collision(ast, compiled_component_import)) {
51
+ return { code: content };
52
+ }
53
+ const s = new MagicString(content);
54
+ const bindings = build_static_bindings(ast);
55
+ // Find and transform Mdz usages with static content
56
+ const { transformations, total_usages, transformed_usages } = find_mdz_usages(ast, mdz_names, {
57
+ components,
58
+ elements,
59
+ filename,
60
+ source: content,
61
+ bindings,
62
+ on_error,
63
+ });
64
+ if (transformations.length === 0) {
65
+ return { code: content };
66
+ }
67
+ // Apply transformations
68
+ for (const t of transformations) {
69
+ s.overwrite(t.start, t.end, t.replacement);
70
+ }
71
+ // Remove dead const bindings that were consumed by transformations
72
+ remove_dead_const_bindings(s, ast, transformations, content);
73
+ // Determine which Mdz imports can be removed
74
+ const removable_imports = find_removable_mdz_imports(ast, mdz_names, total_usages, transformed_usages);
75
+ // Add required imports and remove unused Mdz imports
76
+ manage_imports(s, ast, transformations, removable_imports, compiled_component_import, content);
77
+ return {
78
+ code: s.toString(),
79
+ map: s.generateMap({ hires: true }),
80
+ };
81
+ },
82
+ };
83
+ };
84
+ /**
85
+ * Checks if `MdzPrecompiled` is already imported from a different source.
86
+ * If so, the preprocessor bails to avoid name collisions.
87
+ */
88
+ const has_name_collision = (ast, compiled_component_import) => {
89
+ for (const script of [ast.instance, ast.module]) {
90
+ if (!script)
91
+ continue;
92
+ for (const node of script.content.body) {
93
+ if (node.type !== 'ImportDeclaration')
94
+ continue;
95
+ const source_path = node.source.value;
96
+ for (const spec of node.specifiers) {
97
+ if (spec.local.name === PRECOMPILED_NAME && source_path !== compiled_component_import) {
98
+ return true;
99
+ }
100
+ }
101
+ }
102
+ }
103
+ return false;
104
+ };
105
+ /**
106
+ * Collects identifiers from an expression that resolved through bindings.
107
+ * Only collects top-level Identifier nodes that appear in the bindings map.
108
+ *
109
+ * TODO: support transitive dead const removal — currently only directly-consumed
110
+ * bindings (identifiers in the expression AST) are tracked. Transitive dependencies
111
+ * (e.g., `const a = 'x'; const b = a; content={b}` — `a` is not removed) are not
112
+ * traced. After removing a dead const, re-check whether identifiers in its
113
+ * initializer became dead too.
114
+ */
115
+ const collect_consumed_bindings = (value, bindings) => {
116
+ const consumed = new Set();
117
+ if (value === true || Array.isArray(value))
118
+ return consumed;
119
+ const collect_from_expr = (expr) => {
120
+ if (expr.type === 'Identifier' && bindings.has(expr.name)) {
121
+ consumed.add(expr.name);
122
+ }
123
+ else if (expr.type === 'BinaryExpression' && expr.operator === '+') {
124
+ collect_from_expr(expr.left);
125
+ collect_from_expr(expr.right);
126
+ }
127
+ else if (expr.type === 'TemplateLiteral') {
128
+ for (const e of expr.expressions) {
129
+ collect_from_expr(e);
130
+ }
131
+ }
132
+ else if (expr.type === 'ConditionalExpression') {
133
+ collect_from_expr(expr.consequent);
134
+ collect_from_expr(expr.alternate);
135
+ }
136
+ };
137
+ collect_from_expr(value.expression);
138
+ return consumed;
139
+ };
140
+ /**
141
+ * Walks the AST to find `Mdz` component usages with static `content` props
142
+ * and generates transformations to replace them with `MdzPrecompiled` children.
143
+ */
144
+ const find_mdz_usages = (ast, mdz_names, context) => {
145
+ const transformations = [];
146
+ const total_usages = new Map();
147
+ const transformed_usages = new Map();
148
+ // zimmerframe types visitors against {type: string}, requiring explicit annotations
149
+ // on the callback parameters for Svelte-specific AST types like AST.Component
150
+ walk(ast.fragment, null, {
151
+ Component(node, ctx) {
152
+ // Always recurse into children so nested Mdz components are found
153
+ ctx.next();
154
+ if (!mdz_names.has(node.name))
155
+ return;
156
+ // Track total usages per name
157
+ total_usages.set(node.name, (total_usages.get(node.name) ?? 0) + 1);
158
+ // Skip if spread attributes present — can't determine content statically
159
+ if (node.attributes.some((attr) => attr.type === 'SpreadAttribute'))
160
+ return;
161
+ const content_attr = find_attribute(node, 'content');
162
+ if (!content_attr)
163
+ return;
164
+ // Extract static string value
165
+ const content_value = extract_static_string(content_attr.value, context.bindings);
166
+ if (content_value !== null) {
167
+ // Parse mdz content and render to Svelte markup
168
+ let result;
169
+ try {
170
+ const nodes = mdz_parse(content_value);
171
+ result = mdz_to_svelte(nodes, context.components, context.elements);
172
+ }
173
+ catch (error) {
174
+ handle_preprocess_error(error, '[fuz-mdz]', context.filename, context.on_error);
175
+ return;
176
+ }
177
+ // If content has unconfigured tags, skip this usage (fall back to runtime)
178
+ if (result.has_unconfigured_tags)
179
+ return;
180
+ const consumed = collect_consumed_bindings(content_attr.value, context.bindings);
181
+ const replacement = build_replacement(node, content_attr, result.markup, context.source);
182
+ transformed_usages.set(node.name, (transformed_usages.get(node.name) ?? 0) + 1);
183
+ transformations.push({
184
+ start: node.start,
185
+ end: node.end,
186
+ replacement,
187
+ required_imports: result.imports,
188
+ consumed_bindings: consumed,
189
+ component_node: node,
190
+ });
191
+ return;
192
+ }
193
+ // Try conditional chain (handles both simple and nested ternaries)
194
+ const chain = try_extract_conditional_chain(content_attr.value, context.source, context.bindings);
195
+ if (chain === null)
196
+ return;
197
+ // Parse and render each branch
198
+ const branch_results = [];
199
+ try {
200
+ for (const branch of chain) {
201
+ const nodes = mdz_parse(branch.value);
202
+ const result = mdz_to_svelte(nodes, context.components, context.elements);
203
+ if (result.has_unconfigured_tags)
204
+ return;
205
+ branch_results.push({ markup: result.markup, imports: result.imports });
206
+ }
207
+ }
208
+ catch (error) {
209
+ handle_preprocess_error(error, '[fuz-mdz]', context.filename, context.on_error);
210
+ return;
211
+ }
212
+ // Build {#if}/{:else if}/{:else} markup
213
+ let children_markup = '';
214
+ for (let i = 0; i < chain.length; i++) {
215
+ const branch = chain[i];
216
+ const result = branch_results[i];
217
+ if (i === 0) {
218
+ children_markup += `{#if ${branch.test_source}}${result.markup}`;
219
+ }
220
+ else if (branch.test_source !== null) {
221
+ children_markup += `{:else if ${branch.test_source}}${result.markup}`;
222
+ }
223
+ else {
224
+ children_markup += `{:else}${result.markup}`;
225
+ }
226
+ }
227
+ children_markup += '{/if}';
228
+ const replacement = build_replacement(node, content_attr, children_markup, context.source);
229
+ // Merge imports from all branches
230
+ const merged_imports = new Map();
231
+ for (const result of branch_results) {
232
+ for (const [name, info] of result.imports) {
233
+ merged_imports.set(name, info);
234
+ }
235
+ }
236
+ const consumed = collect_consumed_bindings(content_attr.value, context.bindings);
237
+ transformed_usages.set(node.name, (transformed_usages.get(node.name) ?? 0) + 1);
238
+ transformations.push({
239
+ start: node.start,
240
+ end: node.end,
241
+ replacement,
242
+ required_imports: merged_imports,
243
+ consumed_bindings: consumed,
244
+ component_node: node,
245
+ });
246
+ },
247
+ });
248
+ return { transformations, total_usages, transformed_usages };
249
+ };
250
+ /**
251
+ * Removes dead `const` bindings from the instance script that were consumed
252
+ * by transformations and are no longer referenced anywhere else in the file.
253
+ *
254
+ * Only handles single-declarator `const` statements. Skips `const a = 'x', b = 'y'`
255
+ * and module script variables (which could be exported/imported by other files).
256
+ */
257
+ const remove_dead_const_bindings = (s, ast, transformations, source) => {
258
+ // Collect all consumed binding names across transformations
259
+ const all_consumed = new Set();
260
+ for (const t of transformations) {
261
+ for (const name of t.consumed_bindings) {
262
+ all_consumed.add(name);
263
+ }
264
+ }
265
+ if (all_consumed.size === 0)
266
+ return;
267
+ // Only remove from instance script (module script variables could be exported)
268
+ const instance = ast.instance;
269
+ if (!instance)
270
+ return;
271
+ // Build a skip set of transformed component nodes so their attribute
272
+ // expressions (which still contain the old identifiers) don't false-match
273
+ const skip = new Set();
274
+ for (const t of transformations) {
275
+ skip.add(t.component_node);
276
+ }
277
+ for (const name of all_consumed) {
278
+ // Find the VariableDeclaration containing this binding in instance script
279
+ let declaration_node = null;
280
+ for (const node of instance.content.body) {
281
+ if (node.type !== 'VariableDeclaration' || node.kind !== 'const')
282
+ continue;
283
+ for (const decl of node.declarations) {
284
+ if (decl.id.type === 'Identifier' && decl.id.name === name) {
285
+ declaration_node = node;
286
+ break;
287
+ }
288
+ }
289
+ if (declaration_node)
290
+ break;
291
+ }
292
+ if (!declaration_node)
293
+ continue;
294
+ // Only handle single-declarator statements
295
+ if (declaration_node.declarations.length !== 1)
296
+ continue;
297
+ // Check if the identifier is referenced anywhere else
298
+ const id_skip = new Set([...skip, declaration_node]);
299
+ // Check instance script (excluding the declaration itself)
300
+ if (has_identifier_in_tree(instance.content, name, id_skip))
301
+ continue;
302
+ // Check module script
303
+ if (ast.module?.content && has_identifier_in_tree(ast.module.content, name))
304
+ continue;
305
+ // Check template — skip transformed component nodes whose attribute expressions
306
+ // still contain the old identifier references in the AST
307
+ if (has_identifier_in_tree(ast.fragment, name, id_skip))
308
+ continue;
309
+ remove_variable_declaration(s, declaration_node, source);
310
+ // Add to skip set so chained dead const checks don't find references to this removed node
311
+ skip.add(declaration_node);
312
+ }
313
+ };
314
+ /**
315
+ * Builds the replacement string for a transformed Mdz component.
316
+ *
317
+ * Reconstructs the opening tag as `<MdzPrecompiled` with all attributes except `content`,
318
+ * using source text slicing to preserve exact formatting and dynamic expressions.
319
+ */
320
+ const build_replacement = (node, content_attr, children_markup, source) => {
321
+ // Collect source ranges of all attributes EXCEPT content
322
+ const other_attr_ranges = [];
323
+ for (const attr of node.attributes) {
324
+ if (attr === content_attr)
325
+ continue;
326
+ other_attr_ranges.push({ start: attr.start, end: attr.end });
327
+ }
328
+ // Build opening tag with MdzPrecompiled name
329
+ let opening = `<${PRECOMPILED_NAME}`;
330
+ for (const range of other_attr_ranges) {
331
+ opening += ' ' + source.slice(range.start, range.end);
332
+ }
333
+ opening += '>';
334
+ return `${opening}${children_markup}</${PRECOMPILED_NAME}>`;
335
+ };
336
+ /**
337
+ * Determines which Mdz import declarations can be safely removed or trimmed.
338
+ *
339
+ * An import is removable when:
340
+ * 1. All template usages of that name were successfully transformed.
341
+ * 2. The identifier is not referenced elsewhere in script or template expressions.
342
+ *
343
+ * For multi-specifier imports, only the Mdz specifier is removed (partial removal).
344
+ * For single-specifier imports, the entire declaration is removed.
345
+ */
346
+ const find_removable_mdz_imports = (ast, mdz_names, total_usages, transformed_usages) => {
347
+ const removable = new Map();
348
+ for (const [name, { import_node, specifier }] of mdz_names) {
349
+ const total = total_usages.get(name) ?? 0;
350
+ const transformed = transformed_usages.get(name) ?? 0;
351
+ // Only remove if ALL template usages were transformed
352
+ if (total === 0 || transformed < total)
353
+ continue;
354
+ // Check if identifier is referenced elsewhere in the AST
355
+ const skip = new Set([import_node]);
356
+ // Check instance script body (excluding the import itself)
357
+ if (ast.instance?.content && has_identifier_in_tree(ast.instance.content, name, skip)) {
358
+ continue;
359
+ }
360
+ // Check module script body (excluding the import itself)
361
+ if (ast.module?.content && has_identifier_in_tree(ast.module.content, name, skip)) {
362
+ continue;
363
+ }
364
+ // Check template for expression references (Component.name is a string, not Identifier)
365
+ if (has_identifier_in_tree(ast.fragment, name)) {
366
+ continue;
367
+ }
368
+ const positioned_node = import_node;
369
+ if (import_node.specifiers.length === 1) {
370
+ removable.set(name, { node: positioned_node, kind: 'full' });
371
+ }
372
+ else {
373
+ removable.set(name, {
374
+ node: positioned_node,
375
+ kind: 'partial',
376
+ specifier_to_remove: specifier,
377
+ });
378
+ }
379
+ }
380
+ return removable;
381
+ };
382
+ /**
383
+ * Manages import additions and removals.
384
+ *
385
+ * Adds the `MdzPrecompiled` import and other required imports (DocsLink, Code, resolve).
386
+ * Removes Mdz import declarations that are no longer referenced.
387
+ *
388
+ * Handles both full removal (single-specifier imports) and partial removal
389
+ * (multi-specifier imports where only the Mdz specifier is removed).
390
+ *
391
+ * To avoid MagicString boundary conflicts when the insertion position falls inside
392
+ * a removal range, one removable Mdz import is overwritten with the MdzPrecompiled
393
+ * import line instead of using separate remove + appendLeft.
394
+ */
395
+ const manage_imports = (s, ast, transformations, removable_imports, compiled_component_import, source) => {
396
+ // Collect all required imports across transformations
397
+ const required = new Map();
398
+ for (const t of transformations) {
399
+ for (const [name, info] of t.required_imports) {
400
+ required.set(name, info);
401
+ }
402
+ }
403
+ // Always need MdzPrecompiled when transformations occur
404
+ required.set(PRECOMPILED_NAME, { path: compiled_component_import, kind: 'default' });
405
+ const script = ast.instance;
406
+ // Separate full removals from partial removals
407
+ const full_removals = new Set();
408
+ const partial_removals = [];
409
+ for (const [, action] of removable_imports) {
410
+ if (action.kind === 'full') {
411
+ full_removals.add(action.node);
412
+ }
413
+ else {
414
+ partial_removals.push(action);
415
+ }
416
+ }
417
+ if (!script) {
418
+ // No instance script — removable_imports won't apply (imports are in module script if any)
419
+ // Just add all required imports
420
+ const lines = generate_import_lines(required);
421
+ if (ast.module) {
422
+ s.appendLeft(ast.module.end, `\n\n<script lang="ts">\n${lines}\n</script>`);
423
+ }
424
+ else {
425
+ s.prepend(`<script lang="ts">\n${lines}\n</script>\n\n`);
426
+ }
427
+ // Remove Mdz imports from module script if removable
428
+ for (const node of full_removals) {
429
+ remove_import_declaration(s, node, source);
430
+ }
431
+ // Apply partial removals
432
+ for (const action of partial_removals) {
433
+ remove_import_specifier(s, action.node, action.specifier_to_remove, source);
434
+ }
435
+ return;
436
+ }
437
+ // Check existing imports to avoid duplicates — tracks both name AND source path
438
+ const existing = new Map();
439
+ for (const node of script.content.body) {
440
+ if (node.type === 'ImportDeclaration') {
441
+ const source_path = node.source.value;
442
+ for (const spec of node.specifiers) {
443
+ existing.set(spec.local.name, source_path);
444
+ }
445
+ }
446
+ }
447
+ const to_add = new Map();
448
+ for (const [name, info] of required) {
449
+ const existing_path = existing.get(name);
450
+ if (existing_path === undefined) {
451
+ to_add.set(name, info);
452
+ }
453
+ }
454
+ // Strategy: if we're both adding MdzPrecompiled and removing an Mdz import,
455
+ // overwrite one full-removable import with the MdzPrecompiled line to avoid
456
+ // MagicString boundary conflicts. Other imports use normal appendLeft.
457
+ let overwrite_target = null;
458
+ if (to_add.has(PRECOMPILED_NAME) && full_removals.size > 0) {
459
+ // Pick the first removable import to overwrite
460
+ overwrite_target = full_removals.values().next().value ?? null;
461
+ }
462
+ // Generate the MdzPrecompiled import line separately if using overwrite
463
+ if (overwrite_target) {
464
+ const node_start = overwrite_target.start;
465
+ const node_end = overwrite_target.end;
466
+ // Find the start of the line (consume leading whitespace)
467
+ let line_start = node_start;
468
+ while (line_start > 0 && (source[line_start - 1] === '\t' || source[line_start - 1] === ' ')) {
469
+ line_start--;
470
+ }
471
+ // Extract indentation from the original import line rather than hardcoding a tab.
472
+ // The import node's start is after any leading whitespace, so the indentation is
473
+ // the characters between the line start and the node start.
474
+ const original_indent = source.slice(line_start, node_start);
475
+ const precompiled_line = `${original_indent}import ${PRECOMPILED_NAME} from '${compiled_component_import}';`;
476
+ s.overwrite(line_start, node_end, precompiled_line);
477
+ to_add.delete(PRECOMPILED_NAME);
478
+ }
479
+ // When there are partial removals but no full-removal overwrite target,
480
+ // the appendLeft at find_import_insert_position can conflict with the
481
+ // partial removal's overwrite (they share the same node.end boundary).
482
+ // Bundle the new imports into the first partial removal's overwrite instead.
483
+ const insert_pos = find_import_insert_position(script);
484
+ let partial_carrier = null;
485
+ if (to_add.size > 0 && partial_removals.length > 0) {
486
+ for (const action of partial_removals) {
487
+ if (action.node.end === insert_pos) {
488
+ partial_carrier = action;
489
+ break;
490
+ }
491
+ }
492
+ }
493
+ // Add remaining imports — either bundled with a partial removal or via appendLeft
494
+ let carrier_lines = '';
495
+ if (partial_carrier && to_add.size > 0) {
496
+ carrier_lines = '\n' + generate_import_lines(to_add);
497
+ to_add.clear();
498
+ }
499
+ if (to_add.size > 0) {
500
+ const lines = generate_import_lines(to_add);
501
+ s.appendLeft(insert_pos, '\n' + lines);
502
+ }
503
+ // Remove remaining full Mdz imports (skip the overwrite target)
504
+ for (const node of full_removals) {
505
+ if (node === overwrite_target)
506
+ continue;
507
+ remove_import_declaration(s, node, source);
508
+ }
509
+ // Apply partial import removals
510
+ for (const action of partial_removals) {
511
+ remove_import_specifier(s, action.node, action.specifier_to_remove, source, action === partial_carrier ? carrier_lines : '');
512
+ }
513
+ };
@@ -18,7 +18,7 @@
18
18
  * - Bare identifiers → wrapped in backticks
19
19
  *
20
20
  * @param content Raw `@see` tag content in TSDoc format
21
- * @returns mdz-formatted string ready for `<Mdz>` component
21
+ * @returns mdz-formatted string ready for `Mdz` component
22
22
  *
23
23
  * @example
24
24
  * ```ts
package/dist/tsdoc_mdz.js CHANGED
@@ -21,7 +21,7 @@ const format_reference = (ref) => (mdz_is_url(ref) ? ref : `\`${ref}\``);
21
21
  * - Bare identifiers → wrapped in backticks
22
22
  *
23
23
  * @param content Raw `@see` tag content in TSDoc format
24
- * @returns mdz-formatted string ready for `<Mdz>` component
24
+ * @returns mdz-formatted string ready for `Mdz` component
25
25
  *
26
26
  * @example
27
27
  * ```ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fuzdev/fuz_ui",
3
- "version": "0.181.1",
3
+ "version": "0.182.1",
4
4
  "description": "Svelte UI library",
5
5
  "motto": "friendly user zystem",
6
6
  "glyph": "🧶",
@@ -26,6 +26,7 @@
26
26
  "build": "gro build",
27
27
  "check": "gro check",
28
28
  "test": "gro test",
29
+ "fixtures:update": "gro src/test/fixtures/update",
29
30
  "preview": "vite preview",
30
31
  "deploy": "gro deploy"
31
32
  },
@@ -34,12 +35,13 @@
34
35
  "node": ">=22.15"
35
36
  },
36
37
  "peerDependencies": {
37
- "@fuzdev/fuz_code": ">=0.41.0",
38
- "@fuzdev/fuz_css": ">=0.45.0",
39
- "@fuzdev/fuz_util": ">=0.48.2",
38
+ "@fuzdev/fuz_code": ">=0.44.0",
39
+ "@fuzdev/fuz_css": ">=0.47.0",
40
+ "@fuzdev/fuz_util": ">=0.49.2",
40
41
  "@jridgewell/trace-mapping": "^0.3",
41
- "@ryanatkn/gro": ">=0.186.0",
42
+ "@ryanatkn/gro": ">=0.191.0",
42
43
  "@sveltejs/kit": "^2.47.3",
44
+ "@types/estree": "^1",
43
45
  "esm-env": "^1",
44
46
  "svelte": "^5",
45
47
  "svelte2tsx": "^0.7.45",
@@ -49,44 +51,59 @@
49
51
  "@fuzdev/fuz_code": {
50
52
  "optional": true
51
53
  },
54
+ "@fuzdev/fuz_util": {
55
+ "optional": true
56
+ },
52
57
  "@jridgewell/trace-mapping": {
53
58
  "optional": true
54
59
  },
55
60
  "@ryanatkn/gro": {
56
61
  "optional": true
57
62
  },
63
+ "@types/estree": {
64
+ "optional": true
65
+ },
58
66
  "esm-env": {
59
67
  "optional": true
68
+ },
69
+ "svelte2tsx": {
70
+ "optional": true
71
+ },
72
+ "zod": {
73
+ "optional": true
60
74
  }
61
75
  },
62
76
  "devDependencies": {
63
77
  "@changesets/changelog-git": "^0.2.1",
64
- "@fuzdev/fuz_code": "^0.41.0",
78
+ "@fuzdev/fuz_code": "^0.44.1",
65
79
  "@fuzdev/fuz_css": "^0.47.0",
66
- "@fuzdev/fuz_util": "^0.48.3",
80
+ "@fuzdev/fuz_util": "^0.50.1",
67
81
  "@jridgewell/trace-mapping": "^0.3.31",
68
82
  "@ryanatkn/eslint-config": "^0.9.0",
69
- "@ryanatkn/gro": "^0.190.0",
83
+ "@ryanatkn/gro": "^0.191.0",
70
84
  "@sveltejs/adapter-static": "^3.0.10",
71
85
  "@sveltejs/kit": "^2.50.1",
72
86
  "@sveltejs/package": "^2.5.7",
73
87
  "@sveltejs/vite-plugin-svelte": "^6.2.4",
88
+ "@types/estree": "^1.0.8",
74
89
  "@types/node": "^24.10.1",
75
90
  "@webref/css": "^8.2.0",
76
91
  "eslint": "^9.39.1",
77
92
  "eslint-plugin-svelte": "^3.13.1",
78
93
  "esm-env": "^1.2.2",
79
94
  "jsdom": "^27.2.0",
95
+ "magic-string": "^0.30.21",
80
96
  "prettier": "^3.7.4",
81
97
  "prettier-plugin-svelte": "^3.4.1",
82
98
  "svelte": "^5.49.1",
83
99
  "svelte-check": "^4.3.6",
84
- "svelte2tsx": "^0.7.46",
100
+ "svelte2tsx": "^0.7.47",
85
101
  "tslib": "^2.8.1",
86
102
  "typescript": "^5.9.3",
87
103
  "typescript-eslint": "^8.48.1",
88
104
  "vitest": "^4.0.15",
89
- "zod": "^4.1.13"
105
+ "zimmerframe": "^1.1.4",
106
+ "zod": "^4.3.6"
90
107
  },
91
108
  "prettier": {
92
109
  "plugins": [