Both bootstrappers now handle the full signal runtime: - &rest lambda params → JS arguments.slice / Python *args - Signal/Island/TrackingContext platform functions in both hosts - RENAMES for all signal, island, tracking, and reactive DOM identifiers - signals auto-included with DOM adapter (JS) and HTML adapter (Python) - Signal API exports on Sx object (signal, deref, reset, swap, computed, effect, batch) - New DOM primitives: createComment, domRemove, domChildNodes, domRemoveChildrenAfter, domSetData - jsonSerialize/isEmptyDict for island state serialization - Demo HTML page exercising all signal primitives Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
183 lines
6.8 KiB
HTML
183 lines
6.8 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>SX Reactive Islands Demo</title>
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body { font-family: system-ui, sans-serif; max-width: 640px; margin: 40px auto; padding: 0 20px; color: #1a1a2e; background: #f8f8fc; }
|
|
h1 { margin-bottom: 8px; font-size: 1.5rem; }
|
|
.subtitle { color: #666; margin-bottom: 32px; font-size: 0.9rem; }
|
|
.demo { background: white; border: 1px solid #e2e2ea; border-radius: 8px; padding: 20px; margin-bottom: 20px; }
|
|
.demo h2 { font-size: 1.1rem; margin-bottom: 12px; color: #2d2d4e; }
|
|
.demo-row { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
|
|
button { background: #4a3f8a; color: white; border: none; border-radius: 4px; padding: 6px 16px; cursor: pointer; font-size: 0.9rem; }
|
|
button:hover { background: #5b4fa0; }
|
|
button:active { background: #3a2f7a; }
|
|
.value { font-size: 1.4rem; font-weight: 600; min-width: 3ch; text-align: center; }
|
|
.derived { color: #666; font-size: 0.85rem; }
|
|
.effect-log { background: #f0f0f8; border-radius: 4px; padding: 8px 12px; font-family: monospace; font-size: 0.8rem; max-height: 120px; overflow-y: auto; white-space: pre-wrap; }
|
|
.batch-indicator { display: inline-block; background: #e8f5e9; color: #2e7d32; padding: 2px 8px; border-radius: 3px; font-size: 0.8rem; }
|
|
code { background: #f0f0f8; padding: 2px 6px; border-radius: 3px; font-size: 0.85rem; }
|
|
.note { color: #888; font-size: 0.8rem; margin-top: 8px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>SX Reactive Islands</h1>
|
|
<p class="subtitle">Signals transpiled from <code>signals.sx</code> spec via <code>bootstrap_js.py</code></p>
|
|
|
|
<!-- Demo 1: Basic signal -->
|
|
<div class="demo" id="demo-counter">
|
|
<h2>1. Signal: Counter</h2>
|
|
<div class="demo-row">
|
|
<button onclick="decr()">-</button>
|
|
<span class="value" id="count-display">0</span>
|
|
<button onclick="incr()">+</button>
|
|
</div>
|
|
<div class="derived" id="doubled-display"></div>
|
|
<p class="note"><code>signal</code> + <code>computed</code> + <code>effect</code></p>
|
|
</div>
|
|
|
|
<!-- Demo 2: Batch -->
|
|
<div class="demo" id="demo-batch">
|
|
<h2>2. Batch: Two signals, one notification</h2>
|
|
<div class="demo-row">
|
|
<span>first: <strong id="first-display">0</strong></span>
|
|
<span>second: <strong id="second-display">0</strong></span>
|
|
<span class="batch-indicator" id="render-count"></span>
|
|
</div>
|
|
<div class="demo-row">
|
|
<button onclick="batchBoth()">Batch increment both</button>
|
|
<button onclick="noBatchBoth()">No-batch increment both</button>
|
|
</div>
|
|
<p class="note"><code>batch</code> coalesces writes: 2 updates, 1 re-render</p>
|
|
</div>
|
|
|
|
<!-- Demo 3: Effect with cleanup -->
|
|
<div class="demo" id="demo-effect">
|
|
<h2>3. Effect: Auto-tracking + Cleanup</h2>
|
|
<div class="demo-row">
|
|
<button onclick="togglePolling()">Toggle polling</button>
|
|
<span id="poll-status"></span>
|
|
</div>
|
|
<div class="effect-log" id="effect-log"></div>
|
|
<p class="note"><code>effect</code> returns cleanup fn; dispose stops tracking</p>
|
|
</div>
|
|
|
|
<!-- Demo 4: Computed chains -->
|
|
<div class="demo" id="demo-chain">
|
|
<h2>4. Computed chain: base → doubled → quadrupled</h2>
|
|
<div class="demo-row">
|
|
<button onclick="chainDecr()">-</button>
|
|
<span>base: <strong id="chain-base">1</strong></span>
|
|
<button onclick="chainIncr()">+</button>
|
|
</div>
|
|
<div class="derived">
|
|
doubled: <strong id="chain-doubled"></strong>
|
|
quadrupled: <strong id="chain-quad"></strong>
|
|
</div>
|
|
<p class="note">Three-level computed dependency graph, auto-propagation</p>
|
|
</div>
|
|
|
|
<script src="sx-ref.js"></script>
|
|
<script>
|
|
// Grab signal primitives from transpiled runtime
|
|
var S = window.Sx;
|
|
var signal = S.signal;
|
|
var deref = S.deref;
|
|
var reset = S.reset;
|
|
var swap = S.swap;
|
|
var computed = S.computed;
|
|
var effect = S.effect;
|
|
var batch = S.batch;
|
|
|
|
// ---- Demo 1: Counter ----
|
|
var count = signal(0);
|
|
var doubled = computed(function() { return deref(count) * 2; });
|
|
|
|
effect(function() {
|
|
document.getElementById("count-display").textContent = deref(count);
|
|
});
|
|
effect(function() {
|
|
document.getElementById("doubled-display").textContent = "doubled: " + deref(doubled);
|
|
});
|
|
|
|
function incr() { swap(count, function(n) { return n + 1; }); }
|
|
function decr() { swap(count, function(n) { return n - 1; }); }
|
|
|
|
// ---- Demo 2: Batch ----
|
|
var first = signal(0);
|
|
var second = signal(0);
|
|
var renders = signal(0);
|
|
|
|
effect(function() {
|
|
document.getElementById("first-display").textContent = deref(first);
|
|
document.getElementById("second-display").textContent = deref(second);
|
|
swap(renders, function(n) { return n + 1; });
|
|
});
|
|
effect(function() {
|
|
document.getElementById("render-count").textContent = "renders: " + deref(renders);
|
|
});
|
|
|
|
function batchBoth() {
|
|
batch(function() {
|
|
swap(first, function(n) { return n + 1; });
|
|
swap(second, function(n) { return n + 1; });
|
|
});
|
|
}
|
|
function noBatchBoth() {
|
|
swap(first, function(n) { return n + 1; });
|
|
swap(second, function(n) { return n + 1; });
|
|
}
|
|
|
|
// ---- Demo 3: Effect with cleanup ----
|
|
var polling = signal(false);
|
|
var pollDispose = null;
|
|
var logEl = document.getElementById("effect-log");
|
|
|
|
function log(msg) {
|
|
logEl.textContent += msg + "\n";
|
|
logEl.scrollTop = logEl.scrollHeight;
|
|
}
|
|
|
|
effect(function() {
|
|
var active = deref(polling);
|
|
document.getElementById("poll-status").textContent = active ? "polling..." : "stopped";
|
|
if (active) {
|
|
var n = 0;
|
|
var id = setInterval(function() {
|
|
n++;
|
|
log("poll #" + n);
|
|
}, 500);
|
|
log("effect: started interval");
|
|
// Return cleanup function
|
|
return function() {
|
|
clearInterval(id);
|
|
log("cleanup: cleared interval");
|
|
};
|
|
}
|
|
});
|
|
|
|
function togglePolling() { swap(polling, function(v) { return !v; }); }
|
|
|
|
// ---- Demo 4: Computed chain ----
|
|
var base = signal(1);
|
|
var chainDoubled = computed(function() { return deref(base) * 2; });
|
|
var quadrupled = computed(function() { return deref(chainDoubled) * 2; });
|
|
|
|
effect(function() {
|
|
document.getElementById("chain-base").textContent = deref(base);
|
|
});
|
|
effect(function() {
|
|
document.getElementById("chain-doubled").textContent = deref(chainDoubled);
|
|
});
|
|
effect(function() {
|
|
document.getElementById("chain-quad").textContent = deref(quadrupled);
|
|
});
|
|
|
|
function chainIncr() { swap(base, function(n) { return n + 1; }); }
|
|
function chainDecr() { swap(base, function(n) { return n - 1; }); }
|
|
</script>
|
|
</body>
|
|
</html>
|