import { writable, type Readable } from 'svelte/store' export interface StateMachine { states: T currentState: T[number] } export interface StateMachineTransition { from?: Partial>> to?: Partial>> } type StateMachineInfo = { states: StateMachine['states'] currentState: StateMachine['currentState'] } /** The return value should be a `state` that is available on the current machine. */ export type TransitionFromFunction = ( info: StateMachineInfo & { desiredState: T[number] } ) => T[number] /** Callback after the state has been changed. */ export type TransitionToFunction = ( info: StateMachineInfo & { previousState: T[number] } ) => T[number] type StateStore = Readable> & { setState: (state: T[number]) => StateMachineInfo } /** **IMPORTANT:** use the `as const` syntax on the states array to get type safety. * *Example: `createStateMachine(['foo', 'bar'] as const)`* * * Returns a new state machine with the default state set to the first element of the `states` argument. */ export function createStateMachine( states: T, transition: StateMachineTransition = {} ): StateStore { const defaultValue: StateMachine = { states, currentState: states[0] } const defaultStore = writable(defaultValue) const stateStore: StateStore = { subscribe: defaultStore.subscribe, setState: (nextState) => { defaultStore.update((prev) => { const previousState = prev.currentState const beforeFunc = transition?.from && transition.from[previousState] const afterFunc = transition?.to && transition.to[nextState] let returnState = nextState if (beforeFunc) { returnState = beforeFunc({ states, currentState: previousState, desiredState: nextState }) } if (afterFunc) { returnState = afterFunc({ states, currentState: returnState, previousState }) } prev.currentState = returnState return prev }) return { states, currentState: nextState } } } return stateStore }