Core and state
Core concepts
Signals hold state, templates bind to it, and a scope owns every piece of work an element starts. The full contracts are in core-contracts.md.
Signals
A signal holds a value; computed() derives one. Reads and writes are synchronous, and batch() groups writes so observers see one change.
import { batch, computed, signal } from '@oddkin/core/reactive';
const quantity = signal(1);
const price = signal(12);
const total = computed(() => quantity.value * price.value);
batch(() => {
quantity.value = 2;
price.value = 15;
});
console.log(total.value); // 30The binding rule
- Render-tracked reads. Reading
.valuewhilerender()runs makes it a dependency: a change renders again. - Direct readable bindings. Passing the signal itself,
${this.count}, binds that one part: a change updates it without runningrender(). - Untracked snapshots. Event handlers and asynchronous callbacks run later, outside the render: a
.valueread there is a snapshot and subscribes to nothing.
import { OddkinElement, define, html, signal } from '@oddkin/core';
class DemoCounter extends OddkinElement {
private count = signal(0);
private increment = () => this.count.update((value) => value + 1);
override render() {
// The text binding follows count; incrementing does not re-run render().
return html`<button type="button" @click=${this.increment}>Count: ${this.count}</button>`;
}
}
define('demo-counter', DemoCounter);The settled wording, with what a template does with each kind of value, is the binding rule in core-contracts.md §1 and its binding-value matrix (§6.1).
Ownership with Scope
Each connection gives the element a Scope. Listeners, timers, effects, requests, and controllers registered on it end when the element disconnects, and start again from connected(scope) when it reconnects. There is no separate cleanup method to forget.