Reactivity
Client interactivity comes from Datastar. camelon compiles a typed set of JSX props — on:, bind:, signals, text — to Datastar data-* attributes at build time. Set jsxImportSource: "camelon" in your tsconfig to turn it on.
export default function Counter() {
return (
<div class="d-row" signals='{"count": 0}'>
<button class="d-btn" on:click="@post('/counter/decrement')">−</button>
<output class="d-num" text="$count">0</output>
<button class="d-btn" on:click="@post('/counter/increment')">+</button>
</div>
);
}import type { RouteArgs } from 'camelon';
export async function action({ stream, signals }: RouteArgs) {
const { count = 0 } = signals as { count?: number };
stream.patchSignals({ count: count + 1 });
}import type { RouteArgs } from 'camelon';
export async function action({ stream, signals }: RouteArgs) {
const { count = 0 } = signals as { count?: number };
stream.patchSignals({ count: count - 1 });
}The buttons @post to two resource routes. Each reads the current count off the posted signals and patches the new value back over SSE. The increment.ts and decrement.ts tabs are the whole server side.
Props
| You write | Compiles to |
|---|---|
signals='{"count": 0}' |
data-signals='{"count": 0}' |
text="$count" |
data-text="$count" |
bind:name |
data-bind:name (two-way bind input ↔ $name) |
on:click="@post('/x')" |
data-on:click="@post('/x')" |
on:input__debounce-300ms |
data-on:input__debounce.300ms |
Write dotted modifiers with -. Raw data-* props pass through untouched.
bind: keeps an input and a signal in sync:
export default function Greet() {
return (
<div class="d-card" signals='{"name": ""}'>
<input class="d-input" bind:name="" placeholder="your name" />
<p>Hello <span text="$name">friend</span>!</p>
</div>
);
}Server patches
A resource route answers @post/@get over the stream arg. Patch signals or whole elements. Every patched fragment needs a stable id.
export async function action({ stream, signals }) {
const { count = 0 } = signals;
stream.patchSignals({ count: count + 1 });
}
No stream.close() — the stream closes on its own when the action returns.
signals holds the current client state. A Datastar @post also sends it as the JSON body, so one ActionInput type validates a form submit and a Datastar submit.
Datastar 1.0.2:
data-on-loadis inert. Usedata-effectfor run-on-load and long-lived SSE connections.