@fuzdev/fuz_ui 0.198.1 → 0.199.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 (101) hide show
  1. package/dist/ApiIndex.svelte +2 -2
  2. package/dist/ApiModule.svelte +1 -1
  3. package/dist/LibraryDetail.svelte +14 -16
  4. package/dist/LibraryDetail.svelte.d.ts.map +1 -1
  5. package/dist/LibrarySummary.svelte +12 -12
  6. package/dist/Mdz.svelte +2 -2
  7. package/dist/MdzNodeView.svelte +8 -6
  8. package/dist/MdzNodeView.svelte.d.ts.map +1 -1
  9. package/dist/MdzRoot.svelte +30 -0
  10. package/dist/MdzRoot.svelte.d.ts +12 -0
  11. package/dist/MdzRoot.svelte.d.ts.map +1 -0
  12. package/dist/MdzStream.svelte +32 -0
  13. package/dist/MdzStream.svelte.d.ts +12 -0
  14. package/dist/MdzStream.svelte.d.ts.map +1 -0
  15. package/dist/MdzStreamNodeView.svelte +106 -0
  16. package/dist/MdzStreamNodeView.svelte.d.ts +9 -0
  17. package/dist/MdzStreamNodeView.svelte.d.ts.map +1 -0
  18. package/dist/declaration.svelte.js +2 -2
  19. package/dist/library.svelte.d.ts +9 -70
  20. package/dist/library.svelte.d.ts.map +1 -1
  21. package/dist/library.svelte.js +24 -18
  22. package/dist/library_helpers.d.ts +3 -3
  23. package/dist/library_helpers.js +3 -3
  24. package/dist/mdz.d.ts.map +1 -1
  25. package/dist/mdz.js +37 -29
  26. package/dist/mdz_components.d.ts +37 -23
  27. package/dist/mdz_components.d.ts.map +1 -1
  28. package/dist/mdz_components.js +16 -4
  29. package/dist/mdz_helpers.d.ts +46 -14
  30. package/dist/mdz_helpers.d.ts.map +1 -1
  31. package/dist/mdz_helpers.js +189 -56
  32. package/dist/mdz_lexer.d.ts.map +1 -1
  33. package/dist/mdz_lexer.js +18 -22
  34. package/dist/mdz_opcodes.d.ts +174 -0
  35. package/dist/mdz_opcodes.d.ts.map +1 -0
  36. package/dist/mdz_opcodes.js +14 -0
  37. package/dist/mdz_opcodes_to_nodes.d.ts +20 -0
  38. package/dist/mdz_opcodes_to_nodes.d.ts.map +1 -0
  39. package/dist/mdz_opcodes_to_nodes.js +332 -0
  40. package/dist/mdz_stream_parser.d.ts +80 -0
  41. package/dist/mdz_stream_parser.d.ts.map +1 -0
  42. package/dist/mdz_stream_parser.js +354 -0
  43. package/dist/mdz_stream_parser_block.d.ts +76 -0
  44. package/dist/mdz_stream_parser_block.d.ts.map +1 -0
  45. package/dist/mdz_stream_parser_block.js +458 -0
  46. package/dist/mdz_stream_parser_inline.d.ts +52 -0
  47. package/dist/mdz_stream_parser_inline.d.ts.map +1 -0
  48. package/dist/mdz_stream_parser_inline.js +378 -0
  49. package/dist/mdz_stream_parser_link.d.ts +22 -0
  50. package/dist/mdz_stream_parser_link.d.ts.map +1 -0
  51. package/dist/mdz_stream_parser_link.js +215 -0
  52. package/dist/mdz_stream_parser_state.d.ts +187 -0
  53. package/dist/mdz_stream_parser_state.d.ts.map +1 -0
  54. package/dist/mdz_stream_parser_state.js +398 -0
  55. package/dist/mdz_stream_parser_text.d.ts +14 -0
  56. package/dist/mdz_stream_parser_text.d.ts.map +1 -0
  57. package/dist/mdz_stream_parser_text.js +50 -0
  58. package/dist/mdz_stream_parser_url.d.ts +60 -0
  59. package/dist/mdz_stream_parser_url.d.ts.map +1 -0
  60. package/dist/mdz_stream_parser_url.js +307 -0
  61. package/dist/mdz_stream_state.svelte.d.ts +44 -0
  62. package/dist/mdz_stream_state.svelte.d.ts.map +1 -0
  63. package/dist/mdz_stream_state.svelte.js +357 -0
  64. package/dist/mdz_token_parser.d.ts.map +1 -1
  65. package/dist/mdz_token_parser.js +8 -39
  66. package/dist/module.svelte.js +1 -1
  67. package/dist/site.svelte.d.ts +13 -0
  68. package/dist/site.svelte.d.ts.map +1 -1
  69. package/dist/site.svelte.js +8 -2
  70. package/dist/tsdoc_mdz.d.ts +2 -2
  71. package/dist/tsdoc_mdz.js +2 -2
  72. package/dist/vite_plugin_pkg_json.d.ts +63 -0
  73. package/dist/vite_plugin_pkg_json.d.ts.map +1 -0
  74. package/dist/vite_plugin_pkg_json.js +126 -0
  75. package/package.json +6 -5
  76. package/src/lib/declaration.svelte.ts +2 -2
  77. package/src/lib/library.svelte.ts +37 -20
  78. package/src/lib/library_helpers.ts +3 -3
  79. package/src/lib/mdz.ts +38 -29
  80. package/src/lib/mdz_components.ts +40 -19
  81. package/src/lib/mdz_helpers.ts +199 -56
  82. package/src/lib/mdz_lexer.ts +18 -20
  83. package/src/lib/mdz_opcodes.ts +205 -0
  84. package/src/lib/mdz_opcodes_to_nodes.ts +375 -0
  85. package/src/lib/mdz_stream_parser.ts +415 -0
  86. package/src/lib/mdz_stream_parser_block.ts +532 -0
  87. package/src/lib/mdz_stream_parser_inline.ts +414 -0
  88. package/src/lib/mdz_stream_parser_link.ts +271 -0
  89. package/src/lib/mdz_stream_parser_state.ts +539 -0
  90. package/src/lib/mdz_stream_parser_text.ts +77 -0
  91. package/src/lib/mdz_stream_parser_url.ts +365 -0
  92. package/src/lib/mdz_stream_state.svelte.ts +387 -0
  93. package/src/lib/mdz_token_parser.ts +13 -40
  94. package/src/lib/module.svelte.ts +1 -1
  95. package/src/lib/site.svelte.ts +17 -2
  96. package/src/lib/tsdoc_mdz.ts +2 -2
  97. package/src/lib/vite_plugin_pkg_json.ts +142 -0
  98. package/dist/package_helpers.d.ts +0 -150
  99. package/dist/package_helpers.d.ts.map +0 -1
  100. package/dist/package_helpers.js +0 -179
  101. package/src/lib/package_helpers.ts +0 -186
@@ -0,0 +1,387 @@
1
+ /**
2
+ * Reactive state for the mdz streaming renderer.
3
+ *
4
+ * Maintains a reactive tree of `MdzStreamNode` objects that Svelte 5 can
5
+ * efficiently update. Opcodes from `MdzStreamParser` are applied via
6
+ * `apply()` or `apply_batch()`, mutating the tree in place.
7
+ *
8
+ * @module
9
+ */
10
+
11
+ import {DEV} from 'esm-env';
12
+
13
+ import type {MdzNodeId, MdzNodeType, MdzOpcode} from './mdz_opcodes.js';
14
+
15
+ /**
16
+ * A reactive node in the stream renderer tree.
17
+ * Fields are `$state` so Svelte re-renders only what changes.
18
+ */
19
+ export class MdzStreamNode {
20
+ id: MdzNodeId;
21
+ type: MdzNodeType;
22
+ content: string = $state('');
23
+ children: Array<MdzStreamNode> = $state([]);
24
+ // metadata
25
+ level?: number;
26
+ reference?: string = $state();
27
+ link_type?: string = $state();
28
+ name?: string;
29
+ lang?: string | null;
30
+ heading_id?: string = $state();
31
+
32
+ constructor(id: MdzNodeId, type: MdzNodeType) {
33
+ this.id = id;
34
+ this.type = type;
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Reactive state manager for streaming mdz content.
40
+ * Apply opcodes to incrementally build and update the render tree.
41
+ */
42
+ export class MdzStreamState {
43
+ root: Array<MdzStreamNode> = $state([]);
44
+
45
+ /** Node lookup by ID. */
46
+ #nodes = new Map<MdzNodeId, MdzStreamNode>();
47
+
48
+ /** Parent tracking for re-parenting during revert. */
49
+ #parents = new Map<MdzNodeId, MdzNodeId | null>();
50
+
51
+ /** Stack of open container IDs (for knowing current parent). */
52
+ #stack: Array<MdzNodeId> = [];
53
+
54
+ /**
55
+ * Register a new node and attach it to its current parent (innermost open
56
+ * container, or root). Shared by `open`, `text`, and `void` paths.
57
+ */
58
+ #attach(node: MdzStreamNode): void {
59
+ this.#nodes.set(node.id, node);
60
+ const parent_id = this.#stack.length > 0 ? this.#stack[this.#stack.length - 1]! : null;
61
+ this.#parents.set(node.id, parent_id);
62
+ if (parent_id !== null) {
63
+ const parent = this.#nodes.get(parent_id);
64
+ if (DEV && !parent) {
65
+ throw new Error(`MdzStreamState: parent id ${parent_id} on stack but missing from nodes`);
66
+ }
67
+ if (parent) parent.children.push(node);
68
+ } else {
69
+ this.root.push(node);
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Apply a single opcode.
75
+ */
76
+ apply(opcode: MdzOpcode): void {
77
+ switch (opcode.type) {
78
+ case 'open': {
79
+ if (DEV && this.#nodes.has(opcode.id)) {
80
+ throw new Error(
81
+ `MdzStreamState: duplicate open for id ${opcode.id} (node_type '${opcode.node_type}')`,
82
+ );
83
+ }
84
+ const node = new MdzStreamNode(opcode.id, opcode.node_type);
85
+ if (opcode.level !== undefined) node.level = opcode.level;
86
+ if (opcode.name !== undefined) node.name = opcode.name;
87
+ if (opcode.lang !== undefined) node.lang = opcode.lang;
88
+ this.#attach(node);
89
+ this.#stack.push(opcode.id);
90
+ break;
91
+ }
92
+
93
+ case 'close': {
94
+ if (DEV) {
95
+ if (!this.#nodes.has(opcode.id)) {
96
+ throw new Error(`MdzStreamState: close for unknown id ${opcode.id}`);
97
+ }
98
+ if (!this.#stack.includes(opcode.id)) {
99
+ throw new Error(`MdzStreamState: close for id ${opcode.id} which is not on the stack`);
100
+ }
101
+ }
102
+ const node = this.#nodes.get(opcode.id);
103
+ if (node) {
104
+ if (opcode.discard) {
105
+ // drop the node and its descendants from its parent's children
106
+ const parent_id = this.#parents.get(opcode.id);
107
+ const parent_children =
108
+ parent_id !== null && parent_id !== undefined
109
+ ? this.#nodes.get(parent_id)?.children
110
+ : this.root;
111
+ if (parent_children) {
112
+ const idx = parent_children.indexOf(node);
113
+ if (idx !== -1) parent_children.splice(idx, 1);
114
+ }
115
+ } else {
116
+ // apply deferred metadata
117
+ if (opcode.reference !== undefined) node.reference = opcode.reference;
118
+ if (opcode.link_type !== undefined) node.link_type = opcode.link_type;
119
+ if (opcode.heading_id !== undefined) node.heading_id = opcode.heading_id;
120
+ // Code/Codeblock: collapse text children into `content` so renderers
121
+ // can read it directly without walking children every render.
122
+ if (node.type === 'Code' || node.type === 'Codeblock') {
123
+ let content = '';
124
+ for (const child of node.children) content += child.content;
125
+ node.content = content;
126
+ }
127
+ }
128
+ }
129
+ // pop from stack
130
+ const idx = this.#stack.lastIndexOf(opcode.id);
131
+ if (idx !== -1) this.#stack.splice(idx, 1);
132
+ // cleanup: closed nodes have no further opcode interactions.
133
+ // children are retained in the render tree via children arrays
134
+ // (discarded nodes' subtrees also need full cleanup, which the
135
+ // traversal in `#cleanup_node` already handles).
136
+ this.#cleanup_node(opcode.id);
137
+ break;
138
+ }
139
+
140
+ case 'text': {
141
+ if (DEV && this.#nodes.has(opcode.id)) {
142
+ throw new Error(
143
+ `MdzStreamState: duplicate text for id ${opcode.id} (text_type '${opcode.text_type}')`,
144
+ );
145
+ }
146
+ const node = new MdzStreamNode(opcode.id, opcode.text_type);
147
+ node.content = opcode.content;
148
+ this.#attach(node);
149
+ break;
150
+ }
151
+
152
+ case 'append_text': {
153
+ if (DEV && !this.#nodes.has(opcode.id)) {
154
+ throw new Error(`MdzStreamState: append_text for unknown id ${opcode.id}`);
155
+ }
156
+ const node = this.#nodes.get(opcode.id);
157
+ if (node) {
158
+ node.content += opcode.content;
159
+ }
160
+ break;
161
+ }
162
+
163
+ case 'trim_text': {
164
+ if (DEV && !this.#nodes.has(opcode.id)) {
165
+ throw new Error(`MdzStreamState: trim_text for unknown id ${opcode.id}`);
166
+ }
167
+ const node = this.#nodes.get(opcode.id);
168
+ if (node) {
169
+ node.content = node.content.slice(0, node.content.length - opcode.count);
170
+ if (node.content.length === 0) {
171
+ const parent_id = this.#parents.get(opcode.id);
172
+ const parent_children =
173
+ parent_id !== null && parent_id !== undefined
174
+ ? this.#nodes.get(parent_id)?.children
175
+ : this.root;
176
+ if (parent_children) {
177
+ const idx = parent_children.indexOf(node);
178
+ if (idx !== -1) parent_children.splice(idx, 1);
179
+ }
180
+ this.#nodes.delete(opcode.id);
181
+ this.#parents.delete(opcode.id);
182
+ }
183
+ }
184
+ break;
185
+ }
186
+
187
+ case 'void': {
188
+ if (DEV && this.#nodes.has(opcode.id)) {
189
+ throw new Error(
190
+ `MdzStreamState: duplicate void for id ${opcode.id} (node_type '${opcode.node_type}')`,
191
+ );
192
+ }
193
+ const node = new MdzStreamNode(opcode.id, opcode.node_type);
194
+ this.#attach(node);
195
+ break;
196
+ }
197
+
198
+ case 'revert': {
199
+ if (DEV) {
200
+ if (!this.#nodes.has(opcode.id)) {
201
+ throw new Error(`MdzStreamState: revert for unknown id ${opcode.id}`);
202
+ }
203
+ if (!this.#stack.includes(opcode.id)) {
204
+ throw new Error(`MdzStreamState: revert for id ${opcode.id} which is not on the stack`);
205
+ }
206
+ }
207
+ const node = this.#nodes.get(opcode.id);
208
+ if (!node) break;
209
+
210
+ const parent_id = this.#parents.get(opcode.id);
211
+ const parent_children =
212
+ parent_id !== null && parent_id !== undefined
213
+ ? this.#nodes.get(parent_id)?.children
214
+ : this.root;
215
+
216
+ if (!parent_children) {
217
+ if (DEV) {
218
+ throw new Error(
219
+ `MdzStreamState: revert for id ${opcode.id} but parent has no children array`,
220
+ );
221
+ }
222
+ break;
223
+ }
224
+
225
+ // find the node in parent's children
226
+ let node_idx = -1;
227
+ for (let i = 0; i < parent_children.length; i++) {
228
+ if (parent_children[i]!.id === opcode.id) {
229
+ node_idx = i;
230
+ break;
231
+ }
232
+ }
233
+ if (node_idx === -1) {
234
+ if (DEV) {
235
+ throw new Error(
236
+ `MdzStreamState: revert for id ${opcode.id} but node not found in parent's children`,
237
+ );
238
+ }
239
+ break;
240
+ }
241
+
242
+ // collect replacement content: literal delimiter text + re-parented children
243
+ const replacement_nodes: Array<MdzStreamNode> = [];
244
+ if (opcode.replacement_text) {
245
+ const text_node = new MdzStreamNode(opcode.id, 'Text');
246
+ text_node.content = opcode.replacement_text;
247
+ replacement_nodes.push(text_node);
248
+ }
249
+ for (const child of node.children) {
250
+ replacement_nodes.push(child);
251
+ }
252
+
253
+ if (opcode.wrap_node_type != null && opcode.wrap_id != null) {
254
+ if (DEV && this.#nodes.has(opcode.wrap_id)) {
255
+ throw new Error(
256
+ `MdzStreamState: revert wrap_id ${opcode.wrap_id} already exists in nodes`,
257
+ );
258
+ }
259
+ // block-level revert: wrap content in a new container and push onto stack
260
+ const wrapper = new MdzStreamNode(opcode.wrap_id, opcode.wrap_node_type);
261
+ for (const rn of replacement_nodes) {
262
+ wrapper.children.push(rn);
263
+ }
264
+ for (const child of node.children) {
265
+ this.#parents.set(child.id, opcode.wrap_id);
266
+ }
267
+ this.#nodes.set(opcode.wrap_id, wrapper);
268
+ this.#parents.set(opcode.wrap_id, parent_id ?? null);
269
+ parent_children.splice(node_idx, 1, wrapper);
270
+ this.#stack.push(opcode.wrap_id);
271
+ } else {
272
+ // inline revert: re-parent directly to grandparent
273
+ for (const child of node.children) {
274
+ this.#parents.set(child.id, parent_id ?? null);
275
+ }
276
+ parent_children.splice(node_idx, 1, ...replacement_nodes);
277
+ }
278
+
279
+ // remove reverted node from stack
280
+ const stack_idx = this.#stack.lastIndexOf(opcode.id);
281
+ if (stack_idx !== -1) this.#stack.splice(stack_idx, 1);
282
+
283
+ // cleanup
284
+ this.#nodes.delete(opcode.id);
285
+ this.#parents.delete(opcode.id);
286
+ break;
287
+ }
288
+
289
+ case 'wrap': {
290
+ if (DEV) {
291
+ if (!this.#nodes.has(opcode.target_id)) {
292
+ throw new Error(`MdzStreamState: wrap target_id ${opcode.target_id} not in nodes`);
293
+ }
294
+ if (this.#nodes.has(opcode.id)) {
295
+ throw new Error(`MdzStreamState: wrap id ${opcode.id} already exists in nodes`);
296
+ }
297
+ }
298
+ const target = this.#nodes.get(opcode.target_id);
299
+ if (!target) break;
300
+
301
+ const parent_id = this.#parents.get(opcode.target_id);
302
+ const parent_children =
303
+ parent_id !== null && parent_id !== undefined
304
+ ? this.#nodes.get(parent_id)?.children
305
+ : this.root;
306
+
307
+ if (!parent_children) break;
308
+
309
+ // find target in parent's children
310
+ let target_idx = -1;
311
+ for (let i = 0; i < parent_children.length; i++) {
312
+ if (parent_children[i]!.id === opcode.target_id) {
313
+ target_idx = i;
314
+ break;
315
+ }
316
+ }
317
+ if (target_idx === -1) break;
318
+
319
+ // handle trailing punctuation trim
320
+ let trimmed_node: MdzStreamNode | null = null;
321
+ if (opcode.trim_end && opcode.trim_end > 0 && opcode.trim_id != null) {
322
+ const trimmed_content = target.content.slice(target.content.length - opcode.trim_end);
323
+ target.content = target.content.slice(0, target.content.length - opcode.trim_end);
324
+ trimmed_node = new MdzStreamNode(opcode.trim_id, 'Text');
325
+ trimmed_node.content = trimmed_content;
326
+ this.#nodes.set(opcode.trim_id, trimmed_node);
327
+ this.#parents.set(opcode.trim_id, parent_id ?? null);
328
+ }
329
+
330
+ // create Link wrapper containing target
331
+ const link = new MdzStreamNode(opcode.id, opcode.node_type);
332
+ link.reference = opcode.reference;
333
+ link.link_type = opcode.link_type;
334
+ link.children.push(target);
335
+ this.#nodes.set(opcode.id, link);
336
+ this.#parents.set(opcode.id, parent_id ?? null);
337
+ this.#parents.set(opcode.target_id, opcode.id);
338
+
339
+ // replace target with [Link, trimmed?] in parent's children
340
+ if (trimmed_node) {
341
+ parent_children.splice(target_idx, 1, link, trimmed_node);
342
+ } else {
343
+ parent_children[target_idx] = link;
344
+ }
345
+
346
+ // cleanup: Link is complete (no close opcode coming).
347
+ // trim_id (if any) stays in maps — cleaned when parent closes.
348
+ this.#cleanup_node(opcode.id);
349
+ break;
350
+ }
351
+ }
352
+ }
353
+
354
+ /**
355
+ * Remove a node and its descendants from the lookup maps. Iterative to avoid
356
+ * stack overflow on deep trees.
357
+ *
358
+ * Parser invariant: once a container closes, no future opcode targets that
359
+ * subtree (no `append_text` to a closed Text, no nested `wrap`/`revert`).
360
+ * Children stay reachable via `parent.children` arrays — only the lookup
361
+ * maps are pruned. If this invariant is violated, the misbehaving opcode
362
+ * triggers a DEV throw in `apply()` and silently no-ops in production.
363
+ */
364
+ #cleanup_node(id: MdzNodeId): void {
365
+ const queue: Array<MdzNodeId> = [id];
366
+ while (queue.length > 0) {
367
+ const current_id = queue.pop()!;
368
+ const node = this.#nodes.get(current_id);
369
+ if (node) {
370
+ for (const child of node.children) {
371
+ queue.push(child.id);
372
+ }
373
+ }
374
+ this.#nodes.delete(current_id);
375
+ this.#parents.delete(current_id);
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Apply a batch of opcodes.
381
+ */
382
+ apply_batch(opcodes: Array<MdzOpcode>): void {
383
+ for (const op of opcodes) {
384
+ this.apply(op);
385
+ }
386
+ }
387
+ }
@@ -18,7 +18,12 @@ import type {
18
18
  MdzElementNode,
19
19
  MdzComponentNode,
20
20
  } from './mdz.js';
21
- import {extract_single_tag, mdz_heading_id} from './mdz_helpers.js';
21
+ import {
22
+ extract_single_tag,
23
+ mdz_heading_id,
24
+ mdz_merge_adjacent_text,
25
+ mdz_push_merging_text,
26
+ } from './mdz_helpers.js';
22
27
  import {
23
28
  MdzLexer,
24
29
  type MdzToken,
@@ -124,7 +129,7 @@ class MdzTokenParser {
124
129
  if (node) children.push(node);
125
130
  }
126
131
 
127
- const merged = this.#merge_adjacent_text(children);
132
+ const merged = mdz_merge_adjacent_text(children);
128
133
  const end = merged.length > 0 ? merged[merged.length - 1]!.end : start + level + 1;
129
134
 
130
135
  return {type: 'Heading', level, id: mdz_heading_id(merged), children: merged, start, end};
@@ -228,7 +233,7 @@ class MdzTokenParser {
228
233
  break;
229
234
  }
230
235
  const node = this.#parse_inline();
231
- if (node) children.push(node);
236
+ if (node) mdz_push_merging_text(children, node);
232
237
  }
233
238
 
234
239
  // Unclosed - treat as text
@@ -257,7 +262,7 @@ class MdzTokenParser {
257
262
  break;
258
263
  }
259
264
  const node = this.#parse_inline();
260
- if (node) children.push(node);
265
+ if (node) mdz_push_merging_text(children, node);
261
266
  }
262
267
 
263
268
  return {type: 'Text', content: '_', start, end: start + 1};
@@ -285,7 +290,7 @@ class MdzTokenParser {
285
290
  break;
286
291
  }
287
292
  const node = this.#parse_inline();
288
- if (node) children.push(node);
293
+ if (node) mdz_push_merging_text(children, node);
289
294
  }
290
295
 
291
296
  return {type: 'Text', content: '~', start, end: start + 1};
@@ -329,7 +334,7 @@ class MdzTokenParser {
329
334
  break;
330
335
  }
331
336
  const node = this.#parse_inline();
332
- if (node) children.push(node);
337
+ if (node) mdz_push_merging_text(children, node);
333
338
  }
334
339
 
335
340
  return {type: 'Text', content: '[', start, end: start + 1};
@@ -364,7 +369,7 @@ class MdzTokenParser {
364
369
  return {type: node_type, name: tag_name, children, start, end: t.end};
365
370
  }
366
371
  const node = this.#parse_inline();
367
- if (node) children.push(node);
372
+ if (node) mdz_push_merging_text(children, node);
368
373
  }
369
374
 
370
375
  // Unclosed tag
@@ -414,7 +419,7 @@ class MdzTokenParser {
414
419
  }
415
420
 
416
421
  // Merge adjacent text nodes
417
- const merged = this.#merge_adjacent_text(paragraph_children.slice());
422
+ const merged = mdz_merge_adjacent_text(paragraph_children.slice());
418
423
  paragraph_children.length = 0;
419
424
 
420
425
  if (merged.length === 0) return null;
@@ -426,36 +431,4 @@ class MdzTokenParser {
426
431
  end: merged[merged.length - 1]!.end,
427
432
  };
428
433
  }
429
-
430
- #merge_adjacent_text(nodes: Array<MdzNode>): Array<MdzNode> {
431
- if (nodes.length <= 1) return nodes;
432
-
433
- const merged: Array<MdzNode> = [];
434
- let pending_text: MdzTextNode | null = null;
435
-
436
- for (const node of nodes) {
437
- if (node.type === 'Text') {
438
- if (pending_text) {
439
- pending_text = {
440
- type: 'Text',
441
- content: pending_text.content + node.content,
442
- start: pending_text.start,
443
- end: node.end,
444
- };
445
- } else {
446
- pending_text = {...node};
447
- }
448
- } else {
449
- if (pending_text) {
450
- merged.push(pending_text);
451
- pending_text = null;
452
- }
453
- merged.push(node);
454
- }
455
- }
456
-
457
- if (pending_text) merged.push(pending_text);
458
-
459
- return merged;
460
- }
461
434
  }
@@ -2,7 +2,7 @@ import type {ModuleJsonInput} from 'svelte-docinfo/types.js';
2
2
 
3
3
  import {Declaration} from './declaration.svelte.js';
4
4
  import type {Library} from './library.svelte.js';
5
- import {url_github_file} from './package_helpers.js';
5
+ import {url_github_file} from '@fuzdev/fuz_util/package_helpers.js';
6
6
 
7
7
  /**
8
8
  * Rich runtime representation of a module with computed properties.
@@ -1,6 +1,8 @@
1
1
  import type {Url} from '@fuzdev/fuz_util/url.js';
2
+ import type {PkgJson} from '@fuzdev/fuz_util/pkg_json.js';
2
3
 
3
4
  import {create_context} from './context_helpers.js';
5
+ import {repo_url_parse} from '@fuzdev/fuz_util/package_helpers.js';
4
6
  import type {SvgData} from './svg.js';
5
7
 
6
8
  export interface SiteStateOptions {
@@ -10,12 +12,20 @@ export interface SiteStateOptions {
10
12
  icon?: SvgData | null;
11
13
  /**
12
14
  * The site's glyph, a single unicode character identifying the project.
15
+ * Falls back to `pkg_json.glyph`.
13
16
  */
14
17
  glyph?: string | null;
15
18
  /**
16
19
  * The site's source repository url, e.g. for `DocsFooter` links.
20
+ * Falls back to the url parsed from `pkg_json.repository`.
17
21
  */
18
22
  repo_url?: Url | null;
23
+ /**
24
+ * Curated `package.json`, typically the default export of `virtual:pkg.json`.
25
+ * `glyph` and `repo_url` derive from it when not given explicitly; explicit
26
+ * props always win.
27
+ */
28
+ pkg_json?: PkgJson | null;
19
29
  }
20
30
 
21
31
  /**
@@ -23,6 +33,10 @@ export interface SiteStateOptions {
23
33
  * by components like `Breadcrumb` and `DocsFooter`. Set once per app at the root
24
34
  * layout. Distinct from `library_context`, which carries the heavy
25
35
  * module/declaration metadata that only docs and API pages need.
36
+ *
37
+ * Pass `pkg_json` (from `virtual:pkg.json`) to derive `glyph`/`repo_url` from the
38
+ * package's own metadata instead of hardcoding them; `icon` stays an explicit
39
+ * logo import since it's structured `SvgData`, not package metadata.
26
40
  */
27
41
  export class SiteState {
28
42
  icon: SvgData | null = $state.raw(null);
@@ -31,8 +45,9 @@ export class SiteState {
31
45
 
32
46
  constructor(options: SiteStateOptions = {}) {
33
47
  this.icon = options.icon ?? null;
34
- this.glyph = options.glyph ?? null;
35
- this.repo_url = options.repo_url ?? null;
48
+ this.glyph = options.glyph ?? options.pkg_json?.glyph ?? null;
49
+ this.repo_url =
50
+ options.repo_url ?? (options.pkg_json ? repo_url_parse(options.pkg_json.repository) : null);
36
51
  }
37
52
  }
38
53
 
@@ -37,8 +37,8 @@ const format_reference = (ref: string): string => (mdz_is_url(ref) ? ref : `\`${
37
37
  * tsdoc_see_to_mdz('{@link SomeType}')
38
38
  * // → '`SomeType`'
39
39
  *
40
- * tsdoc_see_to_mdz('https://example.com')
41
- * // → 'https://example.com'
40
+ * tsdoc_see_to_mdz('https://fuz.dev')
41
+ * // → 'https://fuz.dev'
42
42
  *
43
43
  * tsdoc_see_to_mdz('[svelte-docinfo](https://github.com/ryanatkn/svelte-docinfo) for the analysis library')
44
44
  * // → '[svelte-docinfo](https://github.com/ryanatkn/svelte-docinfo) for the analysis library'