Skip to content

Utilities

Builds a CSS class string from mixed input types.

function classes(...args: (string | string[] | Record<string, boolean>)[]): string
TypeBehavior
stringSplit by spaces, validated, and appended
string[]Each string validated and appended
objectKeys with truthy values are validated and appended. Values can be booleans or functions returning booleans.
  • Validates CSS class names (alphanumeric, hyphens, underscores)
  • Deduplicates class names
  • Throws on invalid class names
import { classes } from 'sygnal'
classes('btn', 'primary')
// → 'btn primary'
classes('btn', { active: true, disabled: false })
// → 'btn active'
classes(['card', 'shadow'], { highlighted: isHighlighted })
// → 'card shadow highlighted' (when isHighlighted is truthy)
classes({ visible: () => checkVisibility() })
// → 'visible' (when checkVisibility() returns truthy)

Creates a typed state assertion function for TypeScript. Ensures state objects match the expected type exactly, with no extra properties.

function exactState<STATE>(): <ACTUAL extends STATE>(state: ExactShape<STATE, ACTUAL>) => STATE
import { exactState } from 'sygnal'
type AppState = { count: number; name: string }
const asAppState = exactState<AppState>()
App.model = {
UPDATE: (state) => asAppState({ count: 1, name: 'test' })
// TypeScript error: asAppState({ count: 1, name: 'test', extra: true })
}

Alternative HMR setup function (wraps the manual import.meta.hot / module.hot pattern).

function enableHMR(
app: SygnalApp,
hot: HotModuleAPI,
loadComponent?: () => Promise<AnyComponentModule> | AnyComponentModule,
acceptDependencies?: string | string[]
): SygnalApp
ParameterTypeDescription
appSygnalAppThe return value from run()
hotHotModuleAPIimport.meta.hot (Vite) or module.hot (Webpack)
loadComponentFunctionOptional function to load the updated component
acceptDependenciesstring | string[]Module paths to watch for changes

Builds a STATE reducer that merges a partial update into the state.

function set<S>(partial: Partial<S> | ((state: S, data: any, next: Function, props: any) => Partial<S>)): Reducer
import { set } from 'sygnal'
Panel.model = {
OPEN: set({ isOpen: true }), // static partial
RENAME: set((state, title) => ({ title })), // computed partial
}

Builds a STATE reducer that flips a boolean field.

import { toggle } from 'sygnal'
Panel.model = {
TOGGLE_HELP: toggle('showHelp'),
}

Whether an id is in a selection behavior’s slice. Ids compare as strings, so 1 and '1' agree; a missing slice selects nothing.

function isSelected(slice: { selected: string[] } | null | undefined, id: string | number): boolean

In a view: checked={isSelected(state.sel, mail.id)}. In a reducer: state.mails.filter(mail => !isSelected(state.sel, mail.id)).


Wraps a model so each change to state[key] is recorded, and adds UNDO and REDO actions. The undo behavior does the same through uses; see Undo and Redo for both.

function undoable(model: Model, options: { key: string; limit?: number; track?: string[]; coalesceMs?: number; coalesce?: string[]; resetOn?: string[] }): Model

The history is state.history = { past, future }. Options: key (required), limit (100), track (only these actions), coalesceMs (one step for quick changes by one action), coalesce (only these actions join a step), resetOn (actions that clear the history).


A special constant that, when returned from a state reducer, cancels the state update for that action. Returned from a non-STATE sink reducer (EVENTS, PARENT, a custom driver), it sends nothing.

const ABORT: unique symbol
import { ABORT } from 'sygnal'
MyComponent.model = {
MOVE: (state, data) => {
if (state.locked) return ABORT // No state change
return { ...state, position: data }
}
}