Files
sigpro/docs/api/quick.md
2026-04-27 10:31:58 +02:00

10 KiB
Raw Blame History

SigPro Complete API Reference

SigPro is a RealDOM first reactive microframework. No virtual DOM, no diffing overhead it updates the DOM directly with surgical precision. Builtin automatic cleanup prevents memory leaks, and the API is designed to be both tiny and powerful.

import { $, $$, watch, h, when, each, fx, router, req, mount, batch } from 'sigpro'
// Or, if you prefer the global style in an ESM environment:
// import { sigpro } from 'sigpro'; sigpro();   // then $, h, div... become window globals
// In a classic IIFE script (<script src="sigpro.js">), everything is available globally automatically.

🔁 Core Reactivity

$(value, localStorageKey?) Signal & Computed

Creates a reactive signal. If a function is passed, it becomes a computed signal that caches its result until dependencies change.

Usage Description
const count = $(0) Basic signal, returns a getter/setter: count() reads, count(5) writes.
const double = $( () => count() * 2 ) Computed signal updates automatically when count changes.
const stored = $('hello', 'myKey') Persisted signal reads/writes to localStorage.

Example

const count = $(0)
const double = $( () => count() * 2 )

watch(() => {
  console.log(`count = ${count()}, double = ${double()}`)
}) // logs on every change

count(5) // triggers log: count=5, double=10

$$(object) Deep Reactive Proxy

Makes a plain object (and all nested objects) deeply reactive. Any property access is tracked, any mutation triggers updates.

const state = $$({ user: { name: 'Alice', age: 30 }, items: [1,2,3] })

watch(() => {
  console.log(state.user.name) // tracks `user.name`
})

state.user.name = 'Bob' // triggers the effect

Note

: $$ caches proxies per original object, so calling $$ multiple times on the same object returns the same proxy.


watch(source, callback?) Reactive Effect

Two modes:

  1. Autotrack mode pass a function: watch(() => { /* reads signals */ })
    Automatically reruns whenever any signal read inside changes.

  2. Explicit mode pass an array of signals and a callback:
    watch([count, double], () => { ... })
    Runs the callback when any of the listed signals change. The callback receives the new values.

Both modes return a stop function that disposes the effect.

// auto mode
const stop = watch(() => console.log(count()))

// explicit mode
watch([count, double], ([newCount, newDouble]) => {
  console.log(newCount, newDouble)
})

Important

: Effects are depthaware they run in topological order, parents before children.


🧱 Components & DOM (Hyperscript)

h(tag, props, children) Create DOM Nodes

The universal builder. props can be omitted. Children can be strings, numbers, nodes, arrays, or dynamic functions.

Feature Example
Standard attributes h('div', { class: 'box', id: 'main' })
Events onClick: (e) => ... (automatically cleaned up)
Reactive attributes class: () => count() > 0 ? 'positive' : 'negative'
Twoway binding value: mySignal (works on input, textarea, select)
Refs ref: (el) => ... or ref: { current: null }
SVG support tag names like svg, circle, path sets correct namespace
Dangerous URL sanitising href / src with javascript: or data: are blocked → '#'

Dynamic children pass a function as a child, it will be reexecuted and the DOM patched automatically:

h('div', {}, [
  () => count() > 0 ? h('span', {}, 'positive') : h('span', {}, 'zero or negative')
])

Tag shortcuts

When using the ESM module with named imports, you can import the tag helpers individually:

import { div, h1, button } from 'sigpro'

div({ class: 'container' }, [
  h1({}, 'Title'),
  button({ onClick: () => alert('hi') }, 'Click me')
])

If you prefer the global style (all tags and core functions on window), either:

  • Use the classic IIFE script: <script src="sigpro.js"></script>
  • Or in ESM, call sigpro() after importing: import { sigpro } from 'sigpro'; sigpro();

After that, you can write div(), span(), etc. directly, without any import.

Available tags: a, abbr, article, aside, audio, b, blockquote, br, button, canvas, caption, cite, code, col, colgroup, datalist, dd, del, details, dfn, dialog, div, dl, dt, em, embed, fieldset, figcaption, figure, footer, form, h1h6, header, hr, i, iframe, img, input, ins, kbd, label, legend, li, main, mark, meter, nav, object, ol, optgroup, option, output, p, picture, pre, progress, section, select, slot, small, source, span, strong, sub, summary, sup, svg, table, tbody, td, template, textarea, tfoot, th, thead, time, tr, u, ul, video.


🧩 Flow Control Components

when(condition, thenComponent, elseComponent?)

Reactive conditional rendering. condition can be a boolean, a signal, or any function that returns a boolean. Both branches can be Node, () => Node, or null. Automatically disposes the unmounted branch.

when(
  () => user.loggedIn(),
  () => div({}, 'Welcome back!'),
  () => button({ onClick: () => login() }, 'Login')
)

each(source, itemRenderer, keyFn)

Optimised keyed list rendering. source can be an array or a signal/function returning an array. itemRenderer(item, index) returns a Node (or a function that returns Nodes). keyFn(item, index) returns a unique identifier required for efficient DOM reuse.

const items = $([{ id: 1, text: 'a' }, { id: 2, text: 'b' }])

each(items,
  (item) => Li({}, item.text),
  (item) => item.id
)

When the array changes, elements are added, removed, or reordered with minimal DOM operations.


💥 Batch

batch(fn)

Batch multiple reactive updates into a single flush, improving performance.

batch(() => {
  count(1)
  name('John')
  // effects run only once after the batch ends
})

Animations fx(options, child)

Applies smooth enter animations (CSS transitions / keyframes). Returns the modified element.

fx({ name: 'fade', duration: 300 }, 
  div({}, 'Hello')
)

Options

  • name uses predefined keyframes ${name}-in (you must define them in your CSS)
  • duration in ms (default 200)
  • scale adds scale(0.95)none
  • slide adds translateY(-10px)none
  • rotate adds rotate(-2deg)none
  • blur adds blur(4px)none

If name is given, it sets animation: ${name}-in ${duration}ms. Otherwise it applies a smooth transition from the initial transform/filter to the final state.


🧭 Router router(routes)

Hashbased SPA router. Returns a DOM node that renders the current route.

const routes = [
  { path: '/', component: () => div({}, 'Home') },
  { path: '/user/:id', component: (params) => div({}, `User ${params.id}`) },
  { path: '*', component: () => div({}, '404') }
]

const App = () => div({}, [
  a({ href: '#/' }, 'Home'),
  a({ href: '#/user/42' }, 'User 42'),
  router(routes)
])

API

Method Description
router.params() Returns a reactive signal of current route params (e.g., { id: '42' }).
router.to(path) Navigate to a new hash (e.g., router.to('/user/5')). Prepend # automatically.
router.back() Go back in history.
router.path() Returns current hash path without # (e.g., /user/42).

🌐 HTTP Requests req(config)

Creates a reactive request controller with builtin loading/error/data signals and abort support.

const fetchUser = req({ url: '/api/user/1', method: 'GET' })

// start the request
fetchUser.run().catch(console.error)

// reactively display state
watch(() => {
  if (fetchUser.loading()) console.log('loading...')
  if (fetchUser.error()) console.error(fetchUser.error())
  if (fetchUser.data()) console.log(fetchUser.data())
})

// abort if needed
fetchUser.abort()

Options

  • url (required)
  • method (default 'GET')
  • headers (object, default {})

Return value

  • run(body?) initiates the request, returns a promise.
  • abort() aborts the current request (AbortController).
  • loading signal (boolean)
  • error signal (null or error message)
  • data signal (null or parsed JSON)

Note

: Automatically sets Content-Type: application/json unless body is a FormData. Timeout after 10 seconds aborts the request.


🚀 Mounting mount(component, target)

Clears the target element and mounts the application. Returns the runtime instance (which has a .destroy() method).

mount(() => App(), '#app')
// or
mount(App, document.body)

If you mount again on the same target, the previous instance is automatically destroyed.


🧹 Global Cleanup & Memory

SigPro tracks every effect, DOM event listener, and nested component. When a component is unmounted:

  • All its effects are disposed.
  • All DOM event listeners are removed.
  • All onUnmount callbacks run.
  • Child components are recursively destroyed.

You never need to manually clean up just write reactive code.


📦 Full Example Counter with Persistence

import { $, watch, h, mount } from 'sigpro'

const count = $(0, 'counter') // persists in localStorage

const App = () =>
  div({ class: 'counter' }, [
    h1({}, () => `Count: ${count()}`),
    button({ onClick: () => count(count() + 1) }, '+'),
    button({ onClick: () => count(count() - 1) }, '-'),
    button({ onClick: () => count(0) }, 'Reset')
  ])

mount(App, '#app')

🔧 Customising the API (Renaming)

You can rename everything in one line:

import { $ as signal, watch as effect, h as element, mount as render } from 'sigpro'

Or assign globally (after calling sigpro() or using the classic script):

window.myReactive = $