streetui 1.0.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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +127 -0
  3. package/dist/bin.cjs +894 -0
  4. package/dist/bin.cjs.map +1 -0
  5. package/dist/bin.d.cts +1 -0
  6. package/dist/bin.d.ts +1 -0
  7. package/dist/bin.js +892 -0
  8. package/dist/bin.js.map +1 -0
  9. package/dist/compile-B0q07Hzq.d.cts +656 -0
  10. package/dist/compile-B0q07Hzq.d.ts +656 -0
  11. package/dist/create-bin.cjs +896 -0
  12. package/dist/create-bin.cjs.map +1 -0
  13. package/dist/create-bin.d.cts +1 -0
  14. package/dist/create-bin.d.ts +1 -0
  15. package/dist/create-bin.js +894 -0
  16. package/dist/create-bin.js.map +1 -0
  17. package/dist/hydration-diagnostics-BE6xVWD1.d.cts +89 -0
  18. package/dist/hydration-diagnostics-Bck5dMbz.d.ts +89 -0
  19. package/dist/index.cjs +4284 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.cts +1759 -0
  22. package/dist/index.d.ts +1759 -0
  23. package/dist/index.js +4114 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/server-84Rz4g8W.d.cts +165 -0
  26. package/dist/server-D9GPmB49.d.ts +165 -0
  27. package/dist/server.cjs +972 -0
  28. package/dist/server.cjs.map +1 -0
  29. package/dist/server.d.cts +2 -0
  30. package/dist/server.d.ts +2 -0
  31. package/dist/server.js +940 -0
  32. package/dist/server.js.map +1 -0
  33. package/dist/testing.cjs +1754 -0
  34. package/dist/testing.cjs.map +1 -0
  35. package/dist/testing.d.cts +113 -0
  36. package/dist/testing.d.ts +113 -0
  37. package/dist/testing.js +1719 -0
  38. package/dist/testing.js.map +1 -0
  39. package/package.json +113 -0
  40. package/templates/basic/README.md +39 -0
  41. package/templates/basic/_gitignore +15 -0
  42. package/templates/basic/_package.json +21 -0
  43. package/templates/basic/public/styles.css +40 -0
  44. package/templates/basic/src/app.ts +62 -0
  45. package/templates/basic/src/main.ts +39 -0
  46. package/templates/basic/src/server.ts +40 -0
  47. package/templates/basic/streetui.config.ts +6 -0
  48. package/templates/basic/tsconfig.json +16 -0
  49. package/templates/ssr/README.md +46 -0
  50. package/templates/ssr/_gitignore +15 -0
  51. package/templates/ssr/_package.json +21 -0
  52. package/templates/ssr/public/favicon.svg +4 -0
  53. package/templates/ssr/public/styles.css +61 -0
  54. package/templates/ssr/src/app.ts +135 -0
  55. package/templates/ssr/src/main.ts +53 -0
  56. package/templates/ssr/src/server.ts +47 -0
  57. package/templates/ssr/streetui.config.ts +14 -0
  58. package/templates/ssr/tsconfig.json +16 -0
@@ -0,0 +1,1754 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/testing.ts
21
+ var testing_exports = {};
22
+ __export(testing_exports, {
23
+ VERSION: () => VERSION,
24
+ findAllByRole: () => findAllByRole,
25
+ findByRole: () => findByRole,
26
+ findByText: () => findByText,
27
+ flushUpdates: () => flushUpdates,
28
+ render: () => render,
29
+ renderOnce: () => renderOnce,
30
+ renderServerThenHydrate: () => renderServerThenHydrate,
31
+ waitFor: () => waitFor
32
+ });
33
+ module.exports = __toCommonJS(testing_exports);
34
+
35
+ // ../core/src/identity.ts
36
+ var _counter = 0;
37
+ function resetIdCounter() {
38
+ _counter = 0;
39
+ }
40
+
41
+ // ../core/src/lifecycle.ts
42
+ var CleanupRegistry = class {
43
+ _fns = [];
44
+ add(fn) {
45
+ this._fns.push(fn);
46
+ }
47
+ run() {
48
+ for (const fn of this._fns) {
49
+ try {
50
+ fn();
51
+ } catch {
52
+ }
53
+ }
54
+ this._fns.length = 0;
55
+ }
56
+ };
57
+
58
+ // ../core/src/diagnostics.ts
59
+ var DiagnosticError = class extends Error {
60
+ diagnostics;
61
+ constructor(diagnostics) {
62
+ const summary = diagnostics.filter((d) => d.severity === "error").map((d) => `[${d.code}] ${d.message}`).join("\n");
63
+ super(`StreetUI diagnostics:
64
+ ${summary}`);
65
+ this.name = "DiagnosticError";
66
+ this.diagnostics = diagnostics;
67
+ }
68
+ };
69
+ var DiagnosticCollector = class {
70
+ _diagnostics = [];
71
+ get diagnostics() {
72
+ return this._diagnostics;
73
+ }
74
+ get hasErrors() {
75
+ return this._diagnostics.some((d) => d.severity === "error");
76
+ }
77
+ get hasWarnings() {
78
+ return this._diagnostics.some((d) => d.severity === "warning");
79
+ }
80
+ error(code, message, location, cause) {
81
+ this._diagnostics.push({ severity: "error", code, message, location: location ?? void 0, cause: cause ?? void 0 });
82
+ }
83
+ warn(code, message, location) {
84
+ this._diagnostics.push({ severity: "warning", code, message, location: location ?? void 0, cause: void 0 });
85
+ }
86
+ info(code, message, location) {
87
+ this._diagnostics.push({ severity: "info", code, message, location: location ?? void 0, cause: void 0 });
88
+ }
89
+ merge(other) {
90
+ for (const d of other.diagnostics) {
91
+ this._diagnostics.push(d);
92
+ }
93
+ }
94
+ throwIfErrors() {
95
+ if (this.hasErrors) {
96
+ throw new DiagnosticError(this._diagnostics);
97
+ }
98
+ }
99
+ clear() {
100
+ this._diagnostics.length = 0;
101
+ }
102
+ };
103
+
104
+ // ../compiler/src/validation/validator.ts
105
+ function validateGraph(graph) {
106
+ const dc = new DiagnosticCollector();
107
+ dc.merge(graph.validate());
108
+ const pages = graph.findByType("page");
109
+ if (pages.length === 0) {
110
+ dc.warn(
111
+ "COMPILER_NO_PAGES",
112
+ "Application has no pages defined. At least one page is recommended."
113
+ );
114
+ }
115
+ graph.walk((node) => {
116
+ validateNode(node, dc);
117
+ });
118
+ return dc;
119
+ }
120
+ function validateNode(node, dc) {
121
+ switch (node.type) {
122
+ case "heading": {
123
+ const text = node.getProp("text");
124
+ if (text === void 0 || text === "") {
125
+ dc.warn("COMPILER_EMPTY_HEADING", `Heading node "${node.id}" has no text content`, {
126
+ nodeId: node.id
127
+ });
128
+ }
129
+ break;
130
+ }
131
+ case "image": {
132
+ const src = node.getProp("src");
133
+ const alt = node.getProp("alt");
134
+ if (!src) {
135
+ dc.error("COMPILER_IMAGE_NO_SRC", `Image node "${node.id}" is missing src`, {
136
+ nodeId: node.id
137
+ });
138
+ }
139
+ if (!alt) {
140
+ dc.warn("COMPILER_IMAGE_NO_ALT", `Image node "${node.id}" is missing alt text`, {
141
+ nodeId: node.id
142
+ });
143
+ }
144
+ break;
145
+ }
146
+ case "link": {
147
+ const href = node.getProp("href");
148
+ if (!href) {
149
+ dc.error("COMPILER_LINK_NO_HREF", `Link node "${node.id}" is missing href`, {
150
+ nodeId: node.id
151
+ });
152
+ }
153
+ break;
154
+ }
155
+ default:
156
+ break;
157
+ }
158
+ }
159
+
160
+ // ../compiler/src/transform/transform.ts
161
+ function transformGraph(graph) {
162
+ graph.walk((node, depth) => {
163
+ applyDefaults(node);
164
+ ensureRenderKey(node, depth);
165
+ });
166
+ }
167
+ function applyDefaults(node) {
168
+ switch (node.type) {
169
+ case "heading": {
170
+ if (node.getProp("level") === void 0) {
171
+ node.setProp("level", 1);
172
+ }
173
+ break;
174
+ }
175
+ case "input": {
176
+ if (node.getProp("inputType") === void 0) {
177
+ node.setProp("inputType", "text");
178
+ }
179
+ break;
180
+ }
181
+ case "link": {
182
+ if (node.getProp("external") === void 0) {
183
+ node.setProp("external", false);
184
+ }
185
+ break;
186
+ }
187
+ default:
188
+ break;
189
+ }
190
+ }
191
+ function ensureRenderKey(node, depth) {
192
+ if (node.getProp("_renderKey") === void 0) {
193
+ const key = node.key ?? `${node.type}:${node.id}:${depth}`;
194
+ node.setProp("_renderKey", key);
195
+ }
196
+ }
197
+
198
+ // ../compiler/src/compile.ts
199
+ function compile(app, options = {}) {
200
+ const strict = options.strict ?? true;
201
+ const strictWarnings = options.strictWarnings ?? false;
202
+ const dc = new DiagnosticCollector();
203
+ const graph = app.graph;
204
+ const validationDc = validateGraph(graph);
205
+ dc.merge(validationDc);
206
+ if (strict && dc.hasErrors) {
207
+ dc.throwIfErrors();
208
+ }
209
+ if (strictWarnings && dc.hasWarnings) {
210
+ throw new Error(
211
+ `[StreetUI Compiler] Compilation failed: warnings treated as errors.
212
+ ` + dc.diagnostics.filter((d) => d.severity === "warning").map((d) => ` [${d.code}] ${d.message}`).join("\n")
213
+ );
214
+ }
215
+ transformGraph(graph);
216
+ return {
217
+ graph,
218
+ diagnostics: dc,
219
+ name: graph.name,
220
+ version: graph.version,
221
+ compiledAt: Date.now()
222
+ };
223
+ }
224
+
225
+ // ../dom/src/browser-adapter.ts
226
+ var BrowserDOMAdapter = class {
227
+ createElement(tag, ns) {
228
+ if (ns !== void 0) {
229
+ return document.createElementNS(ns, tag);
230
+ }
231
+ return document.createElement(tag);
232
+ }
233
+ createTextNode(data) {
234
+ return document.createTextNode(data);
235
+ }
236
+ createComment(data) {
237
+ return document.createComment(data);
238
+ }
239
+ createFragment() {
240
+ return document.createDocumentFragment();
241
+ }
242
+ appendChild(parent, child) {
243
+ parent.appendChild(child);
244
+ }
245
+ insertBefore(parent, child, reference) {
246
+ parent.insertBefore(child, reference);
247
+ }
248
+ removeChild(parent, child) {
249
+ parent.removeChild(child);
250
+ }
251
+ replaceChild(parent, newChild, oldChild) {
252
+ parent.replaceChild(newChild, oldChild);
253
+ }
254
+ setAttribute(element, name, value) {
255
+ element.setAttribute(name, value);
256
+ }
257
+ removeAttribute(element, name) {
258
+ element.removeAttribute(name);
259
+ }
260
+ getAttribute(element, name) {
261
+ return element.getAttribute(name);
262
+ }
263
+ setProperty(element, name, value) {
264
+ element[name] = value;
265
+ }
266
+ setTextContent(node, text) {
267
+ node.textContent = text;
268
+ }
269
+ getTextContent(node) {
270
+ return node.textContent;
271
+ }
272
+ addEventListener(target, type, handler, options) {
273
+ target.addEventListener(type, handler, options);
274
+ }
275
+ removeEventListener(target, type, handler, options) {
276
+ target.removeEventListener(type, handler, options);
277
+ }
278
+ querySelector(root, selector) {
279
+ return root.querySelector(selector);
280
+ }
281
+ querySelectorAll(root, selector) {
282
+ return root.querySelectorAll(selector);
283
+ }
284
+ getElementById(id) {
285
+ return document.getElementById(id);
286
+ }
287
+ focus(element) {
288
+ element.focus?.();
289
+ }
290
+ isElement(node) {
291
+ return node.nodeType === Node.ELEMENT_NODE;
292
+ }
293
+ isTextNode(node) {
294
+ return node.nodeType === Node.TEXT_NODE;
295
+ }
296
+ tagName(element) {
297
+ return element.tagName.toLowerCase();
298
+ }
299
+ parentNode(node) {
300
+ return node.parentNode;
301
+ }
302
+ nextSibling(node) {
303
+ return node.nextSibling;
304
+ }
305
+ firstChild(node) {
306
+ return node.firstChild;
307
+ }
308
+ childNodes(node) {
309
+ return Array.from(node.childNodes);
310
+ }
311
+ };
312
+ var browserDOMAdapter = new BrowserDOMAdapter();
313
+
314
+ // ../dom/src/server-node.ts
315
+ var ServerStyle = class {
316
+ declarations = /* @__PURE__ */ new Map();
317
+ setProperty(name, value) {
318
+ this.declarations.set(name, value);
319
+ }
320
+ get isEmpty() {
321
+ return this.declarations.size === 0;
322
+ }
323
+ toCss() {
324
+ return [...this.declarations.entries()].map(([k, v]) => `${k}: ${v}`).join("; ");
325
+ }
326
+ };
327
+ var ServerText = class {
328
+ kind = "text";
329
+ parent = null;
330
+ data;
331
+ constructor(data) {
332
+ this.data = data;
333
+ }
334
+ };
335
+ var ServerComment = class {
336
+ kind = "comment";
337
+ parent = null;
338
+ data;
339
+ constructor(data) {
340
+ this.data = data;
341
+ }
342
+ };
343
+ var ServerFragment = class {
344
+ kind = "fragment";
345
+ parent = null;
346
+ children = [];
347
+ };
348
+ var ServerElement = class {
349
+ kind = "element";
350
+ parent = null;
351
+ tagName;
352
+ attributes = /* @__PURE__ */ new Map();
353
+ /** JS properties set via `setProperty` (e.g. input `value`, `checked`). */
354
+ properties = /* @__PURE__ */ new Map();
355
+ children = [];
356
+ style = new ServerStyle();
357
+ constructor(tagName) {
358
+ this.tagName = tagName.toLowerCase();
359
+ }
360
+ };
361
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
362
+ "area",
363
+ "base",
364
+ "br",
365
+ "col",
366
+ "embed",
367
+ "hr",
368
+ "img",
369
+ "input",
370
+ "link",
371
+ "meta",
372
+ "param",
373
+ "source",
374
+ "track",
375
+ "wbr"
376
+ ]);
377
+ var SERIALIZED_PROPERTIES = {
378
+ value: "attr",
379
+ checked: "boolean",
380
+ selected: "boolean"
381
+ };
382
+ function escapeHtmlText(value) {
383
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
384
+ }
385
+ function escapeHtmlAttr(value) {
386
+ return value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
387
+ }
388
+ function serializeAttributes(el) {
389
+ const parts = [];
390
+ for (const [name, value] of el.attributes) {
391
+ if (value === "") {
392
+ parts.push(` ${name}`);
393
+ } else {
394
+ parts.push(` ${name}="${escapeHtmlAttr(value)}"`);
395
+ }
396
+ }
397
+ for (const [name, kind] of Object.entries(SERIALIZED_PROPERTIES)) {
398
+ if (!el.properties.has(name)) continue;
399
+ if (el.attributes.has(name)) continue;
400
+ const raw = el.properties.get(name);
401
+ if (kind === "boolean") {
402
+ if (raw === true) parts.push(` ${name}`);
403
+ } else {
404
+ if (raw !== void 0 && raw !== null) {
405
+ parts.push(` ${name}="${escapeHtmlAttr(String(raw))}"`);
406
+ }
407
+ }
408
+ }
409
+ if (!el.style.isEmpty && !el.attributes.has("style")) {
410
+ parts.push(` style="${escapeHtmlAttr(el.style.toCss())}"`);
411
+ }
412
+ return parts.join("");
413
+ }
414
+ function serializeServerNode(node) {
415
+ switch (node.kind) {
416
+ case "text":
417
+ return escapeHtmlText(node.data);
418
+ case "comment":
419
+ return `<!--${node.data}-->`;
420
+ case "fragment":
421
+ return serializeChildren(node);
422
+ case "element": {
423
+ const el = node;
424
+ const tag = el.tagName;
425
+ const attrs = serializeAttributes(el);
426
+ if (VOID_ELEMENTS.has(tag)) {
427
+ return `<${tag}${attrs}>`;
428
+ }
429
+ return `<${tag}${attrs}>${serializeChildren(el)}</${tag}>`;
430
+ }
431
+ }
432
+ }
433
+ function serializeChildren(node) {
434
+ let out = "";
435
+ for (const child of node.children) {
436
+ out += serializeServerNode(child);
437
+ }
438
+ return out;
439
+ }
440
+
441
+ // ../dom/src/server-adapter.ts
442
+ function asServer(node) {
443
+ return node;
444
+ }
445
+ function asParent(node) {
446
+ return node;
447
+ }
448
+ var ServerDOMAdapter = class {
449
+ createElement(tag, _ns) {
450
+ return new ServerElement(tag);
451
+ }
452
+ createTextNode(data) {
453
+ return new ServerText(data);
454
+ }
455
+ createComment(data) {
456
+ return new ServerComment(data);
457
+ }
458
+ createFragment() {
459
+ return new ServerFragment();
460
+ }
461
+ appendChild(parent, child) {
462
+ const p = asParent(parent);
463
+ const c = asServer(child);
464
+ this._detach(c);
465
+ c.parent = p;
466
+ p.children.push(c);
467
+ }
468
+ insertBefore(parent, child, reference) {
469
+ const p = asParent(parent);
470
+ const c = asServer(child);
471
+ this._detach(c);
472
+ c.parent = p;
473
+ if (reference === null) {
474
+ p.children.push(c);
475
+ return;
476
+ }
477
+ const ref = asServer(reference);
478
+ const idx = p.children.indexOf(ref);
479
+ if (idx === -1) p.children.push(c);
480
+ else p.children.splice(idx, 0, c);
481
+ }
482
+ removeChild(parent, child) {
483
+ const p = asParent(parent);
484
+ const c = asServer(child);
485
+ const idx = p.children.indexOf(c);
486
+ if (idx !== -1) {
487
+ p.children.splice(idx, 1);
488
+ c.parent = null;
489
+ }
490
+ }
491
+ replaceChild(parent, newChild, oldChild) {
492
+ const p = asParent(parent);
493
+ const nc = asServer(newChild);
494
+ const oc = asServer(oldChild);
495
+ const idx = p.children.indexOf(oc);
496
+ if (idx === -1) return;
497
+ this._detach(nc);
498
+ nc.parent = p;
499
+ p.children.splice(idx, 1, nc);
500
+ oc.parent = null;
501
+ }
502
+ _detach(node) {
503
+ if (node.parent !== null) {
504
+ const siblings = node.parent.children;
505
+ const idx = siblings.indexOf(node);
506
+ if (idx !== -1) siblings.splice(idx, 1);
507
+ node.parent = null;
508
+ }
509
+ }
510
+ setAttribute(element, name, value) {
511
+ element.attributes.set(name, value);
512
+ }
513
+ removeAttribute(element, name) {
514
+ element.attributes.delete(name);
515
+ }
516
+ getAttribute(element, name) {
517
+ return element.attributes.get(name) ?? null;
518
+ }
519
+ setProperty(element, name, value) {
520
+ element.properties.set(name, value);
521
+ }
522
+ setTextContent(node, text) {
523
+ const n = asServer(node);
524
+ if (n.kind === "element" || n.kind === "fragment") {
525
+ const el = n;
526
+ el.children.length = 0;
527
+ const t = new ServerText(text);
528
+ t.parent = el;
529
+ el.children.push(t);
530
+ } else if (n.kind === "text") {
531
+ n.data = text;
532
+ }
533
+ }
534
+ getTextContent(node) {
535
+ const n = asServer(node);
536
+ if (n.kind === "text") return n.data;
537
+ if (n.kind === "element" || n.kind === "fragment") {
538
+ let out = "";
539
+ for (const c of n.children) {
540
+ out += this.getTextContent(c) ?? "";
541
+ }
542
+ return out;
543
+ }
544
+ return null;
545
+ }
546
+ // Server nodes never dispatch events — listeners are a no-op on the server.
547
+ addEventListener() {
548
+ }
549
+ removeEventListener() {
550
+ }
551
+ querySelector() {
552
+ return null;
553
+ }
554
+ querySelectorAll() {
555
+ return [];
556
+ }
557
+ getElementById() {
558
+ return null;
559
+ }
560
+ focus() {
561
+ }
562
+ isElement(node) {
563
+ return asServer(node).kind === "element";
564
+ }
565
+ isTextNode(node) {
566
+ return asServer(node).kind === "text";
567
+ }
568
+ tagName(element) {
569
+ return element.tagName;
570
+ }
571
+ parentNode(node) {
572
+ return asServer(node).parent ?? null;
573
+ }
574
+ nextSibling(node) {
575
+ const n = asServer(node);
576
+ const parent = n.parent;
577
+ if (parent === null) return null;
578
+ const idx = parent.children.indexOf(n);
579
+ if (idx === -1 || idx + 1 >= parent.children.length) return null;
580
+ return parent.children[idx + 1];
581
+ }
582
+ firstChild(node) {
583
+ const n = asServer(node);
584
+ if (n.kind === "element" || n.kind === "fragment") {
585
+ const el = n;
586
+ return el.children[0] ?? null;
587
+ }
588
+ return null;
589
+ }
590
+ childNodes(node) {
591
+ const n = asServer(node);
592
+ if (n.kind === "element" || n.kind === "fragment") {
593
+ return n.children;
594
+ }
595
+ return [];
596
+ }
597
+ // ── Server-only ────────────────────────────────────────────────────────────
598
+ /** Serialize a node's children ("inner HTML") to an HTML string. */
599
+ serializeInner(node) {
600
+ const n = asServer(node);
601
+ if (n.kind === "element" || n.kind === "fragment") {
602
+ return serializeChildren(n);
603
+ }
604
+ return "";
605
+ }
606
+ /** Serialize a node (including itself) to an HTML string. */
607
+ serializeOuter(node) {
608
+ return serializeServerNode(asServer(node));
609
+ }
610
+ };
611
+ var serverDOMAdapter = new ServerDOMAdapter();
612
+
613
+ // ../renderer/src/render-context.ts
614
+ function createRenderContext(dom, graph, container, hydrationDiagnostics) {
615
+ return {
616
+ dom,
617
+ graph,
618
+ instances: /* @__PURE__ */ new Map(),
619
+ container,
620
+ ...hydrationDiagnostics !== void 0 ? { hydrationDiagnostics } : {}
621
+ };
622
+ }
623
+
624
+ // ../renderer/src/node-instance.ts
625
+ var NodeInstance = class {
626
+ graphNode;
627
+ /** The primary DOM node for this instance (element or text node). */
628
+ domNode;
629
+ children = [];
630
+ cleanup = new CleanupRegistry();
631
+ constructor(graphNode, domNode) {
632
+ this.graphNode = graphNode;
633
+ this.domNode = domNode;
634
+ }
635
+ addChild(child) {
636
+ this.children.push(child);
637
+ }
638
+ /** Subscribe to a signal; auto-cleanup on unmount. */
639
+ trackSignal(sig, handler) {
640
+ const unsub = sig.subscribe(handler);
641
+ this.cleanup.add(unsub);
642
+ }
643
+ /** Register a raw cleanup fn (DOM event removal, etc.). */
644
+ trackCleanup(fn) {
645
+ this.cleanup.add(fn);
646
+ }
647
+ dispose() {
648
+ for (const child of this.children) {
649
+ child.dispose();
650
+ }
651
+ this.cleanup.run();
652
+ }
653
+ };
654
+
655
+ // ../renderer/src/attributes.ts
656
+ var DOM_PROPERTIES = /* @__PURE__ */ new Set([
657
+ "value",
658
+ "checked",
659
+ "selected",
660
+ "indeterminate",
661
+ "innerHTML",
662
+ "textContent",
663
+ "innerText",
664
+ "scrollTop",
665
+ "scrollLeft"
666
+ ]);
667
+ var BOOLEAN_ATTRS = /* @__PURE__ */ new Set([
668
+ "disabled",
669
+ "readonly",
670
+ "required",
671
+ "checked",
672
+ "selected",
673
+ "multiple",
674
+ "autofocus",
675
+ "autoplay",
676
+ "controls",
677
+ "default",
678
+ "defer",
679
+ "formnovalidate",
680
+ "hidden",
681
+ "ismap",
682
+ "loop",
683
+ "novalidate",
684
+ "open",
685
+ "reversed",
686
+ "scoped",
687
+ "seamless"
688
+ ]);
689
+ function applyProp(dom, element, name, value) {
690
+ if (name.startsWith("_")) return;
691
+ if (name.startsWith("on")) return;
692
+ if (DOM_PROPERTIES.has(name)) {
693
+ dom.setProperty(element, name, value);
694
+ return;
695
+ }
696
+ if (BOOLEAN_ATTRS.has(name)) {
697
+ if (value === true || value === "" || value === name) {
698
+ dom.setAttribute(element, name, "");
699
+ } else {
700
+ dom.removeAttribute(element, name);
701
+ }
702
+ return;
703
+ }
704
+ if (name === "class" || name === "className") {
705
+ dom.setAttribute(element, "class", String(value ?? ""));
706
+ return;
707
+ }
708
+ if (name === "style" && typeof value === "object" && value !== null) {
709
+ const el = element;
710
+ const styles = value;
711
+ for (const [k, v] of Object.entries(styles)) {
712
+ el.style.setProperty(k, v);
713
+ }
714
+ return;
715
+ }
716
+ if (value === null || value === void 0 || value === false) {
717
+ dom.removeAttribute(element, name);
718
+ return;
719
+ }
720
+ dom.setAttribute(element, name, String(value));
721
+ }
722
+ function patchProp(dom, element, name, oldValue, newValue) {
723
+ if (Object.is(oldValue, newValue)) return;
724
+ applyProp(dom, element, name, newValue);
725
+ }
726
+
727
+ // ../renderer/src/events.ts
728
+ function wireEvents(dom, graph, node, element, instance) {
729
+ for (const eventDesc of node.events) {
730
+ const handler = graph.getHandler(eventDesc.handlerKey);
731
+ if (handler === void 0) continue;
732
+ const domListener = (domEvent) => {
733
+ if (eventDesc.type === "input" || eventDesc.type === "change") {
734
+ const input = domEvent.target;
735
+ handler(input.value);
736
+ } else if (eventDesc.type === "submit") {
737
+ domEvent.preventDefault();
738
+ handler(domEvent);
739
+ } else {
740
+ handler();
741
+ }
742
+ };
743
+ dom.addEventListener(element, eventDesc.type, domListener);
744
+ instance.trackCleanup(() => {
745
+ dom.removeEventListener(element, eventDesc.type, domListener);
746
+ });
747
+ }
748
+ }
749
+
750
+ // ../renderer/src/tag-map.ts
751
+ var TAG_MAP = {
752
+ application: "div",
753
+ page: "div",
754
+ section: "section",
755
+ container: "div",
756
+ heading: "h1",
757
+ text: "span",
758
+ button: "button",
759
+ input: "input",
760
+ form: "form",
761
+ list: "ul",
762
+ "list-item": "li",
763
+ image: "img",
764
+ link: "a",
765
+ component: "div",
766
+ slot: "div",
767
+ fragment: "div",
768
+ "reactive-list": "ul"
769
+ };
770
+ function resolveTag(type) {
771
+ return TAG_MAP[type] ?? "div";
772
+ }
773
+
774
+ // ../renderer/src/patch.ts
775
+ function patchNode(ctx, graphNode, propKey, newValue) {
776
+ const instance = ctx.instances.get(graphNode.id);
777
+ if (instance === void 0) return;
778
+ const domNode = instance.domNode;
779
+ if (!ctx.dom.isElement(domNode)) return;
780
+ const oldValue = graphNode.getProp(propKey);
781
+ switch (propKey) {
782
+ case "text":
783
+ if (!Object.is(oldValue, newValue)) {
784
+ ctx.dom.setTextContent(domNode, String(newValue ?? ""));
785
+ graphNode.setProp("text", String(newValue ?? ""));
786
+ }
787
+ break;
788
+ case "label":
789
+ if (!Object.is(oldValue, newValue)) {
790
+ ctx.dom.setTextContent(domNode, String(newValue ?? ""));
791
+ graphNode.setProp("label", String(newValue ?? ""));
792
+ }
793
+ break;
794
+ case "disabled":
795
+ if (newValue === true) {
796
+ ctx.dom.setAttribute(domNode, "disabled", "");
797
+ } else {
798
+ ctx.dom.removeAttribute(domNode, "disabled");
799
+ }
800
+ graphNode.setProp("disabled", Boolean(newValue));
801
+ break;
802
+ case "value":
803
+ if (!Object.is(oldValue, newValue)) {
804
+ ctx.dom.setProperty(domNode, "value", String(newValue ?? ""));
805
+ graphNode.setProp("value", String(newValue ?? ""));
806
+ }
807
+ break;
808
+ default:
809
+ patchProp(ctx.dom, domNode, propKey, oldValue, newValue);
810
+ graphNode.setProp(propKey, newValue);
811
+ break;
812
+ }
813
+ }
814
+
815
+ // ../renderer/src/reconciliation.ts
816
+ function reconcileChildren(ctx, parentDom, oldInstances, newNodes, mountFn) {
817
+ const oldByKey = /* @__PURE__ */ new Map();
818
+ for (const inst of oldInstances) {
819
+ const key = inst.graphNode.key ?? inst.graphNode.id;
820
+ oldByKey.set(key, inst);
821
+ }
822
+ const newInstances = [];
823
+ const usedKeys = /* @__PURE__ */ new Set();
824
+ for (const newNode of newNodes) {
825
+ const key = newNode.key ?? newNode.id;
826
+ const existing = oldByKey.get(key);
827
+ if (existing !== void 0) {
828
+ usedKeys.add(key);
829
+ const oldSig = existing.graphNode.getProp("_sig");
830
+ const newSig = newNode.getProp("_sig");
831
+ patchExistingInstance(ctx, existing, newNode);
832
+ if (!Object.is(oldSig, newSig)) {
833
+ reconcileItemChildren(ctx, existing, newNode, mountFn);
834
+ }
835
+ newInstances.push(existing);
836
+ } else {
837
+ const inst = mountFn(newNode, parentDom);
838
+ newInstances.push(inst);
839
+ }
840
+ }
841
+ const removed = [];
842
+ for (const inst of oldInstances) {
843
+ const key = inst.graphNode.key ?? inst.graphNode.id;
844
+ if (!usedKeys.has(key)) {
845
+ removed.push(inst);
846
+ }
847
+ }
848
+ for (const inst of removed) {
849
+ const parent = ctx.dom.parentNode(inst.domNode);
850
+ if (parent !== null) {
851
+ ctx.dom.removeChild(parent, inst.domNode);
852
+ }
853
+ inst.dispose();
854
+ }
855
+ reorderDom(ctx, parentDom, newInstances);
856
+ return { instances: newInstances, removed };
857
+ }
858
+ function reconcileItemChildren(ctx, itemInstance, newItemNode, mountFn) {
859
+ const el = itemInstance.domNode;
860
+ if (!ctx.dom.isElement(el)) return;
861
+ const oldChildren = [...itemInstance.children];
862
+ const newChildNodes = [...newItemNode.children];
863
+ const nextChildren = [];
864
+ const kept = /* @__PURE__ */ new Set();
865
+ for (let i = 0; i < newChildNodes.length; i++) {
866
+ const newChild = newChildNodes[i];
867
+ const oldChild = oldChildren[i];
868
+ if (oldChild !== void 0 && oldChild.graphNode.type === newChild.type) {
869
+ patchExistingInstance(ctx, oldChild, newChild);
870
+ reconcileItemChildren(ctx, oldChild, newChild, mountFn);
871
+ nextChildren.push(oldChild);
872
+ kept.add(oldChild);
873
+ } else {
874
+ itemInstance.graphNode.appendChild(newChild);
875
+ const inst = mountFn(newChild, el);
876
+ nextChildren.push(inst);
877
+ }
878
+ }
879
+ for (const old of oldChildren) {
880
+ if (kept.has(old)) continue;
881
+ const parent = ctx.dom.parentNode(old.domNode);
882
+ if (parent !== null) ctx.dom.removeChild(parent, old.domNode);
883
+ old.dispose();
884
+ forgetInstanceTree(ctx, old);
885
+ ctx.graph.detachNode(old.graphNode);
886
+ }
887
+ reorderDom(ctx, el, nextChildren);
888
+ itemInstance.children.length = 0;
889
+ for (const c of nextChildren) itemInstance.children.push(c);
890
+ for (const c of [...itemInstance.graphNode.children]) {
891
+ itemInstance.graphNode.removeChild(c);
892
+ }
893
+ for (const c of nextChildren) itemInstance.graphNode.appendChild(c.graphNode);
894
+ }
895
+ function reorderDom(ctx, parentDom, instances) {
896
+ let referenceNode = null;
897
+ for (let i = instances.length - 1; i >= 0; i--) {
898
+ const inst = instances[i];
899
+ if (inst === void 0) continue;
900
+ const domNode = inst.domNode;
901
+ const currentNext = ctx.dom.nextSibling(domNode);
902
+ if (currentNext !== referenceNode) {
903
+ ctx.dom.insertBefore(parentDom, domNode, referenceNode);
904
+ }
905
+ referenceNode = domNode;
906
+ }
907
+ }
908
+ function forgetInstanceTree(ctx, instance) {
909
+ ctx.instances.delete(instance.graphNode.id);
910
+ for (const child of instance.children) forgetInstanceTree(ctx, child);
911
+ }
912
+ function patchExistingInstance(ctx, instance, newNode) {
913
+ const oldNode = instance.graphNode;
914
+ for (const [key, newVal] of Object.entries(newNode.props)) {
915
+ const oldVal = oldNode.getProp(key);
916
+ if (!Object.is(oldVal, newVal)) {
917
+ patchNode(ctx, instance.graphNode, key, newVal);
918
+ }
919
+ }
920
+ }
921
+
922
+ // ../renderer/src/mount.ts
923
+ var SKIP_PROP_KEYS = /* @__PURE__ */ new Set([
924
+ "text",
925
+ "label",
926
+ "level",
927
+ "inputType",
928
+ "src",
929
+ "alt",
930
+ "href",
931
+ "external",
932
+ "value",
933
+ "placeholder",
934
+ "disabled",
935
+ "_renderKey",
936
+ "key",
937
+ "name"
938
+ ]);
939
+ function mountGraph(ctx) {
940
+ return mountNode(ctx, ctx.graph.root, ctx.container);
941
+ }
942
+ function mountNode(ctx, graphNode, parentDom) {
943
+ const { dom, graph } = ctx;
944
+ if (graphNode.type === "application") {
945
+ const instance2 = new NodeInstance(graphNode, parentDom);
946
+ ctx.instances.set(graphNode.id, instance2);
947
+ for (const child of graphNode.children) {
948
+ const childInstance = mountNode(ctx, child, parentDom);
949
+ instance2.addChild(childInstance);
950
+ }
951
+ return instance2;
952
+ }
953
+ if (graphNode.type === "text") {
954
+ const text = String(graphNode.getProp("text") ?? "");
955
+ const el2 = dom.createElement("span");
956
+ const textNode = dom.createTextNode(text);
957
+ dom.appendChild(el2, textNode);
958
+ applyNodeProps(ctx, graphNode, el2);
959
+ wireEvents(dom, graph, graphNode, el2, new NodeInstance(graphNode, el2));
960
+ const instance2 = new NodeInstance(graphNode, el2);
961
+ ctx.instances.set(graphNode.id, instance2);
962
+ wireSignalBindings(ctx, graphNode, instance2, textUpdate(dom, el2, textNode));
963
+ dom.appendChild(parentDom, el2);
964
+ return instance2;
965
+ }
966
+ if (graphNode.type === "heading") {
967
+ const level = graphNode.getProp("level") ?? 1;
968
+ const tag2 = `h${level}`;
969
+ const el2 = dom.createElement(tag2);
970
+ const text = String(graphNode.getProp("text") ?? "");
971
+ dom.setTextContent(el2, text);
972
+ applyNodeProps(ctx, graphNode, el2);
973
+ const instance2 = new NodeInstance(graphNode, el2);
974
+ ctx.instances.set(graphNode.id, instance2);
975
+ wireEvents(dom, graph, graphNode, el2, instance2);
976
+ wireSignalBindings(ctx, graphNode, instance2, headingUpdate(dom, el2));
977
+ dom.appendChild(parentDom, el2);
978
+ return instance2;
979
+ }
980
+ if (graphNode.type === "input") {
981
+ const el2 = dom.createElement("input");
982
+ const inputType = String(graphNode.getProp("inputType") ?? "text");
983
+ dom.setAttribute(el2, "type", inputType);
984
+ const placeholder = graphNode.getProp("placeholder");
985
+ if (placeholder !== void 0) dom.setAttribute(el2, "placeholder", String(placeholder));
986
+ const value = graphNode.getProp("value");
987
+ if (value !== void 0) dom.setProperty(el2, "value", String(value));
988
+ applyNodeProps(ctx, graphNode, el2);
989
+ const instance2 = new NodeInstance(graphNode, el2);
990
+ ctx.instances.set(graphNode.id, instance2);
991
+ wireEvents(dom, graph, graphNode, el2, instance2);
992
+ wireSignalBindings(ctx, graphNode, instance2, inputUpdate(dom, el2));
993
+ dom.appendChild(parentDom, el2);
994
+ return instance2;
995
+ }
996
+ if (graphNode.type === "image") {
997
+ const el2 = dom.createElement("img");
998
+ const src = graphNode.getProp("src");
999
+ const alt = graphNode.getProp("alt");
1000
+ if (src !== void 0) dom.setAttribute(el2, "src", String(src));
1001
+ if (alt !== void 0) dom.setAttribute(el2, "alt", String(alt));
1002
+ const width = graphNode.getProp("width");
1003
+ const height = graphNode.getProp("height");
1004
+ if (width !== void 0) dom.setAttribute(el2, "width", String(width));
1005
+ if (height !== void 0) dom.setAttribute(el2, "height", String(height));
1006
+ applyNodeProps(ctx, graphNode, el2);
1007
+ const instance2 = new NodeInstance(graphNode, el2);
1008
+ ctx.instances.set(graphNode.id, instance2);
1009
+ dom.appendChild(parentDom, el2);
1010
+ return instance2;
1011
+ }
1012
+ if (graphNode.type === "link") {
1013
+ const el2 = dom.createElement("a");
1014
+ const href = graphNode.getProp("href");
1015
+ const label = graphNode.getProp("label");
1016
+ const external = graphNode.getProp("external");
1017
+ if (href !== void 0) dom.setAttribute(el2, "href", String(href));
1018
+ if (label !== void 0) dom.setTextContent(el2, String(label));
1019
+ if (external === true) {
1020
+ dom.setAttribute(el2, "target", "_blank");
1021
+ dom.setAttribute(el2, "rel", "noopener noreferrer");
1022
+ }
1023
+ applyNodeProps(ctx, graphNode, el2);
1024
+ const instance2 = new NodeInstance(graphNode, el2);
1025
+ ctx.instances.set(graphNode.id, instance2);
1026
+ wireEvents(dom, graph, graphNode, el2, instance2);
1027
+ dom.appendChild(parentDom, el2);
1028
+ return instance2;
1029
+ }
1030
+ if (graphNode.type === "button") {
1031
+ const el2 = dom.createElement("button");
1032
+ const label = graphNode.getProp("label");
1033
+ if (label !== void 0) dom.setTextContent(el2, String(label));
1034
+ const disabled = graphNode.getProp("disabled");
1035
+ if (disabled === true) dom.setAttribute(el2, "disabled", "");
1036
+ applyNodeProps(ctx, graphNode, el2);
1037
+ const instance2 = new NodeInstance(graphNode, el2);
1038
+ ctx.instances.set(graphNode.id, instance2);
1039
+ wireEvents(dom, graph, graphNode, el2, instance2);
1040
+ wireSignalBindings(ctx, graphNode, instance2, buttonUpdate(dom, el2));
1041
+ dom.appendChild(parentDom, el2);
1042
+ return instance2;
1043
+ }
1044
+ if (graphNode.type === "reactive-list" || graphNode.type === "conditional") {
1045
+ const tag2 = resolveTag(graphNode.type);
1046
+ const el2 = dom.createElement(tag2);
1047
+ applyNodeProps(ctx, graphNode, el2);
1048
+ const instance2 = new NodeInstance(graphNode, el2);
1049
+ ctx.instances.set(graphNode.id, instance2);
1050
+ for (const child of graphNode.children) {
1051
+ const childInstance = mountNode(ctx, child, el2);
1052
+ instance2.addChild(childInstance);
1053
+ }
1054
+ dom.appendChild(parentDom, el2);
1055
+ wireReactiveList(ctx, graphNode, instance2, el2);
1056
+ return instance2;
1057
+ }
1058
+ const tag = resolveTag(graphNode.type);
1059
+ const el = dom.createElement(tag);
1060
+ applyNodeProps(ctx, graphNode, el);
1061
+ if (graphNode.type === "list-item") {
1062
+ const itemKey = graphNode.getProp("key");
1063
+ if (itemKey !== void 0) {
1064
+ dom.setAttribute(el, "data-streetui-key", String(itemKey));
1065
+ }
1066
+ }
1067
+ if (graphNode.type === "form") {
1068
+ wireEvents(dom, graph, graphNode, el, new NodeInstance(graphNode, el));
1069
+ }
1070
+ const instance = new NodeInstance(graphNode, el);
1071
+ ctx.instances.set(graphNode.id, instance);
1072
+ for (const child of graphNode.children) {
1073
+ const childInstance = mountNode(ctx, child, el);
1074
+ instance.addChild(childInstance);
1075
+ }
1076
+ dom.appendChild(parentDom, el);
1077
+ return instance;
1078
+ }
1079
+ function textUpdate(dom, el, textNode) {
1080
+ return (propKey, value) => {
1081
+ if (propKey === "text") {
1082
+ dom.setTextContent(textNode, String(value ?? ""));
1083
+ } else {
1084
+ applyProp(dom, el, propKey, value);
1085
+ }
1086
+ };
1087
+ }
1088
+ function headingUpdate(dom, el) {
1089
+ return (propKey, value) => {
1090
+ if (propKey === "text") {
1091
+ dom.setTextContent(el, String(value ?? ""));
1092
+ } else {
1093
+ applyProp(dom, el, propKey, value);
1094
+ }
1095
+ };
1096
+ }
1097
+ function inputUpdate(dom, el) {
1098
+ return (propKey, value) => {
1099
+ if (propKey === "value") {
1100
+ dom.setProperty(el, "value", String(value ?? ""));
1101
+ } else {
1102
+ applyProp(dom, el, propKey, value);
1103
+ }
1104
+ };
1105
+ }
1106
+ function buttonUpdate(dom, el) {
1107
+ return (propKey, value) => {
1108
+ if (propKey === "label") {
1109
+ dom.setTextContent(el, String(value ?? ""));
1110
+ } else if (propKey === "disabled") {
1111
+ if (value === true) {
1112
+ dom.setAttribute(el, "disabled", "");
1113
+ } else {
1114
+ dom.removeAttribute(el, "disabled");
1115
+ }
1116
+ } else {
1117
+ applyProp(dom, el, propKey, value);
1118
+ }
1119
+ };
1120
+ }
1121
+ function applyNodeProps(ctx, graphNode, el) {
1122
+ for (const [key, value] of Object.entries(graphNode.props)) {
1123
+ if (SKIP_PROP_KEYS.has(key)) continue;
1124
+ applyProp(ctx.dom, el, key, value);
1125
+ }
1126
+ }
1127
+ function wireSignalBindings(ctx, graphNode, instance, onUpdate) {
1128
+ for (const stateRef of graphNode.stateRefs) {
1129
+ const signalKey = `__signal__${stateRef.signalId}`;
1130
+ const maybeSig = ctx.graph.getHandler(signalKey);
1131
+ if (maybeSig === void 0 || typeof maybeSig.subscribe !== "function") continue;
1132
+ const unsub = maybeSig.subscribe((value) => {
1133
+ onUpdate(stateRef.propKey, value);
1134
+ });
1135
+ instance.trackCleanup(unsub);
1136
+ }
1137
+ }
1138
+ function wireReactiveList(ctx, graphNode, instance, el) {
1139
+ const build = ctx.graph.getHandler(`__listbuild__${graphNode.id}`);
1140
+ if (build === void 0) return;
1141
+ for (const stateRef of graphNode.stateRefs) {
1142
+ if (stateRef.propKey !== "items") continue;
1143
+ const sig = ctx.graph.getHandler(`__signal__${stateRef.signalId}`);
1144
+ if (sig === void 0 || typeof sig.subscribe !== "function") continue;
1145
+ const unsub = sig.subscribe((value) => {
1146
+ reconcileReactiveList(ctx, graphNode, instance, el, build(value));
1147
+ });
1148
+ instance.trackCleanup(unsub);
1149
+ }
1150
+ }
1151
+ function reconcileReactiveList(ctx, listNode, listInstance, listEl, newNodes) {
1152
+ const oldInstances = [...listInstance.children];
1153
+ const result = reconcileChildren(
1154
+ ctx,
1155
+ listEl,
1156
+ oldInstances,
1157
+ newNodes,
1158
+ (node, parent) => mountNode(ctx, node, parent)
1159
+ );
1160
+ listInstance.children.length = 0;
1161
+ for (const inst of result.instances) listInstance.children.push(inst);
1162
+ for (const removed of result.removed) {
1163
+ forgetInstance(ctx, removed);
1164
+ ctx.graph.detachNode(removed.graphNode);
1165
+ }
1166
+ const adopted = new Set(result.instances.map((i) => i.graphNode));
1167
+ for (const built of newNodes) {
1168
+ if (!adopted.has(built)) ctx.graph.detachNode(built);
1169
+ }
1170
+ for (const child of [...listNode.children]) listNode.removeChild(child);
1171
+ for (const inst of result.instances) listNode.appendChild(inst.graphNode);
1172
+ }
1173
+ function forgetInstance(ctx, instance) {
1174
+ ctx.instances.delete(instance.graphNode.id);
1175
+ for (const child of instance.children) forgetInstance(ctx, child);
1176
+ }
1177
+
1178
+ // ../renderer/src/hydration-diagnostics.ts
1179
+ function formatHydrationDiagnostic(d) {
1180
+ const at = ` at ${d.path}`;
1181
+ switch (d.type) {
1182
+ case "tag-mismatch":
1183
+ return `Hydration mismatch${at} \u2014 Expected: ${d.expected} / Found: ${d.found} / Action: ${d.action}`;
1184
+ case "missing-element":
1185
+ return `Hydration mismatch${at} \u2014 Expected: ${d.expected} / Found: (nothing) / Action: ${d.action}`;
1186
+ case "surplus-element":
1187
+ return `Hydration mismatch${at} \u2014 Expected: (nothing) / Found: ${d.found} / Action: ${d.action}`;
1188
+ }
1189
+ }
1190
+ function createHydrationDiagnosticCollector() {
1191
+ const diagnostics = [];
1192
+ return {
1193
+ diagnostics,
1194
+ sink: {
1195
+ report(d) {
1196
+ diagnostics.push(d);
1197
+ }
1198
+ }
1199
+ };
1200
+ }
1201
+
1202
+ // ../renderer/src/hydrate.ts
1203
+ function hydrateGraph(ctx) {
1204
+ const root = ctx.graph.root;
1205
+ const instance = new NodeInstance(root, ctx.container);
1206
+ ctx.instances.set(root.id, instance);
1207
+ hydrateChildren(ctx, root, instance, ctx.container, "app");
1208
+ return instance;
1209
+ }
1210
+ function hydrateNode(ctx, graphNode, domNode, path) {
1211
+ const { dom, graph } = ctx;
1212
+ switch (graphNode.type) {
1213
+ case "text": {
1214
+ let textNode = dom.firstChild(domNode);
1215
+ if (textNode === null || !dom.isTextNode(textNode)) {
1216
+ const created = dom.createTextNode(String(graphNode.getProp("text") ?? ""));
1217
+ dom.appendChild(domNode, created);
1218
+ textNode = created;
1219
+ }
1220
+ const instance = new NodeInstance(graphNode, domNode);
1221
+ ctx.instances.set(graphNode.id, instance);
1222
+ wireEvents(dom, graph, graphNode, domNode, instance);
1223
+ wireSignalBindings(ctx, graphNode, instance, textUpdate(dom, domNode, textNode));
1224
+ return instance;
1225
+ }
1226
+ case "heading": {
1227
+ const instance = new NodeInstance(graphNode, domNode);
1228
+ ctx.instances.set(graphNode.id, instance);
1229
+ wireEvents(dom, graph, graphNode, domNode, instance);
1230
+ wireSignalBindings(ctx, graphNode, instance, headingUpdate(dom, domNode));
1231
+ return instance;
1232
+ }
1233
+ case "input": {
1234
+ const instance = new NodeInstance(graphNode, domNode);
1235
+ ctx.instances.set(graphNode.id, instance);
1236
+ const value = graphNode.getProp("value");
1237
+ if (value !== void 0) dom.setProperty(domNode, "value", String(value));
1238
+ wireEvents(dom, graph, graphNode, domNode, instance);
1239
+ wireSignalBindings(ctx, graphNode, instance, inputUpdate(dom, domNode));
1240
+ return instance;
1241
+ }
1242
+ case "button": {
1243
+ const instance = new NodeInstance(graphNode, domNode);
1244
+ ctx.instances.set(graphNode.id, instance);
1245
+ wireEvents(dom, graph, graphNode, domNode, instance);
1246
+ wireSignalBindings(ctx, graphNode, instance, buttonUpdate(dom, domNode));
1247
+ return instance;
1248
+ }
1249
+ case "image":
1250
+ case "link": {
1251
+ const instance = new NodeInstance(graphNode, domNode);
1252
+ ctx.instances.set(graphNode.id, instance);
1253
+ if (graphNode.type === "link") wireEvents(dom, graph, graphNode, domNode, instance);
1254
+ return instance;
1255
+ }
1256
+ case "reactive-list":
1257
+ case "conditional": {
1258
+ const instance = new NodeInstance(graphNode, domNode);
1259
+ ctx.instances.set(graphNode.id, instance);
1260
+ hydrateChildren(ctx, graphNode, instance, domNode, path);
1261
+ wireReactiveList(ctx, graphNode, instance, domNode);
1262
+ return instance;
1263
+ }
1264
+ default: {
1265
+ const instance = new NodeInstance(graphNode, domNode);
1266
+ ctx.instances.set(graphNode.id, instance);
1267
+ if (graphNode.type === "form") {
1268
+ wireEvents(dom, graph, graphNode, domNode, instance);
1269
+ }
1270
+ if (graphNode.getProp("_hydrationBoundary") === true) {
1271
+ return instance;
1272
+ }
1273
+ hydrateChildren(ctx, graphNode, instance, domNode, path);
1274
+ return instance;
1275
+ }
1276
+ }
1277
+ }
1278
+ function hydrateChildren(ctx, parentGraphNode, parentInstance, parentDom, parentPath) {
1279
+ const expected = parentGraphNode.children;
1280
+ const actual = elementChildren(ctx, parentDom);
1281
+ let cursor = 0;
1282
+ for (let i = 0; i < expected.length; i++) {
1283
+ const childNode = expected[i];
1284
+ const want = expectedTag(ctx, childNode);
1285
+ const childPath = `${parentPath} / ${childNode.type}[${i}]`;
1286
+ const actualEl = actual[cursor];
1287
+ if (actualEl !== void 0 && ctx.dom.isElement(actualEl) && ctx.dom.tagName(actualEl) === want) {
1288
+ const inst = hydrateNode(ctx, childNode, actualEl, childPath);
1289
+ parentInstance.addChild(inst);
1290
+ cursor++;
1291
+ } else {
1292
+ const ref = actualEl ?? null;
1293
+ const inst = mountFreshAt(ctx, childNode, parentDom, ref);
1294
+ parentInstance.addChild(inst);
1295
+ if (actualEl !== void 0) {
1296
+ const found = ctx.dom.isElement(actualEl) ? ctx.dom.tagName(actualEl) : null;
1297
+ reportHydrationDiagnostic(ctx, {
1298
+ type: "tag-mismatch",
1299
+ expected: want,
1300
+ found,
1301
+ path: childPath,
1302
+ nodeId: childNode.id,
1303
+ nodeType: childNode.type,
1304
+ action: "mounted fresh subtree in place"
1305
+ });
1306
+ ctx.dom.removeChild(parentDom, actualEl);
1307
+ cursor++;
1308
+ } else {
1309
+ reportHydrationDiagnostic(ctx, {
1310
+ type: "missing-element",
1311
+ expected: want,
1312
+ found: null,
1313
+ path: childPath,
1314
+ nodeId: childNode.id,
1315
+ nodeType: childNode.type,
1316
+ action: "mounted fresh subtree"
1317
+ });
1318
+ }
1319
+ }
1320
+ }
1321
+ for (let i = cursor; i < actual.length; i++) {
1322
+ const surplus = actual[i];
1323
+ reportHydrationDiagnostic(ctx, {
1324
+ type: "surplus-element",
1325
+ expected: null,
1326
+ found: ctx.dom.isElement(surplus) ? ctx.dom.tagName(surplus) : null,
1327
+ path: `${parentPath} / [surplus ${i}]`,
1328
+ nodeId: null,
1329
+ nodeType: null,
1330
+ action: "removed surplus server element"
1331
+ });
1332
+ ctx.dom.removeChild(parentDom, surplus);
1333
+ }
1334
+ }
1335
+ function reportHydrationDiagnostic(ctx, d) {
1336
+ const sink = ctx.hydrationDiagnostics;
1337
+ if (sink === void 0) return;
1338
+ sink.report({ ...d, message: formatHydrationDiagnostic(d) });
1339
+ }
1340
+ function mountFreshAt(ctx, node, parentDom, ref) {
1341
+ const inst = mountNode(ctx, node, parentDom);
1342
+ if (ref !== null) {
1343
+ ctx.dom.insertBefore(parentDom, inst.domNode, ref);
1344
+ }
1345
+ return inst;
1346
+ }
1347
+ function elementChildren(ctx, parent) {
1348
+ const out = [];
1349
+ for (const node of ctx.dom.childNodes(parent)) {
1350
+ if (ctx.dom.isElement(node)) out.push(node);
1351
+ }
1352
+ return out;
1353
+ }
1354
+ function expectedTag(ctx, graphNode) {
1355
+ switch (graphNode.type) {
1356
+ case "text":
1357
+ return "span";
1358
+ case "heading": {
1359
+ const level = graphNode.getProp("level") ?? 1;
1360
+ return `h${level}`;
1361
+ }
1362
+ case "input":
1363
+ return "input";
1364
+ case "image":
1365
+ return "img";
1366
+ case "link":
1367
+ return "a";
1368
+ case "button":
1369
+ return "button";
1370
+ default:
1371
+ return resolveTag(graphNode.type);
1372
+ }
1373
+ }
1374
+
1375
+ // ../renderer/src/render-handle.ts
1376
+ var StreetRenderHandle = class {
1377
+ _disposed = false;
1378
+ _ctx;
1379
+ _rootInstance;
1380
+ constructor(ctx, rootInstance) {
1381
+ this._ctx = ctx;
1382
+ this._rootInstance = rootInstance;
1383
+ }
1384
+ flush() {
1385
+ if (this._disposed) return;
1386
+ }
1387
+ unmount() {
1388
+ if (this._disposed) return;
1389
+ this._disposed = true;
1390
+ this._rootInstance.dispose();
1391
+ const dom = this._ctx.dom;
1392
+ const container = this._ctx.container;
1393
+ for (const child of dom.childNodes(container)) {
1394
+ dom.removeChild(container, child);
1395
+ }
1396
+ this._ctx.instances.clear();
1397
+ }
1398
+ };
1399
+
1400
+ // ../renderer/src/renderer.ts
1401
+ var StreetRendererImpl = class {
1402
+ _dom;
1403
+ _hydrationDiagnostics;
1404
+ constructor(options = {}) {
1405
+ this._dom = options.domAdapter ?? new BrowserDOMAdapter();
1406
+ if (options.hydrationDiagnostics !== void 0) {
1407
+ this._hydrationDiagnostics = options.hydrationDiagnostics;
1408
+ }
1409
+ }
1410
+ mount(compiled, container) {
1411
+ const ctx = createRenderContext(this._dom, compiled.graph, container);
1412
+ const rootInstance = mountGraph(ctx);
1413
+ this._wireSignals(ctx, rootInstance);
1414
+ return new StreetRenderHandle(ctx, rootInstance);
1415
+ }
1416
+ /**
1417
+ * Hydrate a container that already holds server-rendered HTML for this
1418
+ * application. Instead of recreating the DOM, it walks the semantic graph
1419
+ * against the existing nodes, adopting matching elements and attaching
1420
+ * behavior (events + signal subscriptions). Mismatched subtrees are locally
1421
+ * replaced. Returns the same handle type as `mount`.
1422
+ */
1423
+ hydrate(compiled, container) {
1424
+ const ctx = createRenderContext(
1425
+ this._dom,
1426
+ compiled.graph,
1427
+ container,
1428
+ this._hydrationDiagnostics
1429
+ );
1430
+ const rootInstance = hydrateGraph(ctx);
1431
+ this._wireSignals(ctx, rootInstance);
1432
+ return new StreetRenderHandle(ctx, rootInstance);
1433
+ }
1434
+ _wireSignals(ctx, rootInstance) {
1435
+ }
1436
+ };
1437
+ function createRenderer(options) {
1438
+ return new StreetRendererImpl(options);
1439
+ }
1440
+
1441
+ // ../renderer/src/ssr.ts
1442
+ function renderToString(compiled, options = {}) {
1443
+ const dom = options.domAdapter ?? new ServerDOMAdapter();
1444
+ const container = dom.createElement("div");
1445
+ const ctx = createRenderContext(dom, compiled.graph, container);
1446
+ const rootInstance = mountGraph(ctx);
1447
+ const html = dom.serializeInner(container);
1448
+ rootInstance.dispose();
1449
+ ctx.instances.clear();
1450
+ return html;
1451
+ }
1452
+
1453
+ // ../testing/src/test-renderer.ts
1454
+ function render(app) {
1455
+ const compiled = compile(app);
1456
+ const container = document.createElement("div");
1457
+ document.body.appendChild(container);
1458
+ const renderer = createRenderer({ domAdapter: new BrowserDOMAdapter() });
1459
+ const handle = renderer.mount(compiled, container);
1460
+ return {
1461
+ container,
1462
+ handle,
1463
+ unmount() {
1464
+ handle.unmount();
1465
+ if (container.parentNode !== null) {
1466
+ container.parentNode.removeChild(container);
1467
+ }
1468
+ },
1469
+ flush() {
1470
+ handle.flush();
1471
+ },
1472
+ getByTag(tag) {
1473
+ const el = container.querySelector(tag);
1474
+ if (el === null) {
1475
+ throw new Error(`[StreetUI Testing] Element <${tag}> not found in render output`);
1476
+ }
1477
+ return el;
1478
+ },
1479
+ getAllByTag(tag) {
1480
+ return Array.from(container.querySelectorAll(tag));
1481
+ },
1482
+ getByText(text) {
1483
+ const all = Array.from(container.querySelectorAll("*"));
1484
+ const match = all.find(
1485
+ (el) => el.children.length === 0 && el.textContent?.includes(text)
1486
+ );
1487
+ if (match === void 0) {
1488
+ throw new Error(`[StreetUI Testing] No element with text "${text}" found`);
1489
+ }
1490
+ return match;
1491
+ },
1492
+ getAllByText(text) {
1493
+ return Array.from(container.querySelectorAll("*")).filter(
1494
+ (el) => el.textContent?.includes(text)
1495
+ );
1496
+ },
1497
+ query(selector) {
1498
+ return container.querySelector(selector);
1499
+ },
1500
+ queryAll(selector) {
1501
+ return Array.from(container.querySelectorAll(selector));
1502
+ },
1503
+ find(selector) {
1504
+ const el = container.querySelector(selector);
1505
+ if (el === null) {
1506
+ throw new Error(`[StreetUI Testing] Selector "${selector}" matched nothing`);
1507
+ }
1508
+ return el;
1509
+ }
1510
+ };
1511
+ }
1512
+ function renderOnce(app, testFn) {
1513
+ const result = render(app);
1514
+ return Promise.resolve(testFn(result)).finally(() => {
1515
+ result.unmount();
1516
+ resetIdCounter();
1517
+ });
1518
+ }
1519
+
1520
+ // ../scheduler/src/scheduler.ts
1521
+ var PRIORITY_ORDER = {
1522
+ immediate: 0,
1523
+ normal: 1,
1524
+ idle: 2
1525
+ };
1526
+ var Scheduler = class {
1527
+ _queue = /* @__PURE__ */ new Map();
1528
+ _flushScheduled = false;
1529
+ _flushing = false;
1530
+ _diagnostics = void 0;
1531
+ /**
1532
+ * Install an optional diagnostic sink for swallowed job errors. Pass
1533
+ * `undefined` to restore the default `console.error` reporting. Additive and
1534
+ * opt-in — the scheduler never sends anything anywhere on its own.
1535
+ */
1536
+ setDiagnostics(sink) {
1537
+ this._diagnostics = sink;
1538
+ }
1539
+ /** Total jobs currently queued. */
1540
+ get size() {
1541
+ return this._queue.size;
1542
+ }
1543
+ /** True if a flush has been scheduled but not yet executed. */
1544
+ get isPending() {
1545
+ return this._flushScheduled;
1546
+ }
1547
+ /**
1548
+ * Enqueue a job. If a job with the same key exists, the new one replaces it
1549
+ * (allowing callers to coalesce repeated updates for the same node).
1550
+ */
1551
+ schedule(job) {
1552
+ this._queue.set(job.key, job);
1553
+ if (!this._flushScheduled && !this._flushing) {
1554
+ this._flushScheduled = true;
1555
+ this._scheduleMicrotask();
1556
+ }
1557
+ }
1558
+ /** Schedule multiple jobs atomically. */
1559
+ scheduleAll(jobs) {
1560
+ for (const job of jobs) {
1561
+ this._queue.set(job.key, job);
1562
+ }
1563
+ if (!this._flushScheduled && !this._flushing && this._queue.size > 0) {
1564
+ this._flushScheduled = true;
1565
+ this._scheduleMicrotask();
1566
+ }
1567
+ }
1568
+ /**
1569
+ * Cancel a queued job by key. No-op if not queued.
1570
+ */
1571
+ cancel(key) {
1572
+ this._queue.delete(key);
1573
+ }
1574
+ /**
1575
+ * Synchronously flush all queued jobs (sorted by priority).
1576
+ * Useful in tests and for immediate rendering.
1577
+ */
1578
+ flush() {
1579
+ if (this._flushing) return;
1580
+ this._flushScheduled = false;
1581
+ this._flushing = true;
1582
+ const jobs = Array.from(this._queue.values()).sort(
1583
+ (a, b) => PRIORITY_ORDER[a.priority] - PRIORITY_ORDER[b.priority]
1584
+ );
1585
+ this._queue.clear();
1586
+ try {
1587
+ for (const job of jobs) {
1588
+ try {
1589
+ job.fn();
1590
+ } catch (err) {
1591
+ if (this._diagnostics?.error) {
1592
+ this._diagnostics.error(`Scheduler job "${job.key}" threw`, err);
1593
+ } else {
1594
+ console.error(`[Scheduler] Job "${job.key}" threw:`, err);
1595
+ }
1596
+ }
1597
+ }
1598
+ } finally {
1599
+ this._flushing = false;
1600
+ }
1601
+ }
1602
+ /** Clear all pending jobs without executing them. */
1603
+ clear() {
1604
+ this._queue.clear();
1605
+ this._flushScheduled = false;
1606
+ }
1607
+ _scheduleMicrotask() {
1608
+ Promise.resolve().then(() => {
1609
+ if (this._flushScheduled) {
1610
+ this.flush();
1611
+ }
1612
+ });
1613
+ }
1614
+ };
1615
+ var scheduler = new Scheduler();
1616
+ function flushSync() {
1617
+ scheduler.flush();
1618
+ }
1619
+
1620
+ // ../testing/src/helpers.ts
1621
+ async function flushUpdates() {
1622
+ flushSync();
1623
+ await Promise.resolve();
1624
+ flushSync();
1625
+ }
1626
+ async function waitFor(check, options = {}) {
1627
+ const timeout = options.timeout ?? 1e3;
1628
+ const interval = options.interval ?? 10;
1629
+ const deadline = Date.now() + timeout;
1630
+ let lastError;
1631
+ for (; ; ) {
1632
+ await flushUpdates();
1633
+ try {
1634
+ const result = check();
1635
+ if (result) return result;
1636
+ lastError = new Error("[StreetUI Testing] waitFor: condition was falsy");
1637
+ } catch (err) {
1638
+ lastError = err;
1639
+ }
1640
+ if (Date.now() >= deadline) {
1641
+ throw lastError instanceof Error ? lastError : new Error(String(lastError));
1642
+ }
1643
+ await new Promise((resolve) => setTimeout(resolve, interval));
1644
+ }
1645
+ }
1646
+ function findByText(container, text) {
1647
+ const match = Array.from(container.querySelectorAll("*")).find(
1648
+ (el) => el.children.length === 0 && (el.textContent?.includes(text) ?? false)
1649
+ );
1650
+ if (match === void 0) {
1651
+ throw new Error(`[StreetUI Testing] No element with text "${text}" found`);
1652
+ }
1653
+ return match;
1654
+ }
1655
+ function implicitRole(el) {
1656
+ const tag = el.tagName.toLowerCase();
1657
+ switch (tag) {
1658
+ case "button":
1659
+ return "button";
1660
+ case "a":
1661
+ return el.hasAttribute("href") ? "link" : null;
1662
+ case "nav":
1663
+ return "navigation";
1664
+ case "h1":
1665
+ case "h2":
1666
+ case "h3":
1667
+ case "h4":
1668
+ case "h5":
1669
+ case "h6":
1670
+ return "heading";
1671
+ case "input": {
1672
+ const type = (el.getAttribute("type") ?? "text").toLowerCase();
1673
+ if (type === "checkbox") return "checkbox";
1674
+ if (type === "radio") return "radio";
1675
+ if (type === "button" || type === "submit") return "button";
1676
+ return "textbox";
1677
+ }
1678
+ case "form":
1679
+ return "form";
1680
+ default:
1681
+ return null;
1682
+ }
1683
+ }
1684
+ function accessibleName(el) {
1685
+ return (el.getAttribute("aria-label") ?? el.textContent ?? "").trim();
1686
+ }
1687
+ function findAllByRole(container, role, options = {}) {
1688
+ const all = Array.from(container.querySelectorAll("*"));
1689
+ return all.filter((el) => {
1690
+ const explicit = el.getAttribute("role");
1691
+ const matches = explicit === role || explicit === null && implicitRole(el) === role;
1692
+ if (!matches) return false;
1693
+ if (options.name !== void 0) {
1694
+ return accessibleName(el).includes(options.name);
1695
+ }
1696
+ return true;
1697
+ });
1698
+ }
1699
+ function findByRole(container, role, options = {}) {
1700
+ const matches = findAllByRole(container, role, options);
1701
+ if (matches.length === 0) {
1702
+ const named = options.name !== void 0 ? ` with name "${options.name}"` : "";
1703
+ throw new Error(`[StreetUI Testing] No element with role "${role}"${named} found`);
1704
+ }
1705
+ if (matches.length > 1) {
1706
+ throw new Error(
1707
+ `[StreetUI Testing] Found ${matches.length} elements with role "${role}" \u2014 refine with { name }`
1708
+ );
1709
+ }
1710
+ return matches[0];
1711
+ }
1712
+ function renderServerThenHydrate(build, options = {}) {
1713
+ resetIdCounter();
1714
+ const serverHtml = renderToString(compile(build()));
1715
+ const container = document.createElement("div");
1716
+ container.innerHTML = serverHtml;
1717
+ document.body.appendChild(container);
1718
+ const collector = options.collectDiagnostics === true ? createHydrationDiagnosticCollector() : void 0;
1719
+ resetIdCounter();
1720
+ const renderer = createRenderer({
1721
+ domAdapter: new BrowserDOMAdapter(),
1722
+ ...collector !== void 0 ? { hydrationDiagnostics: collector.sink } : {}
1723
+ });
1724
+ const handle = renderer.hydrate(compile(build()), container);
1725
+ return {
1726
+ container,
1727
+ serverHtml,
1728
+ handle,
1729
+ diagnostics: collector?.diagnostics ?? [],
1730
+ flush() {
1731
+ handle.flush();
1732
+ },
1733
+ unmount() {
1734
+ handle.unmount();
1735
+ if (container.parentNode !== null) container.parentNode.removeChild(container);
1736
+ }
1737
+ };
1738
+ }
1739
+
1740
+ // src/version.ts
1741
+ var VERSION = "1.0.0";
1742
+ // Annotate the CommonJS export names for ESM import in node:
1743
+ 0 && (module.exports = {
1744
+ VERSION,
1745
+ findAllByRole,
1746
+ findByRole,
1747
+ findByText,
1748
+ flushUpdates,
1749
+ render,
1750
+ renderOnce,
1751
+ renderServerThenHydrate,
1752
+ waitFor
1753
+ });
1754
+ //# sourceMappingURL=testing.cjs.map