@zag-js/core
Version:
A minimal implementation of xstate fsm for UI machines
202 lines (153 loc) • 5.35 kB
Markdown
# @zag-js/core
This package contains a minimal implementation of [XState FSM](https://github.com/statelyai/xstate) for **finite state
machines** with addition of extra features we need for our components.
## Features
- Finite states with optional nested (hierarchical) states
- Initial state
- Transitions (object or strings)
- Context
- Entry actions
- Exit actions
- Transition actions
- Effects
- Boolean guard helpers (`and`, `or`, `not`)
> Note: The core package is intentionally minimal. It doesn't include delayed transitions, interval scheduling, spawn
> helpers, or activities. Use actions/effects to perform side effects.
> To better understand the state machines, we strongly recommend going though the
> [xstate docs](https://xstate.js.org/docs/) and videos. It'll give you the foundations you need.
## Quick start
**Installation**
```bash
npm i @zag-js/core
# or
yarn add @zag-js/core
```
**Usage (machine):**
```js
import { createMachine } from "@zag-js/core"
const toggleMachine = createMachine({
id: "toggle",
initialState() {
return "inactive"
},
states: {
inactive: { on: { TOGGLE: "active" } },
active: { on: { TOGGLE: "inactive" } },
},
})
```
**Usage (service via adapter):**
```js
import { createMachine } from "@zag-js/core"
import { VanillaMachine } from "@zag-js/vanilla"
const machine = createMachine({
initialState() {
return "inactive"
},
states: {
inactive: { on: { TOGGLE: "active" } },
active: { on: { TOGGLE: "inactive" } },
},
})
const service = new VanillaMachine(machine)
service.start()
service.subscribe(() => {
console.log(service.state.get())
})
service.send({ type: "TOGGLE" })
service.send({ type: "TOGGLE" })
service.stop()
```
### Nested states (dot notation)
```ts
import { createMachine } from "@zag-js/core"
const machine = createMachine({
initialState() {
return "dialog"
},
states: {
dialog: {
tags: ["overlay"],
initial: "closed",
states: {
closed: { on: { OPEN: { target: "dialog.open" } } },
open: { on: { CLOSE: { target: "dialog.closed" } } },
},
// parent-level transitions still work
on: { RESET: { target: "dialog.closed" } },
},
},
})
// service.state.matches("dialog.open") === true when nested state is active
```
### State IDs and `#id` targets
Use `id` on a state node when you want to target it explicitly from anywhere in the machine.
```ts
import { createMachine } from "@zag-js/core"
const machine = createMachine({
initialState() {
return "dialog"
},
states: {
dialog: {
initial: "open",
states: {
focused: {
id: "dialogFocused",
},
open: {
initial: "idle",
states: {
idle: {
on: {
CLOSE: { target: "#dialogFocused" },
},
},
},
},
},
},
},
})
```
Notes:
- `#id` targets resolve by state node `id` (XState-style).
- State IDs must be unique within a machine.
## API
### `createMachine(config, options)`
Creates a new finite state machine from the config.
| Argument | Type | Description |
| --------- | ------------------ | ------------------------------------------- |
| `config` | object (see below) | The config object for creating the machine. |
| `options` | object (see below) | The optional options object. |
**Returns:**
A machine definition object consumed by framework adapters (React, Solid, Svelte, Vue, Preact, Vanilla) to create a
runtime service.
The machine config has this schema:
### Machine config
- `initialState` (function) - returns the machine's initial state value.
- `states` (object) - mapping of state names to state configs. Supports nested `states` and `initial` for child states.
- `on` (object) - optional global transitions available from any state.
- `context` (function) - returns bindable context fields.
- `computed` (object) - derived values based on context, props, refs.
- `entry` / `exit` / `effects` - root-level actions/effects.
- `implementations` (object) - lookup tables for `actions`, `guards`, `effects`.
### State config
- `on` (object) - an object mapping event types (keys) to [transitions](#transition-config)
### Transition config
String syntax:
- (string) - the state name to transition to.
- Same as `{ target: stateName }`
Object syntax:
- `target` (string) - the state name to transition to.
- `actions` (Action | Action[]) - the [action(s)](#action-config) to execute when this transition is taken.
- `guard` (Guard) - the condition (predicate function) to test. If it returns `true`, the transition will be taken.
### Machine options
- `actions?` (object) - a lookup object for your string actions.
- `guards?` (object) - a lookup object for your string guards specified as `guard` in the machine.
- `activities?` (object) - a lookup object for your string activities.
- `delays?` (object) - a lookup object for your string delays used in `after` and `every` config.
### Action config
The action function to execute while transitioning from one state to another. It takes the following arguments:
- `context` (any) - the machine's current `context`.
- `event` (object) - the event that caused the action to be executed.