streetui 1.7.0 → 1.8.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.
- package/dist/bin.cjs +1 -1
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/bin.js.map +1 -1
- package/dist/{compile-Cte0nf06.d.cts → compile-CJJQYi71.d.cts} +137 -2
- package/dist/{compile-Cte0nf06.d.ts → compile-CJJQYi71.d.ts} +137 -2
- package/dist/create-bin.cjs +1 -1
- package/dist/create-bin.cjs.map +1 -1
- package/dist/create-bin.js +1 -1
- package/dist/create-bin.js.map +1 -1
- package/dist/{hydration-diagnostics-zK-fihvd.d.cts → hydration-diagnostics-BQeehq3i.d.cts} +1 -1
- package/dist/{hydration-diagnostics-Ddgs3Puu.d.ts → hydration-diagnostics-DNAJumYF.d.ts} +1 -1
- package/dist/index.cjs +103 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +40 -7
- package/dist/index.d.ts +40 -7
- package/dist/index.js +99 -2
- package/dist/index.js.map +1 -1
- package/dist/{server-D4A8eunr.d.cts → server-CnLEnlYU.d.cts} +1 -1
- package/dist/{server-cgmCiPsz.d.ts → server-DNlg7ou2.d.ts} +1 -1
- package/dist/server.cjs +9 -2
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +2 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +9 -2
- package/dist/server.js.map +1 -1
- package/dist/testing.cjs +1088 -2
- package/dist/testing.cjs.map +1 -1
- package/dist/testing.d.cts +63 -4
- package/dist/testing.d.ts +63 -4
- package/dist/testing.js +1082 -2
- package/dist/testing.js.map +1 -1
- package/package.json +1 -1
package/dist/testing.cjs
CHANGED
|
@@ -23,23 +23,38 @@ __export(testing_exports, {
|
|
|
23
23
|
VERSION: () => VERSION,
|
|
24
24
|
analyzeGraph: () => analyzeGraph,
|
|
25
25
|
findAllByRole: () => findAllByRole,
|
|
26
|
+
findAllComponents: () => findAllComponents,
|
|
26
27
|
findByRole: () => findByRole,
|
|
27
28
|
findByText: () => findByText,
|
|
29
|
+
findComponent: () => findComponent,
|
|
28
30
|
flushUpdates: () => flushUpdates,
|
|
29
31
|
formatInspection: () => formatInspection,
|
|
32
|
+
getComponentName: () => getComponentName,
|
|
33
|
+
hydrateComponent: () => hydrateComponent,
|
|
30
34
|
inspectCompilation: () => inspectCompilation,
|
|
31
35
|
render: () => render,
|
|
36
|
+
renderComponent: () => renderComponent,
|
|
32
37
|
renderOnce: () => renderOnce,
|
|
33
38
|
renderServerThenHydrate: () => renderServerThenHydrate,
|
|
39
|
+
trigger: () => trigger,
|
|
34
40
|
waitFor: () => waitFor
|
|
35
41
|
});
|
|
36
42
|
module.exports = __toCommonJS(testing_exports);
|
|
37
43
|
|
|
38
44
|
// ../core/src/identity.ts
|
|
39
45
|
var _counter = 0;
|
|
46
|
+
function nextId() {
|
|
47
|
+
return ++_counter;
|
|
48
|
+
}
|
|
40
49
|
function resetIdCounter() {
|
|
41
50
|
_counter = 0;
|
|
42
51
|
}
|
|
52
|
+
function createNodeId(value) {
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
function generateNodeId(prefix = "node") {
|
|
56
|
+
return createNodeId(`${prefix}:${nextId()}`);
|
|
57
|
+
}
|
|
43
58
|
|
|
44
59
|
// ../core/src/lifecycle.ts
|
|
45
60
|
var CleanupRegistry = class {
|
|
@@ -1420,6 +1435,7 @@ function mountNode(ctx, graphNode, parentDom) {
|
|
|
1420
1435
|
instance.addChild(childInstance);
|
|
1421
1436
|
}
|
|
1422
1437
|
dom.appendChild(parentDom, el);
|
|
1438
|
+
if (graphNode.type === "component") wireComponentBehavior(ctx, graphNode, instance);
|
|
1423
1439
|
return instance;
|
|
1424
1440
|
}
|
|
1425
1441
|
function textUpdate(dom, el, textNode) {
|
|
@@ -1588,6 +1604,11 @@ function wireOverlayBehavior(ctx, graphNode, instance, target) {
|
|
|
1588
1604
|
instance.trackCleanup(teardown);
|
|
1589
1605
|
if (openSig.peek() === true) onOpenChange(true);
|
|
1590
1606
|
}
|
|
1607
|
+
function wireComponentBehavior(ctx, graphNode, instance) {
|
|
1608
|
+
const fn = ctx.graph.getHandler(`__component__${graphNode.id}`);
|
|
1609
|
+
if (fn === void 0) return;
|
|
1610
|
+
for (const cleanup of fn()) instance.trackCleanup(cleanup);
|
|
1611
|
+
}
|
|
1591
1612
|
|
|
1592
1613
|
// ../renderer/src/hydration-diagnostics.ts
|
|
1593
1614
|
function formatHydrationDiagnostic(d) {
|
|
@@ -1712,6 +1733,7 @@ function hydrateNode(ctx, graphNode, domNode, path) {
|
|
|
1712
1733
|
return instance;
|
|
1713
1734
|
}
|
|
1714
1735
|
hydrateChildren(ctx, graphNode, instance, domNode, path);
|
|
1736
|
+
if (graphNode.type === "component") wireComponentBehavior(ctx, graphNode, instance);
|
|
1715
1737
|
return instance;
|
|
1716
1738
|
}
|
|
1717
1739
|
}
|
|
@@ -1910,7 +1932,8 @@ function analyzeGraph(graph) {
|
|
|
1910
1932
|
const isList = node.type === "reactive-list";
|
|
1911
1933
|
const isConditional = node.type === "conditional";
|
|
1912
1934
|
const isPortal = node.type === "portal";
|
|
1913
|
-
const
|
|
1935
|
+
const isComponent = node.type === "component";
|
|
1936
|
+
const isStatic = node.stateRefs.length === 0 && !hasEvents && !isList && !isConditional && !isPortal && !isComponent;
|
|
1914
1937
|
const isStaticSubtree = isStatic && allChildrenStatic;
|
|
1915
1938
|
nodes.set(node.id, {
|
|
1916
1939
|
isStatic,
|
|
@@ -2335,21 +2358,1084 @@ function renderServerThenHydrate(build, options = {}) {
|
|
|
2335
2358
|
};
|
|
2336
2359
|
}
|
|
2337
2360
|
|
|
2361
|
+
// ../state/src/signal.ts
|
|
2362
|
+
var _activeConsumer = null;
|
|
2363
|
+
function withConsumer(consumer, fn) {
|
|
2364
|
+
const prev = _activeConsumer;
|
|
2365
|
+
_activeConsumer = consumer;
|
|
2366
|
+
try {
|
|
2367
|
+
return fn();
|
|
2368
|
+
} finally {
|
|
2369
|
+
_activeConsumer = prev;
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
var _batchDepth = 0;
|
|
2373
|
+
var _pendingFlushes = /* @__PURE__ */ new Map();
|
|
2374
|
+
function _enqueueBatchFlush(sig, value) {
|
|
2375
|
+
_pendingFlushes.set(sig, { signal: sig, value });
|
|
2376
|
+
}
|
|
2377
|
+
var Signal = class {
|
|
2378
|
+
_value;
|
|
2379
|
+
_subscribers = /* @__PURE__ */ new Set();
|
|
2380
|
+
_consumers = /* @__PURE__ */ new Set();
|
|
2381
|
+
constructor(initial) {
|
|
2382
|
+
this._value = initial;
|
|
2383
|
+
}
|
|
2384
|
+
get() {
|
|
2385
|
+
if (_activeConsumer !== null) {
|
|
2386
|
+
this._consumers.add(_activeConsumer);
|
|
2387
|
+
_activeConsumer._addSource(this);
|
|
2388
|
+
}
|
|
2389
|
+
return this._value;
|
|
2390
|
+
}
|
|
2391
|
+
peek() {
|
|
2392
|
+
return this._value;
|
|
2393
|
+
}
|
|
2394
|
+
set(value) {
|
|
2395
|
+
if (Object.is(this._value, value)) return;
|
|
2396
|
+
this._value = value;
|
|
2397
|
+
if (_batchDepth > 0) {
|
|
2398
|
+
_enqueueBatchFlush(this, value);
|
|
2399
|
+
} else {
|
|
2400
|
+
this._flush(value);
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
update(fn) {
|
|
2404
|
+
this.set(fn(this._value));
|
|
2405
|
+
}
|
|
2406
|
+
subscribe(fn) {
|
|
2407
|
+
this._subscribers.add(fn);
|
|
2408
|
+
return () => {
|
|
2409
|
+
this._subscribers.delete(fn);
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
_removeConsumer(consumer) {
|
|
2413
|
+
this._consumers.delete(consumer);
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* Called by the batch machinery after the batch has completed.
|
|
2417
|
+
* Notifies subscribers with the final coalesced value.
|
|
2418
|
+
*/
|
|
2419
|
+
_flushBatch(value) {
|
|
2420
|
+
this._flush(value);
|
|
2421
|
+
}
|
|
2422
|
+
_flush(value) {
|
|
2423
|
+
for (const sub of [...this._subscribers]) sub(value);
|
|
2424
|
+
for (const consumer of [...this._consumers]) consumer._invalidate();
|
|
2425
|
+
}
|
|
2426
|
+
/**
|
|
2427
|
+
* @internal DevTools inspection only. The number of live observers
|
|
2428
|
+
* (direct subscribers plus derived/effect consumers). Read-only; never
|
|
2429
|
+
* mutates reactive state.
|
|
2430
|
+
*/
|
|
2431
|
+
_observerCount() {
|
|
2432
|
+
return this._subscribers.size + this._consumers.size;
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
var DerivedSignal = class {
|
|
2436
|
+
_value = void 0;
|
|
2437
|
+
_dirty = true;
|
|
2438
|
+
_disposed = false;
|
|
2439
|
+
_fn;
|
|
2440
|
+
_subscribers = /* @__PURE__ */ new Set();
|
|
2441
|
+
/** All upstream sources this derived currently reads from. */
|
|
2442
|
+
_sources = /* @__PURE__ */ new Set();
|
|
2443
|
+
/** Downstream consumers that depend on this derived. */
|
|
2444
|
+
_consumers = /* @__PURE__ */ new Set();
|
|
2445
|
+
constructor(fn) {
|
|
2446
|
+
this._fn = fn;
|
|
2447
|
+
}
|
|
2448
|
+
get() {
|
|
2449
|
+
if (_activeConsumer !== null) {
|
|
2450
|
+
this._consumers.add(_activeConsumer);
|
|
2451
|
+
_activeConsumer._addSource(this);
|
|
2452
|
+
}
|
|
2453
|
+
if (this._dirty) this._recompute();
|
|
2454
|
+
return this._value;
|
|
2455
|
+
}
|
|
2456
|
+
peek() {
|
|
2457
|
+
if (this._dirty) this._recompute();
|
|
2458
|
+
return this._value;
|
|
2459
|
+
}
|
|
2460
|
+
subscribe(fn) {
|
|
2461
|
+
if (this._dirty) this._recompute();
|
|
2462
|
+
this._subscribers.add(fn);
|
|
2463
|
+
return () => {
|
|
2464
|
+
this._subscribers.delete(fn);
|
|
2465
|
+
};
|
|
2466
|
+
}
|
|
2467
|
+
_addSource(src) {
|
|
2468
|
+
this._sources.add(src);
|
|
2469
|
+
}
|
|
2470
|
+
_removeConsumer(consumer) {
|
|
2471
|
+
this._consumers.delete(consumer);
|
|
2472
|
+
}
|
|
2473
|
+
_invalidate() {
|
|
2474
|
+
if (this._disposed) return;
|
|
2475
|
+
this._dirty = true;
|
|
2476
|
+
const newVal = this.peek();
|
|
2477
|
+
for (const sub of [...this._subscribers]) sub(newVal);
|
|
2478
|
+
for (const consumer of [...this._consumers]) consumer._invalidate();
|
|
2479
|
+
}
|
|
2480
|
+
_recompute() {
|
|
2481
|
+
for (const src of this._sources) src._removeConsumer(this);
|
|
2482
|
+
this._sources.clear();
|
|
2483
|
+
this._value = withConsumer(this, this._fn);
|
|
2484
|
+
this._dirty = false;
|
|
2485
|
+
}
|
|
2486
|
+
dispose() {
|
|
2487
|
+
this._disposed = true;
|
|
2488
|
+
for (const src of this._sources) src._removeConsumer(this);
|
|
2489
|
+
this._sources.clear();
|
|
2490
|
+
this._subscribers.clear();
|
|
2491
|
+
this._consumers.clear();
|
|
2492
|
+
}
|
|
2493
|
+
/**
|
|
2494
|
+
* @internal DevTools inspection only. Live observers (subscribers plus
|
|
2495
|
+
* downstream consumers). Read-only.
|
|
2496
|
+
*/
|
|
2497
|
+
_observerCount() {
|
|
2498
|
+
return this._subscribers.size + this._consumers.size;
|
|
2499
|
+
}
|
|
2500
|
+
};
|
|
2501
|
+
var Effect = class {
|
|
2502
|
+
_fn;
|
|
2503
|
+
_cleanup = void 0;
|
|
2504
|
+
_disposed = false;
|
|
2505
|
+
_sources = /* @__PURE__ */ new Set();
|
|
2506
|
+
constructor(fn) {
|
|
2507
|
+
this._fn = fn;
|
|
2508
|
+
this._run();
|
|
2509
|
+
}
|
|
2510
|
+
_addSource(src) {
|
|
2511
|
+
this._sources.add(src);
|
|
2512
|
+
}
|
|
2513
|
+
_invalidate() {
|
|
2514
|
+
if (this._disposed) return;
|
|
2515
|
+
this._run();
|
|
2516
|
+
}
|
|
2517
|
+
_run() {
|
|
2518
|
+
for (const src of this._sources) src._removeConsumer(this);
|
|
2519
|
+
this._sources.clear();
|
|
2520
|
+
if (typeof this._cleanup === "function") this._cleanup();
|
|
2521
|
+
const result = withConsumer(this, this._fn);
|
|
2522
|
+
this._cleanup = typeof result === "function" ? result : void 0;
|
|
2523
|
+
}
|
|
2524
|
+
dispose() {
|
|
2525
|
+
this._disposed = true;
|
|
2526
|
+
for (const src of this._sources) src._removeConsumer(this);
|
|
2527
|
+
this._sources.clear();
|
|
2528
|
+
if (typeof this._cleanup === "function") this._cleanup();
|
|
2529
|
+
this._cleanup = void 0;
|
|
2530
|
+
}
|
|
2531
|
+
};
|
|
2532
|
+
function signal(initial) {
|
|
2533
|
+
return new Signal(initial);
|
|
2534
|
+
}
|
|
2535
|
+
function derived(fn) {
|
|
2536
|
+
return new DerivedSignal(fn);
|
|
2537
|
+
}
|
|
2538
|
+
function effect(fn) {
|
|
2539
|
+
const e = new Effect(fn);
|
|
2540
|
+
return () => e.dispose();
|
|
2541
|
+
}
|
|
2542
|
+
|
|
2543
|
+
// ../dsl/src/builders.ts
|
|
2544
|
+
function isSignal(v) {
|
|
2545
|
+
return v !== null && typeof v === "object" && typeof v["get"] === "function" && typeof v["subscribe"] === "function";
|
|
2546
|
+
}
|
|
2547
|
+
function bindValue(graph, node, propKey, value) {
|
|
2548
|
+
if (isSignal(value)) {
|
|
2549
|
+
const signalId = `${node.id}:${propKey}`;
|
|
2550
|
+
node.stateRefs.push({ signalId, propKey });
|
|
2551
|
+
graph.registerHandler(`__signal__${signalId}`, value);
|
|
2552
|
+
return value.peek();
|
|
2553
|
+
}
|
|
2554
|
+
return value;
|
|
2555
|
+
}
|
|
2556
|
+
function applyA11yProps(props, options) {
|
|
2557
|
+
if (options.role !== void 0) props["role"] = options.role;
|
|
2558
|
+
if (options.tabIndex !== void 0) props["tabindex"] = String(options.tabIndex);
|
|
2559
|
+
if (options.ariaLabel !== void 0) props["aria-label"] = options.ariaLabel;
|
|
2560
|
+
if (options.ariaLabelledBy !== void 0) props["aria-labelledby"] = options.ariaLabelledBy;
|
|
2561
|
+
if (options.ariaDescribedBy !== void 0) props["aria-describedby"] = options.ariaDescribedBy;
|
|
2562
|
+
if (options.ariaExpanded !== void 0) props["aria-expanded"] = String(options.ariaExpanded);
|
|
2563
|
+
if (options.ariaControls !== void 0) props["aria-controls"] = options.ariaControls;
|
|
2564
|
+
if (options.ariaHidden !== void 0) props["aria-hidden"] = String(options.ariaHidden);
|
|
2565
|
+
if (options.ariaLive !== void 0) props["aria-live"] = options.ariaLive;
|
|
2566
|
+
if (options.ariaCurrent !== void 0) props["aria-current"] = String(options.ariaCurrent);
|
|
2567
|
+
if (options.ariaInvalid !== void 0) props["aria-invalid"] = String(options.ariaInvalid);
|
|
2568
|
+
if (options.ariaRequired !== void 0) props["aria-required"] = String(options.ariaRequired);
|
|
2569
|
+
if (options.ariaModal !== void 0) props["aria-modal"] = String(options.ariaModal);
|
|
2570
|
+
}
|
|
2571
|
+
function containerProps(options) {
|
|
2572
|
+
const props = {};
|
|
2573
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
2574
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
2575
|
+
if (options.key !== void 0) props["key"] = options.key;
|
|
2576
|
+
applyA11yProps(props, options);
|
|
2577
|
+
return props;
|
|
2578
|
+
}
|
|
2579
|
+
function itemIdentity(item, index) {
|
|
2580
|
+
if (item !== null && typeof item === "object") {
|
|
2581
|
+
const obj = item;
|
|
2582
|
+
if ("id" in obj) return `id:${String(obj["id"])}`;
|
|
2583
|
+
if ("key" in obj) return `key:${String(obj["key"])}`;
|
|
2584
|
+
return `idx:${index}`;
|
|
2585
|
+
}
|
|
2586
|
+
return `val:${String(item)}`;
|
|
2587
|
+
}
|
|
2588
|
+
function itemValueSignature(item) {
|
|
2589
|
+
try {
|
|
2590
|
+
return JSON.stringify(item) ?? String(item);
|
|
2591
|
+
} catch {
|
|
2592
|
+
return String(item);
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
function reactiveListItemSignature(item) {
|
|
2596
|
+
return itemValueSignature(item);
|
|
2597
|
+
}
|
|
2598
|
+
function reactiveListItemKey(item, index) {
|
|
2599
|
+
return itemIdentity(item, index);
|
|
2600
|
+
}
|
|
2601
|
+
var OVERLAY_KINDS = {
|
|
2602
|
+
dialog: {
|
|
2603
|
+
role: "dialog",
|
|
2604
|
+
modal: true,
|
|
2605
|
+
takesFocus: true,
|
|
2606
|
+
ariaModal: true,
|
|
2607
|
+
defaultCloseOnEscape: true,
|
|
2608
|
+
defaultRestoreFocus: true
|
|
2609
|
+
},
|
|
2610
|
+
popover: {
|
|
2611
|
+
role: "dialog",
|
|
2612
|
+
modal: false,
|
|
2613
|
+
takesFocus: true,
|
|
2614
|
+
ariaModal: false,
|
|
2615
|
+
defaultCloseOnEscape: true,
|
|
2616
|
+
defaultRestoreFocus: true
|
|
2617
|
+
},
|
|
2618
|
+
tooltip: {
|
|
2619
|
+
role: "tooltip",
|
|
2620
|
+
modal: false,
|
|
2621
|
+
takesFocus: false,
|
|
2622
|
+
ariaModal: false,
|
|
2623
|
+
defaultCloseOnEscape: false,
|
|
2624
|
+
defaultRestoreFocus: false
|
|
2625
|
+
},
|
|
2626
|
+
dropdown: {
|
|
2627
|
+
role: "menu",
|
|
2628
|
+
modal: false,
|
|
2629
|
+
takesFocus: true,
|
|
2630
|
+
ariaModal: false,
|
|
2631
|
+
defaultCloseOnEscape: true,
|
|
2632
|
+
defaultRestoreFocus: true
|
|
2633
|
+
},
|
|
2634
|
+
toast: {
|
|
2635
|
+
role: "status",
|
|
2636
|
+
modal: false,
|
|
2637
|
+
takesFocus: false,
|
|
2638
|
+
ariaModal: false,
|
|
2639
|
+
ariaLive: "polite",
|
|
2640
|
+
defaultCloseOnEscape: false,
|
|
2641
|
+
defaultRestoreFocus: false
|
|
2642
|
+
}
|
|
2643
|
+
};
|
|
2644
|
+
var ContentBuilderBase = class {
|
|
2645
|
+
constructor(_node, _graph) {
|
|
2646
|
+
this._node = _node;
|
|
2647
|
+
this._graph = _graph;
|
|
2648
|
+
}
|
|
2649
|
+
heading(text, options = {}) {
|
|
2650
|
+
const props = { level: options.level ?? 1 };
|
|
2651
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
2652
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
2653
|
+
applyA11yProps(props, options);
|
|
2654
|
+
const node = this._graph.createNode("heading", { parent: this._node, props });
|
|
2655
|
+
const resolved = bindValue(this._graph, node, "text", text);
|
|
2656
|
+
node.setProp("text", resolved);
|
|
2657
|
+
}
|
|
2658
|
+
text(content, options = {}) {
|
|
2659
|
+
const props = {};
|
|
2660
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
2661
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
2662
|
+
applyA11yProps(props, options);
|
|
2663
|
+
const node = this._graph.createNode("text", { parent: this._node, props });
|
|
2664
|
+
const resolved = bindValue(this._graph, node, "text", content);
|
|
2665
|
+
node.setProp("text", resolved);
|
|
2666
|
+
}
|
|
2667
|
+
button(label, options = {}) {
|
|
2668
|
+
const props = {};
|
|
2669
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
2670
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
2671
|
+
applyA11yProps(props, options);
|
|
2672
|
+
const node = this._graph.createNode("button", { parent: this._node, props });
|
|
2673
|
+
const resolved = bindValue(this._graph, node, "label", label);
|
|
2674
|
+
node.setProp("label", resolved);
|
|
2675
|
+
if (options.disabled !== void 0) {
|
|
2676
|
+
const resolvedDisabled = bindValue(this._graph, node, "disabled", options.disabled);
|
|
2677
|
+
node.setProp("disabled", resolvedDisabled);
|
|
2678
|
+
}
|
|
2679
|
+
if (options.onClick !== void 0) {
|
|
2680
|
+
const handlerKey = `click:${node.id}`;
|
|
2681
|
+
this._graph.registerHandler(handlerKey, options.onClick);
|
|
2682
|
+
node.addEvent({ type: "click", handlerKey });
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
input(options = {}) {
|
|
2686
|
+
const props = {};
|
|
2687
|
+
props["inputType"] = options.type ?? "text";
|
|
2688
|
+
if (options.placeholder !== void 0) props["placeholder"] = options.placeholder;
|
|
2689
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
2690
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
2691
|
+
applyA11yProps(props, options);
|
|
2692
|
+
const nodeOpts = {
|
|
2693
|
+
props,
|
|
2694
|
+
parent: this._node
|
|
2695
|
+
};
|
|
2696
|
+
if (options.id !== void 0) nodeOpts.key = options.id;
|
|
2697
|
+
const node = this._graph.createNode("input", nodeOpts);
|
|
2698
|
+
const bindSignal = options.bind;
|
|
2699
|
+
const valueBindable = bindSignal !== void 0 ? bindSignal : options.value;
|
|
2700
|
+
const inputHandler = bindSignal !== void 0 ? (v) => bindSignal.set(v) : options.onInput;
|
|
2701
|
+
if (valueBindable !== void 0) {
|
|
2702
|
+
const resolved = bindValue(this._graph, node, "value", valueBindable);
|
|
2703
|
+
node.setProp("value", resolved);
|
|
2704
|
+
}
|
|
2705
|
+
if (options.disabled !== void 0) {
|
|
2706
|
+
const resolved = bindValue(this._graph, node, "disabled", options.disabled);
|
|
2707
|
+
node.setProp("disabled", resolved);
|
|
2708
|
+
}
|
|
2709
|
+
if (inputHandler !== void 0) {
|
|
2710
|
+
const handlerKey = `input:${node.id}`;
|
|
2711
|
+
this._graph.registerHandler(handlerKey, inputHandler);
|
|
2712
|
+
node.addEvent({ type: "input", handlerKey });
|
|
2713
|
+
}
|
|
2714
|
+
if (options.onChange !== void 0) {
|
|
2715
|
+
const handlerKey = `change:${node.id}`;
|
|
2716
|
+
this._graph.registerHandler(handlerKey, options.onChange);
|
|
2717
|
+
node.addEvent({ type: "change", handlerKey });
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
2720
|
+
image(options) {
|
|
2721
|
+
const props = {
|
|
2722
|
+
src: options.src,
|
|
2723
|
+
alt: options.alt
|
|
2724
|
+
};
|
|
2725
|
+
if (options.width !== void 0) props["width"] = options.width;
|
|
2726
|
+
if (options.height !== void 0) props["height"] = options.height;
|
|
2727
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
2728
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
2729
|
+
applyA11yProps(props, options);
|
|
2730
|
+
const nodeOpts = {
|
|
2731
|
+
props,
|
|
2732
|
+
parent: this._node
|
|
2733
|
+
};
|
|
2734
|
+
if (options.id !== void 0) nodeOpts.key = options.id;
|
|
2735
|
+
this._graph.createNode("image", nodeOpts);
|
|
2736
|
+
}
|
|
2737
|
+
link(label, options) {
|
|
2738
|
+
const props = {
|
|
2739
|
+
href: options.href,
|
|
2740
|
+
external: options.external ?? false
|
|
2741
|
+
};
|
|
2742
|
+
if (options.class !== void 0) props["class"] = options.class;
|
|
2743
|
+
if (options.id !== void 0) props["id"] = options.id;
|
|
2744
|
+
applyA11yProps(props, options);
|
|
2745
|
+
const node = this._graph.createNode("link", { parent: this._node, props });
|
|
2746
|
+
const resolved = bindValue(this._graph, node, "label", label);
|
|
2747
|
+
node.setProp("label", resolved);
|
|
2748
|
+
if (options.onClick !== void 0) {
|
|
2749
|
+
const handlerKey = `click:${node.id}`;
|
|
2750
|
+
this._graph.registerHandler(handlerKey, options.onClick);
|
|
2751
|
+
node.addEvent({ type: "click", handlerKey });
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
};
|
|
2755
|
+
var ContainerBuilderBase = class extends ContentBuilderBase {
|
|
2756
|
+
section(key, builder, options = {}) {
|
|
2757
|
+
const node = this._graph.createNode("section", {
|
|
2758
|
+
key,
|
|
2759
|
+
parent: this._node,
|
|
2760
|
+
props: containerProps(options)
|
|
2761
|
+
});
|
|
2762
|
+
builder(new SectionBuilderImpl(node, this._graph));
|
|
2763
|
+
}
|
|
2764
|
+
container(key, builder, options = {}) {
|
|
2765
|
+
const node = this._graph.createNode("container", {
|
|
2766
|
+
key,
|
|
2767
|
+
parent: this._node,
|
|
2768
|
+
props: containerProps(options)
|
|
2769
|
+
});
|
|
2770
|
+
builder(new ContainerBuilderImpl(node, this._graph));
|
|
2771
|
+
}
|
|
2772
|
+
list(key, builder, options = {}) {
|
|
2773
|
+
const node = this._graph.createNode("list", {
|
|
2774
|
+
key,
|
|
2775
|
+
parent: this._node,
|
|
2776
|
+
props: containerProps(options)
|
|
2777
|
+
});
|
|
2778
|
+
builder(new ListBuilderImpl(node, this._graph));
|
|
2779
|
+
}
|
|
2780
|
+
listOf(key, items, renderItem, options = {}) {
|
|
2781
|
+
const graph = this._graph;
|
|
2782
|
+
const node = graph.createNode("reactive-list", {
|
|
2783
|
+
key,
|
|
2784
|
+
parent: this._node,
|
|
2785
|
+
props: containerProps(options)
|
|
2786
|
+
});
|
|
2787
|
+
const signalId = `${node.id}:items`;
|
|
2788
|
+
node.stateRefs.push({ signalId, propKey: "items" });
|
|
2789
|
+
graph.registerHandler(`__signal__${signalId}`, items);
|
|
2790
|
+
const buildItem = (item, index) => {
|
|
2791
|
+
const itemKey = reactiveListItemKey(item, index);
|
|
2792
|
+
const itemNode = graph.createNode("list-item", {
|
|
2793
|
+
key: itemKey,
|
|
2794
|
+
// `_item` records the source item *reference* so the reconciler can
|
|
2795
|
+
// short-circuit unchanged rows by identity (no signature hashing); `_sig`
|
|
2796
|
+
// is the content signature used to detect an in-place data change when the
|
|
2797
|
+
// reference differs. Both are internal metadata (leading `_`) and never
|
|
2798
|
+
// reach the DOM.
|
|
2799
|
+
props: {
|
|
2800
|
+
key: itemKey,
|
|
2801
|
+
_sig: reactiveListItemSignature(item),
|
|
2802
|
+
// The item reference is stored as opaque internal metadata (never
|
|
2803
|
+
// rendered); cast through `unknown` since `T` is not a `PropValue`.
|
|
2804
|
+
_item: item
|
|
2805
|
+
}
|
|
2806
|
+
});
|
|
2807
|
+
renderItem(item, index, new ContainerBuilderImpl(itemNode, graph));
|
|
2808
|
+
return itemNode;
|
|
2809
|
+
};
|
|
2810
|
+
const buildPlan = (raw) => {
|
|
2811
|
+
const arr = Array.isArray(raw) ? raw : [];
|
|
2812
|
+
const plan = new Array(arr.length);
|
|
2813
|
+
for (let i = 0; i < arr.length; i++) {
|
|
2814
|
+
const item = arr[i];
|
|
2815
|
+
const index = i;
|
|
2816
|
+
plan[i] = {
|
|
2817
|
+
key: reactiveListItemKey(item, index),
|
|
2818
|
+
item,
|
|
2819
|
+
sig: () => reactiveListItemSignature(item),
|
|
2820
|
+
build: () => buildItem(item, index)
|
|
2821
|
+
};
|
|
2822
|
+
}
|
|
2823
|
+
return plan;
|
|
2824
|
+
};
|
|
2825
|
+
graph.registerHandler(`__listplan__${node.id}`, buildPlan);
|
|
2826
|
+
const current = isSignal(items) ? items.peek() : items;
|
|
2827
|
+
const initial = Array.isArray(current) ? current : [];
|
|
2828
|
+
initial.forEach((item, i) => {
|
|
2829
|
+
node.appendChild(buildItem(item, i));
|
|
2830
|
+
});
|
|
2831
|
+
}
|
|
2832
|
+
form(key, builder, options = {}) {
|
|
2833
|
+
const props = containerProps(options);
|
|
2834
|
+
const node = this._graph.createNode("form", {
|
|
2835
|
+
key,
|
|
2836
|
+
parent: this._node,
|
|
2837
|
+
props
|
|
2838
|
+
});
|
|
2839
|
+
if (options.onSubmit !== void 0) {
|
|
2840
|
+
const handlerKey = `submit:${node.id}`;
|
|
2841
|
+
this._graph.registerHandler(handlerKey, options.onSubmit);
|
|
2842
|
+
node.addEvent({ type: "submit", handlerKey });
|
|
2843
|
+
}
|
|
2844
|
+
builder(new FormBuilderImpl(node, this._graph));
|
|
2845
|
+
}
|
|
2846
|
+
when(condition, builder, elseBuilder) {
|
|
2847
|
+
const graph = this._graph;
|
|
2848
|
+
const node = graph.createNode("conditional", {
|
|
2849
|
+
parent: this._node,
|
|
2850
|
+
props: containerProps({})
|
|
2851
|
+
});
|
|
2852
|
+
const buildBranch = (build, tag) => {
|
|
2853
|
+
const branchKey = `when-${tag}:${node.id}`;
|
|
2854
|
+
const branch = graph.createNode("container", {
|
|
2855
|
+
key: branchKey,
|
|
2856
|
+
props: { key: branchKey }
|
|
2857
|
+
});
|
|
2858
|
+
build(new ContainerBuilderImpl(branch, graph));
|
|
2859
|
+
return branch;
|
|
2860
|
+
};
|
|
2861
|
+
const buildAll = (raw) => {
|
|
2862
|
+
if (raw) return [buildBranch(builder, "then")];
|
|
2863
|
+
return elseBuilder !== void 0 ? [buildBranch(elseBuilder, "else")] : [];
|
|
2864
|
+
};
|
|
2865
|
+
if (isSignal(condition)) {
|
|
2866
|
+
const signalId = `${node.id}:items`;
|
|
2867
|
+
node.stateRefs.push({ signalId, propKey: "items" });
|
|
2868
|
+
graph.registerHandler(`__signal__${signalId}`, condition);
|
|
2869
|
+
graph.registerHandler(`__listbuild__${node.id}`, buildAll);
|
|
2870
|
+
const current = condition.peek();
|
|
2871
|
+
for (const child of buildAll(current)) node.appendChild(child);
|
|
2872
|
+
} else {
|
|
2873
|
+
for (const child of buildAll(condition)) node.appendChild(child);
|
|
2874
|
+
}
|
|
2875
|
+
}
|
|
2876
|
+
errorBoundary(id, builder, options) {
|
|
2877
|
+
const sources = options.source === void 0 ? [] : Array.isArray(options.source) ? [...options.source] : [options.source];
|
|
2878
|
+
const localError = signal(void 0);
|
|
2879
|
+
const retryNonce = signal(0);
|
|
2880
|
+
const readError = () => {
|
|
2881
|
+
const local = localError.peek();
|
|
2882
|
+
if (local !== void 0 && local !== null) return local;
|
|
2883
|
+
for (const s of sources) {
|
|
2884
|
+
const e = s.peek();
|
|
2885
|
+
if (e !== void 0 && e !== null) return e;
|
|
2886
|
+
}
|
|
2887
|
+
return void 0;
|
|
2888
|
+
};
|
|
2889
|
+
const hasError = derived(() => {
|
|
2890
|
+
retryNonce.get();
|
|
2891
|
+
localError.get();
|
|
2892
|
+
for (const s of sources) s.get();
|
|
2893
|
+
return readError() !== void 0;
|
|
2894
|
+
});
|
|
2895
|
+
const retry = () => {
|
|
2896
|
+
localError.set(void 0);
|
|
2897
|
+
options.onRetry?.();
|
|
2898
|
+
retryNonce.update((n) => n + 1);
|
|
2899
|
+
};
|
|
2900
|
+
this.container(id, (c) => {
|
|
2901
|
+
c.when(
|
|
2902
|
+
hasError,
|
|
2903
|
+
// Error state → fallback.
|
|
2904
|
+
(fb) => options.fallback(fb, readError(), retry),
|
|
2905
|
+
// Healthy state → body, guarded against synchronous build throws.
|
|
2906
|
+
(body) => {
|
|
2907
|
+
try {
|
|
2908
|
+
builder(body);
|
|
2909
|
+
} catch (err) {
|
|
2910
|
+
queueMicrotask(() => localError.set(err));
|
|
2911
|
+
}
|
|
2912
|
+
}
|
|
2913
|
+
);
|
|
2914
|
+
}, { id });
|
|
2915
|
+
}
|
|
2916
|
+
// ── Portals & overlays ──────────────────────────────────────────────────────
|
|
2917
|
+
portal(key, builder, options = {}) {
|
|
2918
|
+
const node = this._graph.createNode("portal", {
|
|
2919
|
+
key,
|
|
2920
|
+
parent: this._node,
|
|
2921
|
+
props: containerProps(options)
|
|
2922
|
+
});
|
|
2923
|
+
builder(new ContainerBuilderImpl(node, this._graph));
|
|
2924
|
+
}
|
|
2925
|
+
/**
|
|
2926
|
+
* Shared assembly for every overlay kind: a `portal` node whose single child
|
|
2927
|
+
* is a `when(open, panel)` conditional. The panel container carries the
|
|
2928
|
+
* kind's ARIA semantics; `builder` fills it. An `__overlay__<portalId>`
|
|
2929
|
+
* descriptor is registered so the renderer wires focus/keyboard behavior to
|
|
2930
|
+
* the same `open` signal that drives the panel. Reuses existing primitives
|
|
2931
|
+
* (portal + when + container) — no new render path.
|
|
2932
|
+
*/
|
|
2933
|
+
_overlay(kind, key, options, builder) {
|
|
2934
|
+
const graph = this._graph;
|
|
2935
|
+
const portalNode = graph.createNode("portal", {
|
|
2936
|
+
key,
|
|
2937
|
+
parent: this._node,
|
|
2938
|
+
props: { key }
|
|
2939
|
+
});
|
|
2940
|
+
const openBindable = options.open;
|
|
2941
|
+
const openSignal = isSignal(openBindable) ? openBindable : signal(openBindable);
|
|
2942
|
+
const panelOptions = {
|
|
2943
|
+
role: options.role ?? kind.role,
|
|
2944
|
+
...kind.ariaModal ? { ariaModal: true } : {},
|
|
2945
|
+
...kind.ariaLive !== void 0 ? { ariaLive: kind.ariaLive } : {},
|
|
2946
|
+
...options.class !== void 0 ? { class: options.class } : {},
|
|
2947
|
+
...options.ariaLabel !== void 0 ? { ariaLabel: options.ariaLabel } : {},
|
|
2948
|
+
...options.ariaLabelledBy !== void 0 ? { ariaLabelledBy: options.ariaLabelledBy } : {},
|
|
2949
|
+
...options.ariaDescribedBy !== void 0 ? { ariaDescribedBy: options.ariaDescribedBy } : {}
|
|
2950
|
+
};
|
|
2951
|
+
const portalBuilder = new ContainerBuilderImpl(portalNode, graph);
|
|
2952
|
+
portalBuilder.when(openSignal, (panelHost) => {
|
|
2953
|
+
panelHost.container(`${key}__panel`, builder, panelOptions);
|
|
2954
|
+
});
|
|
2955
|
+
const descriptor = {
|
|
2956
|
+
open: openSignal,
|
|
2957
|
+
modal: kind.modal,
|
|
2958
|
+
takesFocus: kind.takesFocus,
|
|
2959
|
+
closeOnEscape: options.closeOnEscape ?? kind.defaultCloseOnEscape,
|
|
2960
|
+
restoreFocus: options.restoreFocus ?? kind.defaultRestoreFocus,
|
|
2961
|
+
...options.initialFocusId !== void 0 ? { initialFocusId: options.initialFocusId } : {},
|
|
2962
|
+
...options.onClose !== void 0 ? { onClose: options.onClose } : {}
|
|
2963
|
+
};
|
|
2964
|
+
graph.registerHandler(
|
|
2965
|
+
`__overlay__${portalNode.id}`,
|
|
2966
|
+
() => descriptor
|
|
2967
|
+
);
|
|
2968
|
+
}
|
|
2969
|
+
dialog(key, options, builder) {
|
|
2970
|
+
this._overlay(OVERLAY_KINDS.dialog, key, options, builder);
|
|
2971
|
+
}
|
|
2972
|
+
popover(key, options, builder) {
|
|
2973
|
+
this._overlay(OVERLAY_KINDS.popover, key, options, builder);
|
|
2974
|
+
}
|
|
2975
|
+
tooltip(key, options, builder) {
|
|
2976
|
+
this._overlay(OVERLAY_KINDS.tooltip, key, options, builder);
|
|
2977
|
+
}
|
|
2978
|
+
dropdown(key, options, builder) {
|
|
2979
|
+
this._overlay(OVERLAY_KINDS.dropdown, key, options, builder);
|
|
2980
|
+
}
|
|
2981
|
+
toast(key, options, builder) {
|
|
2982
|
+
this._overlay(OVERLAY_KINDS.toast, key, options, builder);
|
|
2983
|
+
}
|
|
2984
|
+
// ── Components ────────────────────────────────────────────────────────────
|
|
2985
|
+
/**
|
|
2986
|
+
* Instantiate a reusable component (§3–§9). Creates a `'component'` node
|
|
2987
|
+
* (rendered as a `<div>` wrapper — preserves the one-node/one-element
|
|
2988
|
+
* positional-hydration invariant), then runs `def.setup(props, ctx)`
|
|
2989
|
+
* synchronously to obtain the render function and fills the component's own
|
|
2990
|
+
* container scope with it — structurally identical to `container`/
|
|
2991
|
+
* `errorBoundary`. Cleanups the setup registers via `ctx.effect`/
|
|
2992
|
+
* `ctx.onCleanup` are collected into a closure and exposed to the renderer
|
|
2993
|
+
* through a `__component__<id>` handler (mirroring `__overlay__`); the mount/
|
|
2994
|
+
* hydrate paths read it and route each into `NodeInstance.trackCleanup`, so
|
|
2995
|
+
* teardown runs (children-first) when the component leaves the graph.
|
|
2996
|
+
*
|
|
2997
|
+
* `setup` runs once per instance here at build time. When this component sits
|
|
2998
|
+
* inside a keyed list / conditional, a rebuild disposes the old instance
|
|
2999
|
+
* (running its cleanups + pruning its `__component__` entry) and re-runs this
|
|
3000
|
+
* method for the new node — so re-invocation is safe and leak-free.
|
|
3001
|
+
*/
|
|
3002
|
+
component(key, def, props, children) {
|
|
3003
|
+
const graph = this._graph;
|
|
3004
|
+
const node = graph.createNode("component", {
|
|
3005
|
+
key,
|
|
3006
|
+
parent: this._node,
|
|
3007
|
+
// `data-streetui-component` is a non-underscore prop, so it reaches the
|
|
3008
|
+
// DOM as an attribute and is visible to DevTools (§21) — unlike the
|
|
3009
|
+
// internal `_`-prefixed metadata the renderer hides.
|
|
3010
|
+
props: { key, "data-streetui-component": def.name }
|
|
3011
|
+
});
|
|
3012
|
+
const cleanups = [];
|
|
3013
|
+
const ctx = {
|
|
3014
|
+
key,
|
|
3015
|
+
onCleanup(fn) {
|
|
3016
|
+
cleanups.push(fn);
|
|
3017
|
+
},
|
|
3018
|
+
effect(fn) {
|
|
3019
|
+
cleanups.push(effect(fn));
|
|
3020
|
+
},
|
|
3021
|
+
renderChildren(content) {
|
|
3022
|
+
if (children !== void 0) children(content);
|
|
3023
|
+
}
|
|
3024
|
+
};
|
|
3025
|
+
let render2;
|
|
3026
|
+
try {
|
|
3027
|
+
render2 = def.setup(props, ctx);
|
|
3028
|
+
render2(new ContainerBuilderImpl(node, graph));
|
|
3029
|
+
} catch (err) {
|
|
3030
|
+
queueMicrotask(() => {
|
|
3031
|
+
throw err;
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
3034
|
+
if (cleanups.length > 0) {
|
|
3035
|
+
graph.registerHandler(
|
|
3036
|
+
`__component__${node.id}`,
|
|
3037
|
+
() => cleanups
|
|
3038
|
+
);
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
};
|
|
3042
|
+
var SectionBuilderImpl = class extends ContainerBuilderBase {
|
|
3043
|
+
};
|
|
3044
|
+
var ContainerBuilderImpl = class extends ContainerBuilderBase {
|
|
3045
|
+
};
|
|
3046
|
+
var FormBuilderImpl = class extends ContainerBuilderBase {
|
|
3047
|
+
};
|
|
3048
|
+
var ListBuilderImpl = class extends ContentBuilderBase {
|
|
3049
|
+
item(key, builder, options = {}) {
|
|
3050
|
+
const node = this._graph.createNode("list-item", {
|
|
3051
|
+
key,
|
|
3052
|
+
parent: this._node,
|
|
3053
|
+
props: containerProps(options)
|
|
3054
|
+
});
|
|
3055
|
+
builder(new ContainerBuilderImpl(node, this._graph));
|
|
3056
|
+
}
|
|
3057
|
+
};
|
|
3058
|
+
var PageBuilderImpl = class extends ContainerBuilderBase {
|
|
3059
|
+
};
|
|
3060
|
+
var AppBuilder = class {
|
|
3061
|
+
constructor(_graph) {
|
|
3062
|
+
this._graph = _graph;
|
|
3063
|
+
}
|
|
3064
|
+
page(key, builder) {
|
|
3065
|
+
const node = this._graph.createNode("page", {
|
|
3066
|
+
key,
|
|
3067
|
+
parent: this._graph.root,
|
|
3068
|
+
props: { key }
|
|
3069
|
+
});
|
|
3070
|
+
builder(new PageBuilderImpl(node, this._graph));
|
|
3071
|
+
}
|
|
3072
|
+
};
|
|
3073
|
+
|
|
3074
|
+
// ../graph/src/graph-node.ts
|
|
3075
|
+
var GraphNode = class _GraphNode {
|
|
3076
|
+
id;
|
|
3077
|
+
type;
|
|
3078
|
+
key;
|
|
3079
|
+
props;
|
|
3080
|
+
events;
|
|
3081
|
+
stateRefs;
|
|
3082
|
+
children;
|
|
3083
|
+
parent;
|
|
3084
|
+
constructor(type, options = {}) {
|
|
3085
|
+
this.type = type;
|
|
3086
|
+
this.id = options.id ?? generateNodeId(type);
|
|
3087
|
+
this.key = options.key;
|
|
3088
|
+
this.props = options.props ?? {};
|
|
3089
|
+
this.events = options.events ?? [];
|
|
3090
|
+
this.stateRefs = options.stateRefs ?? [];
|
|
3091
|
+
this.children = [];
|
|
3092
|
+
this.parent = null;
|
|
3093
|
+
}
|
|
3094
|
+
// ── Child management ────────────────────────────────────────────────────────
|
|
3095
|
+
appendChild(child) {
|
|
3096
|
+
if (child.parent !== null) {
|
|
3097
|
+
child.parent.removeChild(child);
|
|
3098
|
+
}
|
|
3099
|
+
child.parent = this;
|
|
3100
|
+
this.children.push(child);
|
|
3101
|
+
}
|
|
3102
|
+
insertBefore(child, reference) {
|
|
3103
|
+
const idx = this.children.indexOf(reference);
|
|
3104
|
+
if (idx === -1) {
|
|
3105
|
+
this.appendChild(child);
|
|
3106
|
+
return;
|
|
3107
|
+
}
|
|
3108
|
+
if (child.parent !== null) {
|
|
3109
|
+
child.parent.removeChild(child);
|
|
3110
|
+
}
|
|
3111
|
+
child.parent = this;
|
|
3112
|
+
this.children.splice(idx, 0, child);
|
|
3113
|
+
}
|
|
3114
|
+
removeChild(child) {
|
|
3115
|
+
const idx = this.children.indexOf(child);
|
|
3116
|
+
if (idx === -1) return;
|
|
3117
|
+
this.children.splice(idx, 1);
|
|
3118
|
+
child.parent = null;
|
|
3119
|
+
}
|
|
3120
|
+
replaceChild(newChild, oldChild) {
|
|
3121
|
+
const idx = this.children.indexOf(oldChild);
|
|
3122
|
+
if (idx === -1) {
|
|
3123
|
+
throw new Error(`GraphNode.replaceChild: oldChild is not a child of this node`);
|
|
3124
|
+
}
|
|
3125
|
+
if (newChild.parent !== null) {
|
|
3126
|
+
newChild.parent.removeChild(newChild);
|
|
3127
|
+
}
|
|
3128
|
+
oldChild.parent = null;
|
|
3129
|
+
newChild.parent = this;
|
|
3130
|
+
this.children.splice(idx, 1, newChild);
|
|
3131
|
+
}
|
|
3132
|
+
// ── Prop helpers ────────────────────────────────────────────────────────────
|
|
3133
|
+
setProp(key, value) {
|
|
3134
|
+
this.props = { ...this.props, [key]: value };
|
|
3135
|
+
}
|
|
3136
|
+
getProp(key) {
|
|
3137
|
+
return this.props[key];
|
|
3138
|
+
}
|
|
3139
|
+
// ── Event helpers ───────────────────────────────────────────────────────────
|
|
3140
|
+
addEvent(descriptor) {
|
|
3141
|
+
this.events.push(descriptor);
|
|
3142
|
+
}
|
|
3143
|
+
removeEvent(type) {
|
|
3144
|
+
this.events = this.events.filter((e) => e.type !== type);
|
|
3145
|
+
}
|
|
3146
|
+
// ── Queries ─────────────────────────────────────────────────────────────────
|
|
3147
|
+
get isLeaf() {
|
|
3148
|
+
return this.children.length === 0;
|
|
3149
|
+
}
|
|
3150
|
+
get depth() {
|
|
3151
|
+
let d = 0;
|
|
3152
|
+
let node = this.parent;
|
|
3153
|
+
while (node !== null) {
|
|
3154
|
+
d++;
|
|
3155
|
+
node = node.parent;
|
|
3156
|
+
}
|
|
3157
|
+
return d;
|
|
3158
|
+
}
|
|
3159
|
+
get root() {
|
|
3160
|
+
let node = this;
|
|
3161
|
+
while (node.parent !== null) {
|
|
3162
|
+
node = node.parent;
|
|
3163
|
+
}
|
|
3164
|
+
return node;
|
|
3165
|
+
}
|
|
3166
|
+
/** Shallow clone — does not clone children. */
|
|
3167
|
+
shallowClone() {
|
|
3168
|
+
const opts = {
|
|
3169
|
+
props: { ...this.props },
|
|
3170
|
+
events: [...this.events],
|
|
3171
|
+
stateRefs: [...this.stateRefs]
|
|
3172
|
+
};
|
|
3173
|
+
if (this.key !== void 0) opts.key = this.key;
|
|
3174
|
+
return new _GraphNode(this.type, opts);
|
|
3175
|
+
}
|
|
3176
|
+
};
|
|
3177
|
+
|
|
3178
|
+
// ../graph/src/graph.ts
|
|
3179
|
+
var ApplicationGraph = class {
|
|
3180
|
+
root;
|
|
3181
|
+
name;
|
|
3182
|
+
version;
|
|
3183
|
+
_nodeIndex = /* @__PURE__ */ new Map();
|
|
3184
|
+
/** Handler registry — maps handlerKey → actual function */
|
|
3185
|
+
handlers = /* @__PURE__ */ new Map();
|
|
3186
|
+
constructor(options) {
|
|
3187
|
+
this.name = options.name;
|
|
3188
|
+
this.version = options.version ?? "0.0.1";
|
|
3189
|
+
this.root = new GraphNode("application", { props: { name: options.name } });
|
|
3190
|
+
this._nodeIndex.set(this.root.id, this.root);
|
|
3191
|
+
}
|
|
3192
|
+
// ── Node creation & attachment ────────────────────────────────────────────
|
|
3193
|
+
createNode(type, options = {}) {
|
|
3194
|
+
const nodeOpts = {};
|
|
3195
|
+
if (options.key !== void 0) nodeOpts.key = options.key;
|
|
3196
|
+
if (options.props !== void 0) nodeOpts.props = options.props;
|
|
3197
|
+
const node = new GraphNode(type, nodeOpts);
|
|
3198
|
+
this._nodeIndex.set(node.id, node);
|
|
3199
|
+
if (options.parent !== void 0) {
|
|
3200
|
+
options.parent.appendChild(node);
|
|
3201
|
+
}
|
|
3202
|
+
return node;
|
|
3203
|
+
}
|
|
3204
|
+
attachNode(node, parent) {
|
|
3205
|
+
this._nodeIndex.set(node.id, node);
|
|
3206
|
+
parent.appendChild(node);
|
|
3207
|
+
}
|
|
3208
|
+
detachNode(node) {
|
|
3209
|
+
if (node.parent !== null) {
|
|
3210
|
+
node.parent.removeChild(node);
|
|
3211
|
+
}
|
|
3212
|
+
this._removeFromIndex(node);
|
|
3213
|
+
}
|
|
3214
|
+
_removeFromIndex(node) {
|
|
3215
|
+
this._nodeIndex.delete(node.id);
|
|
3216
|
+
this._unregisterNodeHandlers(node);
|
|
3217
|
+
for (const child of node.children) {
|
|
3218
|
+
this._removeFromIndex(child);
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
/**
|
|
3222
|
+
* Remove every handler-registry entry owned by a single node. A node owns:
|
|
3223
|
+
* - one entry per event descriptor (its `handlerKey`),
|
|
3224
|
+
* - one `__signal__<signalId>` entry per state ref (signalIds are namespaced
|
|
3225
|
+
* by node id, so they are never shared between nodes), and
|
|
3226
|
+
* - a `__listbuild__<id>` entry if it is a reactive-list.
|
|
3227
|
+
* Called for every node in a detached subtree so removing list items (or
|
|
3228
|
+
* discarding freshly-built-but-unadopted item subtrees) leaves no stale
|
|
3229
|
+
* registrations behind.
|
|
3230
|
+
*/
|
|
3231
|
+
_unregisterNodeHandlers(node) {
|
|
3232
|
+
for (const event of node.events) {
|
|
3233
|
+
this.handlers.delete(event.handlerKey);
|
|
3234
|
+
}
|
|
3235
|
+
for (const ref of node.stateRefs) {
|
|
3236
|
+
this.handlers.delete(`__signal__${ref.signalId}`);
|
|
3237
|
+
}
|
|
3238
|
+
this.handlers.delete(`__listbuild__${node.id}`);
|
|
3239
|
+
this.handlers.delete(`__listplan__${node.id}`);
|
|
3240
|
+
this.handlers.delete(`__overlay__${node.id}`);
|
|
3241
|
+
this.handlers.delete(`__component__${node.id}`);
|
|
3242
|
+
}
|
|
3243
|
+
// ── Handler registry ──────────────────────────────────────────────────────
|
|
3244
|
+
registerHandler(key, fn) {
|
|
3245
|
+
this.handlers.set(key, fn);
|
|
3246
|
+
}
|
|
3247
|
+
getHandler(key) {
|
|
3248
|
+
return this.handlers.get(key);
|
|
3249
|
+
}
|
|
3250
|
+
/** True if a handler is currently registered under `key`. Inspection helper. */
|
|
3251
|
+
hasHandler(key) {
|
|
3252
|
+
return this.handlers.has(key);
|
|
3253
|
+
}
|
|
3254
|
+
/** Number of currently-registered handlers. Inspection helper. */
|
|
3255
|
+
get handlerCount() {
|
|
3256
|
+
return this.handlers.size;
|
|
3257
|
+
}
|
|
3258
|
+
// ── Lookup ────────────────────────────────────────────────────────────────
|
|
3259
|
+
findById(id) {
|
|
3260
|
+
return this._nodeIndex.get(id);
|
|
3261
|
+
}
|
|
3262
|
+
findAll(predicate) {
|
|
3263
|
+
const results = [];
|
|
3264
|
+
this._walk(this.root, (node) => {
|
|
3265
|
+
if (predicate(node)) results.push(node);
|
|
3266
|
+
});
|
|
3267
|
+
return results;
|
|
3268
|
+
}
|
|
3269
|
+
findByType(type) {
|
|
3270
|
+
return this.findAll((n) => n.type === type);
|
|
3271
|
+
}
|
|
3272
|
+
// ── Traversal ─────────────────────────────────────────────────────────────
|
|
3273
|
+
walk(visitor) {
|
|
3274
|
+
this._walk(this.root, visitor, 0);
|
|
3275
|
+
}
|
|
3276
|
+
_walk(node, visitor, depth = 0) {
|
|
3277
|
+
visitor(node, depth);
|
|
3278
|
+
for (const child of node.children) {
|
|
3279
|
+
this._walk(child, visitor, depth + 1);
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
get nodeCount() {
|
|
3283
|
+
return this._nodeIndex.size;
|
|
3284
|
+
}
|
|
3285
|
+
// ── Validation ────────────────────────────────────────────────────────────
|
|
3286
|
+
validate() {
|
|
3287
|
+
const dc = new DiagnosticCollector();
|
|
3288
|
+
this.walk((node) => {
|
|
3289
|
+
for (const event of node.events) {
|
|
3290
|
+
if (!this.handlers.has(event.handlerKey)) {
|
|
3291
|
+
dc.warn(
|
|
3292
|
+
"GRAPH_MISSING_HANDLER",
|
|
3293
|
+
`Node "${node.id}" references handler "${event.handlerKey}" which is not registered`,
|
|
3294
|
+
{ nodeId: node.id }
|
|
3295
|
+
);
|
|
3296
|
+
}
|
|
3297
|
+
}
|
|
3298
|
+
if (node.type === "page" && node.parent?.type !== "application") {
|
|
3299
|
+
dc.error(
|
|
3300
|
+
"GRAPH_PAGE_DEPTH",
|
|
3301
|
+
`Page node "${node.id}" must be a direct child of the application root`,
|
|
3302
|
+
{ nodeId: node.id }
|
|
3303
|
+
);
|
|
3304
|
+
}
|
|
3305
|
+
});
|
|
3306
|
+
return dc;
|
|
3307
|
+
}
|
|
3308
|
+
// ── Serialization ─────────────────────────────────────────────────────────
|
|
3309
|
+
serialize() {
|
|
3310
|
+
return {
|
|
3311
|
+
name: this.name,
|
|
3312
|
+
version: this.version,
|
|
3313
|
+
root: this._serializeNode(this.root)
|
|
3314
|
+
};
|
|
3315
|
+
}
|
|
3316
|
+
_serializeNode(node) {
|
|
3317
|
+
const result = {
|
|
3318
|
+
id: node.id,
|
|
3319
|
+
type: node.type,
|
|
3320
|
+
key: node.key,
|
|
3321
|
+
props: node.props,
|
|
3322
|
+
events: node.events,
|
|
3323
|
+
stateRefs: node.stateRefs,
|
|
3324
|
+
children: node.children.map((c) => this._serializeNode(c))
|
|
3325
|
+
};
|
|
3326
|
+
return result;
|
|
3327
|
+
}
|
|
3328
|
+
};
|
|
3329
|
+
|
|
3330
|
+
// ../dsl/src/dsl.ts
|
|
3331
|
+
var StreetApp = class {
|
|
3332
|
+
_graph;
|
|
3333
|
+
_builder;
|
|
3334
|
+
constructor(options) {
|
|
3335
|
+
const graphOpts = { name: options.name };
|
|
3336
|
+
if (options.version !== void 0) graphOpts.version = options.version;
|
|
3337
|
+
this._graph = new ApplicationGraph(graphOpts);
|
|
3338
|
+
this._builder = new AppBuilder(this._graph);
|
|
3339
|
+
}
|
|
3340
|
+
page(key, builder) {
|
|
3341
|
+
this._builder.page(key, builder);
|
|
3342
|
+
return this;
|
|
3343
|
+
}
|
|
3344
|
+
/** Compile to ApplicationGraph — validates and returns the graph. */
|
|
3345
|
+
build() {
|
|
3346
|
+
const dc = this._graph.validate();
|
|
3347
|
+
dc.throwIfErrors();
|
|
3348
|
+
return this._graph;
|
|
3349
|
+
}
|
|
3350
|
+
/** Access graph before building (useful for inspection). */
|
|
3351
|
+
get graph() {
|
|
3352
|
+
return this._graph;
|
|
3353
|
+
}
|
|
3354
|
+
};
|
|
3355
|
+
var streetui = {
|
|
3356
|
+
app(options) {
|
|
3357
|
+
return new StreetApp(options);
|
|
3358
|
+
}
|
|
3359
|
+
};
|
|
3360
|
+
|
|
3361
|
+
// ../testing/src/component.ts
|
|
3362
|
+
var COMPONENT_ATTR = "data-streetui-component";
|
|
3363
|
+
function renderComponent(def, props, children) {
|
|
3364
|
+
const app = streetui.app({ name: `test:${def.name}` });
|
|
3365
|
+
app.page("host", (page) => {
|
|
3366
|
+
page.component("root", def, props, children);
|
|
3367
|
+
});
|
|
3368
|
+
const result = render(app);
|
|
3369
|
+
const component = result.container.querySelector(`[${COMPONENT_ATTR}]`);
|
|
3370
|
+
if (component === null) {
|
|
3371
|
+
throw new Error(`[StreetUI Testing] renderComponent: no component element rendered for "${def.name}"`);
|
|
3372
|
+
}
|
|
3373
|
+
return { ...result, component };
|
|
3374
|
+
}
|
|
3375
|
+
function hydrateComponent(build, options = {}) {
|
|
3376
|
+
return renderServerThenHydrate(() => {
|
|
3377
|
+
const { def, props, children } = build();
|
|
3378
|
+
const app = streetui.app({ name: `test:${def.name}` });
|
|
3379
|
+
app.page("host", (page) => page.component("root", def, props, children));
|
|
3380
|
+
return app;
|
|
3381
|
+
}, options);
|
|
3382
|
+
}
|
|
3383
|
+
function findAllComponents(container, name) {
|
|
3384
|
+
const selector = name === void 0 ? `[${COMPONENT_ATTR}]` : `[${COMPONENT_ATTR}="${name}"]`;
|
|
3385
|
+
return Array.from(container.querySelectorAll(selector));
|
|
3386
|
+
}
|
|
3387
|
+
function findComponent(container, name) {
|
|
3388
|
+
const matches = findAllComponents(container, name);
|
|
3389
|
+
const named = name !== void 0 ? ` named "${name}"` : "";
|
|
3390
|
+
if (matches.length === 0) {
|
|
3391
|
+
throw new Error(`[StreetUI Testing] No component${named} found`);
|
|
3392
|
+
}
|
|
3393
|
+
if (matches.length > 1) {
|
|
3394
|
+
throw new Error(
|
|
3395
|
+
`[StreetUI Testing] Found ${matches.length} components${named} \u2014 pass a name to disambiguate`
|
|
3396
|
+
);
|
|
3397
|
+
}
|
|
3398
|
+
return matches[0];
|
|
3399
|
+
}
|
|
3400
|
+
function getComponentName(el) {
|
|
3401
|
+
return el.getAttribute(COMPONENT_ATTR);
|
|
3402
|
+
}
|
|
3403
|
+
function trigger(el, type, init = {}) {
|
|
3404
|
+
const base = { bubbles: true, cancelable: true, ...init };
|
|
3405
|
+
let event;
|
|
3406
|
+
if (type.startsWith("key")) {
|
|
3407
|
+
event = new KeyboardEvent(type, base);
|
|
3408
|
+
} else if (type.startsWith("mouse") || type === "click" || type === "dblclick") {
|
|
3409
|
+
event = new MouseEvent(type, base);
|
|
3410
|
+
} else if (type === "input" || type === "change") {
|
|
3411
|
+
event = new Event(type, base);
|
|
3412
|
+
} else {
|
|
3413
|
+
event = new Event(type, base);
|
|
3414
|
+
}
|
|
3415
|
+
el.dispatchEvent(event);
|
|
3416
|
+
}
|
|
3417
|
+
|
|
2338
3418
|
// src/version.ts
|
|
2339
|
-
var VERSION = "1.
|
|
3419
|
+
var VERSION = "1.8.0";
|
|
2340
3420
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2341
3421
|
0 && (module.exports = {
|
|
2342
3422
|
VERSION,
|
|
2343
3423
|
analyzeGraph,
|
|
2344
3424
|
findAllByRole,
|
|
3425
|
+
findAllComponents,
|
|
2345
3426
|
findByRole,
|
|
2346
3427
|
findByText,
|
|
3428
|
+
findComponent,
|
|
2347
3429
|
flushUpdates,
|
|
2348
3430
|
formatInspection,
|
|
3431
|
+
getComponentName,
|
|
3432
|
+
hydrateComponent,
|
|
2349
3433
|
inspectCompilation,
|
|
2350
3434
|
render,
|
|
3435
|
+
renderComponent,
|
|
2351
3436
|
renderOnce,
|
|
2352
3437
|
renderServerThenHydrate,
|
|
3438
|
+
trigger,
|
|
2353
3439
|
waitFor
|
|
2354
3440
|
});
|
|
2355
3441
|
//# sourceMappingURL=testing.cjs.map
|