CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-zag-js--core

A minimal implementation of xstate fsm for UI machines

Overview
Eval results
Files

machine-creation.mddocs/

Machine Creation

Core machine creation and setup utilities for defining finite state machines with typed schemas, guards, and transition logic.

Capabilities

Create Machine

Creates a new finite state machine from configuration with full type safety and schema validation.

/**
 * Creates a new finite state machine from configuration
 * @param config - Machine configuration defining states, transitions, context, and behavior
 * @returns Machine configuration (use with a runtime service to execute)
 */
function createMachine<T extends MachineSchema>(config: Machine<T>): Machine<T>;

interface Machine<T extends MachineSchema> {
  /** Enable debug logging for state transitions */
  debug?: boolean | undefined;
  /** Function that returns props object for the machine */
  props?: ((params: PropsParams<T>) => T["props"]) | undefined;
  /** Function that returns bindable context values */
  context?: ((params: ContextParams<T>) => {
    [K in keyof T["context"]]: Bindable<T["context"][K]>
  }) | undefined;
  /** Computed values derived from context and props */
  computed?: {
    [K in keyof T["computed"]]: (params: ComputedParams<T>) => T["computed"][K]
  } | undefined;
  /** Function that returns the initial state based on props */
  initialState: (params: { prop: PropFn<T> }) => T["state"];
  /** Actions to execute when entering the machine */
  entry?: ActionsOrFn<T> | undefined;
  /** Actions to execute when exiting the machine */
  exit?: ActionsOrFn<T> | undefined;
  /** Effects to run while the machine is active */
  effects?: EffectsOrFn<T> | undefined;
  /** Function that returns refs object for DOM elements */
  refs?: ((params: RefsParams<T>) => T["refs"]) | undefined;
  /** Watch function called on every state change */
  watch?: ((params: Params<T>) => void) | undefined;
  /** Global event handlers (fallback when state doesn't handle event) */
  on?: {
    [E in T["event"]["type"]]?: Transition<T> | Array<Transition<T>>
  } | undefined;
  /** State node definitions */
  states: {
    [K in T["state"]]: {
      /** Tags associated with this state */
      tags?: T["tag"][] | undefined;
      /** Actions to execute when entering this state */
      entry?: ActionsOrFn<T> | undefined;
      /** Actions to execute when exiting this state */
      exit?: ActionsOrFn<T> | undefined;
      /** Effects to run while in this state */
      effects?: EffectsOrFn<T> | undefined;
      /** Event handlers for this state */
      on?: {
        [E in T["event"]["type"]]?: Transition<T> | Array<Transition<T>>
      } | undefined;
    }
  };
  /** Implementation functions for guards, actions, and effects */
  implementations?: {
    guards?: {
      [K in T["guard"]]: (params: Params<T>) => boolean
    } | undefined;
    actions?: {
      [K in T["action"]]: (params: Params<T>) => void
    } | undefined;
    effects?: {
      [K in T["effect"]]: (params: Params<T>) => void | VoidFunction
    } | undefined;
  } | undefined;
}

Usage Examples:

import { createMachine, MachineSchema } from "@zag-js/core";

// Simple toggle machine
interface ToggleMachine extends MachineSchema {
  state: "inactive" | "active";
  event: { type: "TOGGLE" };
}

const toggleMachine = createMachine<ToggleMachine>({
  initialState: () => "inactive",
  states: {
    inactive: {
      on: { TOGGLE: { target: "active" } }
    },
    active: {
      on: { TOGGLE: { target: "inactive" } }
    }
  }
});

// Machine with context and actions
interface CounterMachine extends MachineSchema {
  state: "idle" | "counting";
  context: { count: number; step: number };
  event: { type: "INCREMENT" } | { type: "DECREMENT" } | { type: "SET_STEP"; step: number };
  action: "increment" | "decrement" | "setStep";
}

const counterMachine = createMachine<CounterMachine>({
  initialState: () => "idle",
  context: ({ bindable }) => ({
    count: bindable(() => ({ defaultValue: 0 })),
    step: bindable(() => ({ defaultValue: 1 }))
  }),
  states: {
    idle: {
      on: {
        INCREMENT: { target: "counting", actions: ["increment"] },
        DECREMENT: { target: "counting", actions: ["decrement"] },
        SET_STEP: { actions: ["setStep"] }
      }
    },
    counting: {
      on: {
        INCREMENT: { actions: ["increment"] },
        DECREMENT: { actions: ["decrement"] }
      }
    }
  },
  implementations: {
    actions: {
      increment: ({ context }) => context.set("count", (prev) => prev + context.get("step")),
      decrement: ({ context }) => context.set("count", (prev) => prev - context.get("step")),
      setStep: ({ context, event }) => context.set("step", event.step)
    }
  }
});

Setup Function

Returns setup utilities for machine creation with typed guards and transition choosers.

/**
 * Returns setup utilities for machine creation with typed guards and choosers
 * @returns Object with guards, createMachine, and choose utilities
 */
function setup<T extends MachineSchema>(): {
  guards: GuardUtilities<T>;
  createMachine: (config: Machine<T>) => Machine<T>;
  choose: (transitions: Transition<T> | Transition<T>[]) => (params: Params<T>) => T["action"][] | undefined;
};

interface GuardUtilities<T extends MachineSchema> {
  /** Logical AND combination of guards */
  and(...guards: Array<GuardFn<T> | T["guard"]>): GuardFn<T>;
  /** Logical OR combination of guards */
  or(...guards: Array<GuardFn<T> | T["guard"]>): GuardFn<T>;
  /** Logical NOT negation of guard */
  not(guard: GuardFn<T> | T["guard"]): GuardFn<T>;
}

Usage Examples:

import { setup, MachineSchema } from "@zag-js/core";

interface FormMachine extends MachineSchema {
  state: "idle" | "validating" | "valid" | "invalid";
  context: { email: string; isValid: boolean };
  guard: "isEmailValid" | "isEmailEmpty";
  action: "validate" | "clear";
}

const { guards, createMachine } = setup<FormMachine>();

const formMachine = createMachine({
  initialState: () => "idle",
  states: {
    idle: {
      on: {
        VALIDATE: {
          target: "validating",
          guard: guards.not("isEmailEmpty")
        }
      }
    },
    validating: {
      entry: ["validate"],
      on: {
        VALIDATION_COMPLETE: [
          { target: "valid", guard: "isEmailValid" },
          { target: "invalid", guard: guards.not("isEmailValid") }
        ]
      }
    },
    valid: {
      on: { CLEAR: { target: "idle", actions: ["clear"] } }
    },
    invalid: {
      on: { CLEAR: { target: "idle", actions: ["clear"] } }
    }
  },
  implementations: {
    guards: {
      isEmailValid: ({ context }) => /\S+@\S+\.\S+/.test(context.get("email")),
      isEmailEmpty: ({ context }) => context.get("email").trim() === ""
    },
    actions: {
      validate: ({ context, send }) => {
        // Simulate async validation
        setTimeout(() => send({ type: "VALIDATION_COMPLETE" }), 100);
      },
      clear: ({ context }) => context.set("email", "")
    }
  }
});

Create Guards

Creates logical guard combinations for complex conditional logic in state transitions.

/**
 * Creates logical guard combinations (and, or, not)
 * @returns Object with logical guard combination functions
 */
function createGuards<T extends MachineSchema>(): GuardUtilities<T>;

Usage Examples:

import { createGuards, MachineSchema } from "@zag-js/core";

interface AuthMachine extends MachineSchema {
  state: "idle" | "loading" | "success" | "error";
  context: { user: User | null; attempts: number };
  guard: "isLoggedIn" | "hasMaxAttempts" | "isValidUser";
  action: "login" | "logout" | "incrementAttempts";
}

const guards = createGuards<AuthMachine>();

// Using guard combinations
const loginGuard = guards.and("isValidUser", guards.not("hasMaxAttempts"));
const logoutGuard = guards.or("isLoggedIn", guards.not("isValidUser"));

Supporting Types

interface Transition<T extends MachineSchema> {
  /** Target state to transition to */
  target?: T["state"] | undefined;
  /** Actions to execute during transition */
  actions?: T["action"][] | undefined;
  /** Guard condition that must pass for transition */
  guard?: T["guard"] | GuardFn<T> | undefined;
  /** Allow re-entering the same state */
  reenter?: boolean | undefined;
}

type ActionsOrFn<T extends MachineSchema> = 
  T["action"][] | ((params: Params<T>) => T["action"][] | undefined);

type EffectsOrFn<T extends MachineSchema> = 
  T["effect"][] | ((params: Params<T>) => T["effect"][] | undefined);

type GuardFn<T extends MachineSchema> = (params: Params<T>) => boolean;

interface PropsParams<T extends MachineSchema> {
  props: Partial<T["props"]>;
  scope: Scope;
}

interface ContextParams<T extends MachineSchema> {
  prop: PropFn<T>;
  bindable: BindableFn;
  scope: Scope;
  getContext: () => BindableContext<T>;
  getComputed: () => ComputedFn<T>;
  getRefs: () => BindableRefs<T>;
  getEvent: () => EventType<T["event"]>;
  flush: (fn: VoidFunction) => void;
}

interface ComputedParams<T extends MachineSchema> {
  context: BindableContext<T>;
  event: EventType<T["event"]>;
  prop: PropFn<T>;
  refs: BindableRefs<T>;
  scope: Scope;
  computed: ComputedFn<T>;
}

interface RefsParams<T extends MachineSchema> {
  prop: PropFn<T>;
  context: BindableContext<T>;
}

Install with Tessl CLI

npx tessl i tessl/npm-zag-js--core

docs

index.md

machine-creation.md

type-system.md

utilities.md

tile.json