Writing a function
A function is something an expression can call as @name(...). The built-in requests, @dispatch, @confirm and @clipboard are all functions; so is anything you define with action().
import { action } from 'sigmx'
export const shout = action({ name: 'shout', // @shout('hi') call: (_, text: string) => String(text ?? '').toUpperCase(),})<button data-on:click="$result = @shout($text)">…</button>The context
Section titled “The context”call receives the calling context first, then the arguments the expression passed:
| member | purpose |
|---|---|
el |
the element whose attribute is running the expression |
evt |
the event, when the expression was triggered by one (data-on); otherwise undefined |
store, runtime |
the store and the instance’s services |
error(message) |
build an Error carrying the attribute name and element |
report(error) |
route an error that happened later to onError with the same context |
cleanup(fn) |
run fn when the attribute that invoked the function is torn down; returns a function to unregister it |
Name a function the way you would a JavaScript identifier (@setAll, @toggleAll): the expression compiler turns @name( into a property access, so a kebab-case name would not parse.
Arguments and return values
Section titled “Arguments and return values”Arguments are whatever the expression evaluates: signals, literals, objects, events. Validate what you need and throw error() for a misuse, since it carries the attribute name and element:
call: ({ error }, url: string) => { if (!url) throw error('needs a URL') …}Whatever call returns is the value of the @name() expression, so $result = @shout($text) and @confirm('Sure?') && @delete('/x') both work. A function that returns a promise is awaited by nobody; the expression gets the promise, and the runtime reports its rejection through onError exactly once. Chain from markup with .then():
<button data-on:click="@share({ url: location.href }).then((r) => $status = r)">Share</button>Cancellable work
Section titled “Cancellable work”cleanup(fn) ties a long-running operation to the life of the attribute that started it. The request functions register an abort this way, so removing an element aborts its in-flight request. Unregister with the returned function once the work has finished, or the list of cleanups grows with every call:
call: (ctx, url: string) => { const ac = new AbortController() const forget = ctx.cleanup(() => ac.abort()) return fetch(url, { signal: ac.signal }).finally(forget)}Late errors
Section titled “Late errors”Rejections of the returned promise are reported for you. For callbacks that fire outside that promise (a timer, a third-party callback, an event) use report:
call: ({ report }, name: string) => { window.thirdParty(name, { callback: (r) => { if (r.error) report(r.error) } })}Worked example
Section titled “Worked example”@shout is the smallest possible function. @share is asynchronous: it uses navigator.share where the browser has it, treats a cancelled share sheet as a result rather than an error, and falls back to the clipboard elsewhere. The button chains on the returned promise to show what happened.
<div class="ex fns" data-signals="{ text: 'hello there', result: '' }"> <input class="field" data-bind:text placeholder="Some text"> <div class="row"> <button class="btn" data-on:click="$result = @shout($text)">@shout</button> <button class="btn ghost" data-on:click="@share({ title: 'sigmx', text: $text, url: location.href }).then((r) => $result = r)">@share</button> <span class="chip" data-show="$result" data-text="$result"></span> </div></div><script> import { action } from 'sigmx' const shout = action({ name: 'shout', call: (_, text: string) => String(text ?? '').toUpperCase(), }) const share = action({ name: 'share', call: async ({ error }, data: ShareData) => { if (!data?.url && !data?.text) throw error('needs a url or text') if (navigator.share) { try { await navigator.share(data) return 'shared' } catch (e) { if ((e as Error).name === 'AbortError') return 'cancelled' throw e } } await navigator.clipboard.writeText(data.url ?? data.text ?? '') return 'copied to clipboard' }, }) ;(window as any).sigmx.use(shout, share)</script><style is:global> .fns { padding: 1rem; display: grid; gap: .7rem; background: var(--ex-surface); } .fns .row { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap; } .fns .chip { text-transform: none; letter-spacing: 0; font-size: .8rem; }</style>Registering
Section titled “Registering”createSigmx({ plugins: [...essentials, shout, share] })Under auto mode a function ships when @share( appears in any scanned file. The demo registers its functions at runtime with window.sigmx.use(), which works the same but is invisible to the scanner.