controlled-machine 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"react.cjs","sources":["../src/react.ts"],"sourcesContent":["/**\n * Controlled Machine - React Integration\n *\n * React hook for using controlled-machine in React components.\n */\n\nimport { useCallback, useRef, useEffect, useMemo } from 'react'\nimport {\n type MachineTypes,\n type Machine,\n type Events,\n type Computed,\n type State,\n type Send,\n type EffectHelpers,\n type EffectStore,\n type Context,\n computeValues,\n executeActions,\n executeHandler,\n processEffects,\n clearEffectStore,\n} from './index'\n\n// ============================================\n// React Hook\n// ============================================\n\nexport function useMachine<T extends MachineTypes>(\n machine: Machine<T>,\n input: T['input'],\n): { send: Send<Events<T>>; computed: Computed<T>; state: State<T> } {\n // refs for stable callbacks\n const inputRef = useRef(input)\n const machineRef = useRef(machine)\n const isMountedRef = useRef(true)\n\n inputRef.current = input\n machineRef.current = machine\n\n // compute values\n const { computed: computedDef } = machine\n const context = useMemo(\n () => computeValues(input, computedDef),\n [input, computedDef],\n )\n\n // extract computed only\n const computed = useMemo(() => {\n if (!computedDef) return {} as Computed<T>\n const result = {} as Computed<T>\n for (const key in computedDef) {\n result[key] = context[key]\n }\n return result\n }, [context, computedDef])\n\n const contextRef = useRef(context)\n contextRef.current = context\n\n const prevContextRef = useRef<Context<T>>(context)\n const effectStoreRef = useRef<EffectStore>({\n watchedValues: new Map(),\n enterCleanups: new Map(),\n changeCleanups: new Map(),\n exitCleanups: new Map(),\n })\n\n // always: auto-evaluate when context changes (synchronous, during render)\n const { always, actions } = machine\n if (prevContextRef.current !== context && always && actions) {\n const actionsMap = actions as Record<string, (context: Context<T>) => void>\n for (const rule of always) {\n if (!rule.when || rule.when(context, undefined)) {\n executeActions(rule.do, actionsMap, context, undefined)\n break\n }\n }\n }\n prevContextRef.current = context\n\n // send: stable function (no deps, uses refs)\n const send: Send<Events<T>> = useCallback(\n <K extends keyof Events<T>>(\n event: K,\n ...args: Events<T>[K] extends undefined ? [] : [payload: Events<T>[K]]\n ) => {\n const currentMachine = machineRef.current\n const currentInput = inputRef.current\n const currentContext = computeValues(\n currentInput,\n currentMachine.computed,\n )\n const payload = args[0] as Events<T>[K]\n\n // 1. State-specific handler first\n const state = (currentContext as { state?: State<T> }).state\n if (state && currentMachine.states?.[state]?.on?.[event]) {\n const stateHandler = currentMachine.states[state].on![event]!\n executeHandler(\n stateHandler,\n currentMachine.actions ?? {},\n currentContext,\n payload,\n )\n }\n\n // 2. Global handler\n const globalHandler = currentMachine.on?.[event]\n if (globalHandler) {\n executeHandler(\n globalHandler,\n currentMachine.actions ?? {},\n currentContext,\n payload,\n )\n }\n },\n [], // no dependencies - uses refs\n )\n\n // safeSend: won't be called after unmount\n const safeSend: Send<Events<T>> = useCallback(\n <K extends keyof Events<T>>(\n event: K,\n ...args: Events<T>[K] extends undefined ? [] : [payload: Events<T>[K]]\n ) => {\n if (!isMountedRef.current) return\n send(event, ...args)\n },\n [send],\n )\n\n // effect helpers\n const effectHelpers: EffectHelpers<Events<T>> = useMemo(\n () => ({ send: safeSend }),\n [safeSend],\n )\n\n // effects: detect watch value changes\n const { effects } = machine\n useEffect(() => {\n processEffects(effects, context, effectHelpers, effectStoreRef.current)\n }, [context, effects, effectHelpers])\n\n // mount/unmount management\n useEffect(() => {\n isMountedRef.current = true\n const store = effectStoreRef.current\n return () => {\n isMountedRef.current = false\n clearEffectStore(store)\n }\n }, [])\n\n // state from context (default to empty string if not provided)\n const state = (context as { state?: State<T> }).state ?? ('' as State<T>)\n\n return { send, computed, state }\n}\n\n// Re-export types for convenience\nexport type { Send } from './index'\n"],"names":["useRef","useMemo","computeValues","executeActions","useCallback","state","executeHandler","useEffect","processEffects","clearEffectStore"],"mappings":"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"}
1
+ {"version":3,"file":"react.cjs","sources":["../src/react.ts"],"sourcesContent":["/**\n * Controlled Machine - React Integration\n *\n * React hook for using controlled-machine in React components.\n */\n\nimport { useCallback, useRef, useEffect, useMemo } from 'react'\nimport {\n type MachineTypes,\n type Machine,\n type Events,\n type Computed,\n type State,\n type Send,\n type EffectHelpers,\n type EffectStore,\n type Context,\n computeValues,\n executeActions,\n executeHandler,\n processEffects,\n clearEffectStore,\n MachineInstance,\n} from './index'\n\n// ============================================\n// React Hook\n// ============================================\n\nexport function useMachine<T extends MachineTypes>(\n machine: MachineInstance<T>,\n input: T['input'],\n): { send: Send<Events<T>>; computed: Computed<T>; state: State<T> } {\n // refs for stable callbacks\n const inputRef = useRef(input)\n const machineRef = useRef(machine)\n const isMountedRef = useRef(true)\n\n inputRef.current = input\n machineRef.current = machine\n\n // compute values\n const { computed: computedDef } = machine\n const context = useMemo(\n () => computeValues(input, computedDef),\n [input, computedDef],\n )\n\n // extract computed only\n const computed = useMemo(() => {\n if (!computedDef) return {} as Computed<T>\n const result = {} as Computed<T>\n for (const key in computedDef) {\n result[key] = context[key]\n }\n return result\n }, [context, computedDef])\n\n const contextRef = useRef(context)\n contextRef.current = context\n\n const prevContextRef = useRef<Context<T>>(context)\n const effectStoreRef = useRef<EffectStore>({\n watchedValues: new Map(),\n enterCleanups: new Map(),\n changeCleanups: new Map(),\n exitCleanups: new Map(),\n })\n\n // always: auto-evaluate when context changes (synchronous, during render)\n const { always, actions } = machine\n if (prevContextRef.current !== context && always && actions) {\n const actionsMap = actions as Record<string, (context: Context<T>) => void>\n for (const rule of always) {\n if (!rule.when || rule.when(context, undefined)) {\n executeActions(rule.do, actionsMap, context, undefined)\n break\n }\n }\n }\n prevContextRef.current = context\n\n // send: stable function (no deps, uses refs)\n const send: Send<Events<T>> = useCallback(\n <K extends keyof Events<T>>(\n event: K,\n ...args: Events<T>[K] extends undefined ? [] : [payload: Events<T>[K]]\n ) => {\n const currentMachine = machineRef.current\n const currentInput = inputRef.current\n const currentContext = computeValues(\n currentInput,\n currentMachine.computed,\n )\n const payload = args[0] as Events<T>[K]\n\n // 1. State-specific handler first\n const state = (currentContext as { state?: State<T> }).state\n if (state && currentMachine.states?.[state]?.on?.[event]) {\n const stateHandler = currentMachine.states[state].on![event]!\n executeHandler(\n stateHandler,\n currentMachine.actions ?? {},\n currentContext,\n payload,\n )\n }\n\n // 2. Global handler\n const globalHandler = currentMachine.on?.[event]\n if (globalHandler) {\n executeHandler(\n globalHandler,\n currentMachine.actions ?? {},\n currentContext,\n payload,\n )\n }\n },\n [], // no dependencies - uses refs\n )\n\n // safeSend: won't be called after unmount\n const safeSend: Send<Events<T>> = useCallback(\n <K extends keyof Events<T>>(\n event: K,\n ...args: Events<T>[K] extends undefined ? [] : [payload: Events<T>[K]]\n ) => {\n if (!isMountedRef.current) return\n send(event, ...args)\n },\n [send],\n )\n\n // effect helpers\n const effectHelpers: EffectHelpers<Events<T>> = useMemo(\n () => ({ send: safeSend }),\n [safeSend],\n )\n\n // effects: detect watch value changes\n const { effects } = machine\n useEffect(() => {\n processEffects(effects, context, effectHelpers, effectStoreRef.current)\n }, [context, effects, effectHelpers])\n\n // mount/unmount management\n useEffect(() => {\n isMountedRef.current = true\n const store = effectStoreRef.current\n return () => {\n isMountedRef.current = false\n clearEffectStore(store)\n }\n }, [])\n\n // state from context (default to empty string if not provided)\n const state = (context as { state?: State<T> }).state ?? ('' as State<T>)\n\n return { send, computed, state }\n}\n\n// Re-export types for convenience\nexport type { Send } from './index'\n"],"names":["useRef","useMemo","computeValues","executeActions","useCallback","state","executeHandler","useEffect","processEffects","clearEffectStore"],"mappings":";;;;AA6BO,SAAS,WACd,SACA,OACmE;AAEnE,QAAM,WAAWA,MAAAA,OAAO,KAAK;AAC7B,QAAM,aAAaA,MAAAA,OAAO,OAAO;AACjC,QAAM,eAAeA,MAAAA,OAAO,IAAI;AAEhC,WAAS,UAAU;AACnB,aAAW,UAAU;AAGrB,QAAM,EAAE,UAAU,YAAA,IAAgB;AAClC,QAAM,UAAUC,MAAAA;AAAAA,IACd,MAAMC,MAAAA,cAAc,OAAO,WAAW;AAAA,IACtC,CAAC,OAAO,WAAW;AAAA,EAAA;AAIrB,QAAM,WAAWD,MAAAA,QAAQ,MAAM;AAC7B,QAAI,CAAC,YAAa,QAAO,CAAA;AACzB,UAAM,SAAS,CAAA;AACf,eAAW,OAAO,aAAa;AAC7B,aAAO,GAAG,IAAI,QAAQ,GAAG;AAAA,IAC3B;AACA,WAAO;AAAA,EACT,GAAG,CAAC,SAAS,WAAW,CAAC;AAEzB,QAAM,aAAaD,MAAAA,OAAO,OAAO;AACjC,aAAW,UAAU;AAErB,QAAM,iBAAiBA,MAAAA,OAAmB,OAAO;AACjD,QAAM,iBAAiBA,MAAAA,OAAoB;AAAA,IACzC,mCAAmB,IAAA;AAAA,IACnB,mCAAmB,IAAA;AAAA,IACnB,oCAAoB,IAAA;AAAA,IACpB,kCAAkB,IAAA;AAAA,EAAI,CACvB;AAGD,QAAM,EAAE,QAAQ,QAAA,IAAY;AAC5B,MAAI,eAAe,YAAY,WAAW,UAAU,SAAS;AAC3D,UAAM,aAAa;AACnB,eAAW,QAAQ,QAAQ;AACzB,UAAI,CAAC,KAAK,QAAQ,KAAK,KAAK,SAAS,MAAS,GAAG;AAC/CG,cAAAA,eAAe,KAAK,IAAI,YAAY,SAAS,MAAS;AACtD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,iBAAe,UAAU;AAGzB,QAAM,OAAwBC,MAAAA;AAAAA,IAC5B,CACE,UACG,SACA;AACH,YAAM,iBAAiB,WAAW;AAClC,YAAM,eAAe,SAAS;AAC9B,YAAM,iBAAiBF,MAAAA;AAAAA,QACrB;AAAA,QACA,eAAe;AAAA,MAAA;AAEjB,YAAM,UAAU,KAAK,CAAC;AAGtB,YAAMG,SAAS,eAAwC;AACvD,UAAIA,UAAS,eAAe,SAASA,MAAK,GAAG,KAAK,KAAK,GAAG;AACxD,cAAM,eAAe,eAAe,OAAOA,MAAK,EAAE,GAAI,KAAK;AAC3DC,cAAAA;AAAAA,UACE;AAAA,UACA,eAAe,WAAW,CAAA;AAAA,UAC1B;AAAA,UACA;AAAA,QAAA;AAAA,MAEJ;AAGA,YAAM,gBAAgB,eAAe,KAAK,KAAK;AAC/C,UAAI,eAAe;AACjBA,cAAAA;AAAAA,UACE;AAAA,UACA,eAAe,WAAW,CAAA;AAAA,UAC1B;AAAA,UACA;AAAA,QAAA;AAAA,MAEJ;AAAA,IACF;AAAA,IACA,CAAA;AAAA;AAAA,EAAC;AAIH,QAAM,WAA4BF,MAAAA;AAAAA,IAChC,CACE,UACG,SACA;AACH,UAAI,CAAC,aAAa,QAAS;AAC3B,WAAK,OAAO,GAAG,IAAI;AAAA,IACrB;AAAA,IACA,CAAC,IAAI;AAAA,EAAA;AAIP,QAAM,gBAA0CH,MAAAA;AAAAA,IAC9C,OAAO,EAAE,MAAM;IACf,CAAC,QAAQ;AAAA,EAAA;AAIX,QAAM,EAAE,YAAY;AACpBM,QAAAA,UAAU,MAAM;AACdC,UAAAA,eAAe,SAAS,SAAS,eAAe,eAAe,OAAO;AAAA,EACxE,GAAG,CAAC,SAAS,SAAS,aAAa,CAAC;AAGpCD,QAAAA,UAAU,MAAM;AACd,iBAAa,UAAU;AACvB,UAAM,QAAQ,eAAe;AAC7B,WAAO,MAAM;AACX,mBAAa,UAAU;AACvBE,YAAAA,iBAAiB,KAAK;AAAA,IACxB;AAAA,EACF,GAAG,CAAA,CAAE;AAGL,QAAM,QAAS,QAAiC,SAAU;AAE1D,SAAO,EAAE,MAAM,UAAU,MAAA;AAC3B;;"}
package/dist/react.d.cts CHANGED
@@ -42,6 +42,13 @@ declare type Machine<T extends MachineTypes> = {
42
42
  };
43
43
  };
44
44
 
45
+ declare type MachineInstance<T extends MachineTypes> = Machine<T> & {
46
+ send: <K extends keyof Events<T>>(event: K, input: Input<T>, ...args: Events<T>[K] extends undefined ? [] : [payload: Events<T>[K]]) => void;
47
+ evaluate: (input: Input<T>) => void;
48
+ getComputed: (input: Input<T>) => Computed<T>;
49
+ cleanup: () => void;
50
+ };
51
+
45
52
  /**
46
53
  * Object-based generic types - specify only needed types in any order
47
54
  *
@@ -91,7 +98,7 @@ declare type StatesConfig<TState extends string, TContext, TEvents extends Event
91
98
  [K in TState]?: StateConfig<TContext, TEvents, TActions>;
92
99
  };
93
100
 
94
- export declare function useMachine<T extends MachineTypes>(machine: Machine<T>, input: T['input']): {
101
+ export declare function useMachine<T extends MachineTypes>(machine: MachineInstance<T>, input: T['input']): {
95
102
  send: Send<Events<T>>;
96
103
  computed: Computed<T>;
97
104
  state: State<T>;
package/dist/react.d.ts CHANGED
@@ -42,6 +42,13 @@ declare type Machine<T extends MachineTypes> = {
42
42
  };
43
43
  };
44
44
 
45
+ declare type MachineInstance<T extends MachineTypes> = Machine<T> & {
46
+ send: <K extends keyof Events<T>>(event: K, input: Input<T>, ...args: Events<T>[K] extends undefined ? [] : [payload: Events<T>[K]]) => void;
47
+ evaluate: (input: Input<T>) => void;
48
+ getComputed: (input: Input<T>) => Computed<T>;
49
+ cleanup: () => void;
50
+ };
51
+
45
52
  /**
46
53
  * Object-based generic types - specify only needed types in any order
47
54
  *
@@ -91,7 +98,7 @@ declare type StatesConfig<TState extends string, TContext, TEvents extends Event
91
98
  [K in TState]?: StateConfig<TContext, TEvents, TActions>;
92
99
  };
93
100
 
94
- export declare function useMachine<T extends MachineTypes>(machine: Machine<T>, input: T['input']): {
101
+ export declare function useMachine<T extends MachineTypes>(machine: MachineInstance<T>, input: T['input']): {
95
102
  send: Send<Events<T>>;
96
103
  computed: Computed<T>;
97
104
  state: State<T>;
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","sources":["../src/react.ts"],"sourcesContent":["/**\n * Controlled Machine - React Integration\n *\n * React hook for using controlled-machine in React components.\n */\n\nimport { useCallback, useRef, useEffect, useMemo } from 'react'\nimport {\n type MachineTypes,\n type Machine,\n type Events,\n type Computed,\n type State,\n type Send,\n type EffectHelpers,\n type EffectStore,\n type Context,\n computeValues,\n executeActions,\n executeHandler,\n processEffects,\n clearEffectStore,\n} from './index'\n\n// ============================================\n// React Hook\n// ============================================\n\nexport function useMachine<T extends MachineTypes>(\n machine: Machine<T>,\n input: T['input'],\n): { send: Send<Events<T>>; computed: Computed<T>; state: State<T> } {\n // refs for stable callbacks\n const inputRef = useRef(input)\n const machineRef = useRef(machine)\n const isMountedRef = useRef(true)\n\n inputRef.current = input\n machineRef.current = machine\n\n // compute values\n const { computed: computedDef } = machine\n const context = useMemo(\n () => computeValues(input, computedDef),\n [input, computedDef],\n )\n\n // extract computed only\n const computed = useMemo(() => {\n if (!computedDef) return {} as Computed<T>\n const result = {} as Computed<T>\n for (const key in computedDef) {\n result[key] = context[key]\n }\n return result\n }, [context, computedDef])\n\n const contextRef = useRef(context)\n contextRef.current = context\n\n const prevContextRef = useRef<Context<T>>(context)\n const effectStoreRef = useRef<EffectStore>({\n watchedValues: new Map(),\n enterCleanups: new Map(),\n changeCleanups: new Map(),\n exitCleanups: new Map(),\n })\n\n // always: auto-evaluate when context changes (synchronous, during render)\n const { always, actions } = machine\n if (prevContextRef.current !== context && always && actions) {\n const actionsMap = actions as Record<string, (context: Context<T>) => void>\n for (const rule of always) {\n if (!rule.when || rule.when(context, undefined)) {\n executeActions(rule.do, actionsMap, context, undefined)\n break\n }\n }\n }\n prevContextRef.current = context\n\n // send: stable function (no deps, uses refs)\n const send: Send<Events<T>> = useCallback(\n <K extends keyof Events<T>>(\n event: K,\n ...args: Events<T>[K] extends undefined ? [] : [payload: Events<T>[K]]\n ) => {\n const currentMachine = machineRef.current\n const currentInput = inputRef.current\n const currentContext = computeValues(\n currentInput,\n currentMachine.computed,\n )\n const payload = args[0] as Events<T>[K]\n\n // 1. State-specific handler first\n const state = (currentContext as { state?: State<T> }).state\n if (state && currentMachine.states?.[state]?.on?.[event]) {\n const stateHandler = currentMachine.states[state].on![event]!\n executeHandler(\n stateHandler,\n currentMachine.actions ?? {},\n currentContext,\n payload,\n )\n }\n\n // 2. Global handler\n const globalHandler = currentMachine.on?.[event]\n if (globalHandler) {\n executeHandler(\n globalHandler,\n currentMachine.actions ?? {},\n currentContext,\n payload,\n )\n }\n },\n [], // no dependencies - uses refs\n )\n\n // safeSend: won't be called after unmount\n const safeSend: Send<Events<T>> = useCallback(\n <K extends keyof Events<T>>(\n event: K,\n ...args: Events<T>[K] extends undefined ? [] : [payload: Events<T>[K]]\n ) => {\n if (!isMountedRef.current) return\n send(event, ...args)\n },\n [send],\n )\n\n // effect helpers\n const effectHelpers: EffectHelpers<Events<T>> = useMemo(\n () => ({ send: safeSend }),\n [safeSend],\n )\n\n // effects: detect watch value changes\n const { effects } = machine\n useEffect(() => {\n processEffects(effects, context, effectHelpers, effectStoreRef.current)\n }, [context, effects, effectHelpers])\n\n // mount/unmount management\n useEffect(() => {\n isMountedRef.current = true\n const store = effectStoreRef.current\n return () => {\n isMountedRef.current = false\n clearEffectStore(store)\n }\n }, [])\n\n // state from context (default to empty string if not provided)\n const state = (context as { state?: State<T> }).state ?? ('' as State<T>)\n\n return { send, computed, state }\n}\n\n// Re-export types for convenience\nexport type { Send } from './index'\n"],"names":["state"],"mappings":";;AA4BO,SAAS,WACd,SACA,OACmE;AAEnE,QAAM,WAAW,OAAO,KAAK;AAC7B,QAAM,aAAa,OAAO,OAAO;AACjC,QAAM,eAAe,OAAO,IAAI;AAEhC,WAAS,UAAU;AACnB,aAAW,UAAU;AAGrB,QAAM,EAAE,UAAU,YAAA,IAAgB;AAClC,QAAM,UAAU;AAAA,IACd,MAAM,cAAc,OAAO,WAAW;AAAA,IACtC,CAAC,OAAO,WAAW;AAAA,EAAA;AAIrB,QAAM,WAAW,QAAQ,MAAM;AAC7B,QAAI,CAAC,YAAa,QAAO,CAAA;AACzB,UAAM,SAAS,CAAA;AACf,eAAW,OAAO,aAAa;AAC7B,aAAO,GAAG,IAAI,QAAQ,GAAG;AAAA,IAC3B;AACA,WAAO;AAAA,EACT,GAAG,CAAC,SAAS,WAAW,CAAC;AAEzB,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AAErB,QAAM,iBAAiB,OAAmB,OAAO;AACjD,QAAM,iBAAiB,OAAoB;AAAA,IACzC,mCAAmB,IAAA;AAAA,IACnB,mCAAmB,IAAA;AAAA,IACnB,oCAAoB,IAAA;AAAA,IACpB,kCAAkB,IAAA;AAAA,EAAI,CACvB;AAGD,QAAM,EAAE,QAAQ,QAAA,IAAY;AAC5B,MAAI,eAAe,YAAY,WAAW,UAAU,SAAS;AAC3D,UAAM,aAAa;AACnB,eAAW,QAAQ,QAAQ;AACzB,UAAI,CAAC,KAAK,QAAQ,KAAK,KAAK,SAAS,MAAS,GAAG;AAC/C,uBAAe,KAAK,IAAI,YAAY,SAAS,MAAS;AACtD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,iBAAe,UAAU;AAGzB,QAAM,OAAwB;AAAA,IAC5B,CACE,UACG,SACA;AACH,YAAM,iBAAiB,WAAW;AAClC,YAAM,eAAe,SAAS;AAC9B,YAAM,iBAAiB;AAAA,QACrB;AAAA,QACA,eAAe;AAAA,MAAA;AAEjB,YAAM,UAAU,KAAK,CAAC;AAGtB,YAAMA,SAAS,eAAwC;AACvD,UAAIA,UAAS,eAAe,SAASA,MAAK,GAAG,KAAK,KAAK,GAAG;AACxD,cAAM,eAAe,eAAe,OAAOA,MAAK,EAAE,GAAI,KAAK;AAC3D;AAAA,UACE;AAAA,UACA,eAAe,WAAW,CAAA;AAAA,UAC1B;AAAA,UACA;AAAA,QAAA;AAAA,MAEJ;AAGA,YAAM,gBAAgB,eAAe,KAAK,KAAK;AAC/C,UAAI,eAAe;AACjB;AAAA,UACE;AAAA,UACA,eAAe,WAAW,CAAA;AAAA,UAC1B;AAAA,UACA;AAAA,QAAA;AAAA,MAEJ;AAAA,IACF;AAAA,IACA,CAAA;AAAA;AAAA,EAAC;AAIH,QAAM,WAA4B;AAAA,IAChC,CACE,UACG,SACA;AACH,UAAI,CAAC,aAAa,QAAS;AAC3B,WAAK,OAAO,GAAG,IAAI;AAAA,IACrB;AAAA,IACA,CAAC,IAAI;AAAA,EAAA;AAIP,QAAM,gBAA0C;AAAA,IAC9C,OAAO,EAAE,MAAM;IACf,CAAC,QAAQ;AAAA,EAAA;AAIX,QAAM,EAAE,YAAY;AACpB,YAAU,MAAM;AACd,mBAAe,SAAS,SAAS,eAAe,eAAe,OAAO;AAAA,EACxE,GAAG,CAAC,SAAS,SAAS,aAAa,CAAC;AAGpC,YAAU,MAAM;AACd,iBAAa,UAAU;AACvB,UAAM,QAAQ,eAAe;AAC7B,WAAO,MAAM;AACX,mBAAa,UAAU;AACvB,uBAAiB,KAAK;AAAA,IACxB;AAAA,EACF,GAAG,CAAA,CAAE;AAGL,QAAM,QAAS,QAAiC,SAAU;AAE1D,SAAO,EAAE,MAAM,UAAU,MAAA;AAC3B;"}
1
+ {"version":3,"file":"react.js","sources":["../src/react.ts"],"sourcesContent":["/**\n * Controlled Machine - React Integration\n *\n * React hook for using controlled-machine in React components.\n */\n\nimport { useCallback, useRef, useEffect, useMemo } from 'react'\nimport {\n type MachineTypes,\n type Machine,\n type Events,\n type Computed,\n type State,\n type Send,\n type EffectHelpers,\n type EffectStore,\n type Context,\n computeValues,\n executeActions,\n executeHandler,\n processEffects,\n clearEffectStore,\n MachineInstance,\n} from './index'\n\n// ============================================\n// React Hook\n// ============================================\n\nexport function useMachine<T extends MachineTypes>(\n machine: MachineInstance<T>,\n input: T['input'],\n): { send: Send<Events<T>>; computed: Computed<T>; state: State<T> } {\n // refs for stable callbacks\n const inputRef = useRef(input)\n const machineRef = useRef(machine)\n const isMountedRef = useRef(true)\n\n inputRef.current = input\n machineRef.current = machine\n\n // compute values\n const { computed: computedDef } = machine\n const context = useMemo(\n () => computeValues(input, computedDef),\n [input, computedDef],\n )\n\n // extract computed only\n const computed = useMemo(() => {\n if (!computedDef) return {} as Computed<T>\n const result = {} as Computed<T>\n for (const key in computedDef) {\n result[key] = context[key]\n }\n return result\n }, [context, computedDef])\n\n const contextRef = useRef(context)\n contextRef.current = context\n\n const prevContextRef = useRef<Context<T>>(context)\n const effectStoreRef = useRef<EffectStore>({\n watchedValues: new Map(),\n enterCleanups: new Map(),\n changeCleanups: new Map(),\n exitCleanups: new Map(),\n })\n\n // always: auto-evaluate when context changes (synchronous, during render)\n const { always, actions } = machine\n if (prevContextRef.current !== context && always && actions) {\n const actionsMap = actions as Record<string, (context: Context<T>) => void>\n for (const rule of always) {\n if (!rule.when || rule.when(context, undefined)) {\n executeActions(rule.do, actionsMap, context, undefined)\n break\n }\n }\n }\n prevContextRef.current = context\n\n // send: stable function (no deps, uses refs)\n const send: Send<Events<T>> = useCallback(\n <K extends keyof Events<T>>(\n event: K,\n ...args: Events<T>[K] extends undefined ? [] : [payload: Events<T>[K]]\n ) => {\n const currentMachine = machineRef.current\n const currentInput = inputRef.current\n const currentContext = computeValues(\n currentInput,\n currentMachine.computed,\n )\n const payload = args[0] as Events<T>[K]\n\n // 1. State-specific handler first\n const state = (currentContext as { state?: State<T> }).state\n if (state && currentMachine.states?.[state]?.on?.[event]) {\n const stateHandler = currentMachine.states[state].on![event]!\n executeHandler(\n stateHandler,\n currentMachine.actions ?? {},\n currentContext,\n payload,\n )\n }\n\n // 2. Global handler\n const globalHandler = currentMachine.on?.[event]\n if (globalHandler) {\n executeHandler(\n globalHandler,\n currentMachine.actions ?? {},\n currentContext,\n payload,\n )\n }\n },\n [], // no dependencies - uses refs\n )\n\n // safeSend: won't be called after unmount\n const safeSend: Send<Events<T>> = useCallback(\n <K extends keyof Events<T>>(\n event: K,\n ...args: Events<T>[K] extends undefined ? [] : [payload: Events<T>[K]]\n ) => {\n if (!isMountedRef.current) return\n send(event, ...args)\n },\n [send],\n )\n\n // effect helpers\n const effectHelpers: EffectHelpers<Events<T>> = useMemo(\n () => ({ send: safeSend }),\n [safeSend],\n )\n\n // effects: detect watch value changes\n const { effects } = machine\n useEffect(() => {\n processEffects(effects, context, effectHelpers, effectStoreRef.current)\n }, [context, effects, effectHelpers])\n\n // mount/unmount management\n useEffect(() => {\n isMountedRef.current = true\n const store = effectStoreRef.current\n return () => {\n isMountedRef.current = false\n clearEffectStore(store)\n }\n }, [])\n\n // state from context (default to empty string if not provided)\n const state = (context as { state?: State<T> }).state ?? ('' as State<T>)\n\n return { send, computed, state }\n}\n\n// Re-export types for convenience\nexport type { Send } from './index'\n"],"names":["state"],"mappings":";;AA6BO,SAAS,WACd,SACA,OACmE;AAEnE,QAAM,WAAW,OAAO,KAAK;AAC7B,QAAM,aAAa,OAAO,OAAO;AACjC,QAAM,eAAe,OAAO,IAAI;AAEhC,WAAS,UAAU;AACnB,aAAW,UAAU;AAGrB,QAAM,EAAE,UAAU,YAAA,IAAgB;AAClC,QAAM,UAAU;AAAA,IACd,MAAM,cAAc,OAAO,WAAW;AAAA,IACtC,CAAC,OAAO,WAAW;AAAA,EAAA;AAIrB,QAAM,WAAW,QAAQ,MAAM;AAC7B,QAAI,CAAC,YAAa,QAAO,CAAA;AACzB,UAAM,SAAS,CAAA;AACf,eAAW,OAAO,aAAa;AAC7B,aAAO,GAAG,IAAI,QAAQ,GAAG;AAAA,IAC3B;AACA,WAAO;AAAA,EACT,GAAG,CAAC,SAAS,WAAW,CAAC;AAEzB,QAAM,aAAa,OAAO,OAAO;AACjC,aAAW,UAAU;AAErB,QAAM,iBAAiB,OAAmB,OAAO;AACjD,QAAM,iBAAiB,OAAoB;AAAA,IACzC,mCAAmB,IAAA;AAAA,IACnB,mCAAmB,IAAA;AAAA,IACnB,oCAAoB,IAAA;AAAA,IACpB,kCAAkB,IAAA;AAAA,EAAI,CACvB;AAGD,QAAM,EAAE,QAAQ,QAAA,IAAY;AAC5B,MAAI,eAAe,YAAY,WAAW,UAAU,SAAS;AAC3D,UAAM,aAAa;AACnB,eAAW,QAAQ,QAAQ;AACzB,UAAI,CAAC,KAAK,QAAQ,KAAK,KAAK,SAAS,MAAS,GAAG;AAC/C,uBAAe,KAAK,IAAI,YAAY,SAAS,MAAS;AACtD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,iBAAe,UAAU;AAGzB,QAAM,OAAwB;AAAA,IAC5B,CACE,UACG,SACA;AACH,YAAM,iBAAiB,WAAW;AAClC,YAAM,eAAe,SAAS;AAC9B,YAAM,iBAAiB;AAAA,QACrB;AAAA,QACA,eAAe;AAAA,MAAA;AAEjB,YAAM,UAAU,KAAK,CAAC;AAGtB,YAAMA,SAAS,eAAwC;AACvD,UAAIA,UAAS,eAAe,SAASA,MAAK,GAAG,KAAK,KAAK,GAAG;AACxD,cAAM,eAAe,eAAe,OAAOA,MAAK,EAAE,GAAI,KAAK;AAC3D;AAAA,UACE;AAAA,UACA,eAAe,WAAW,CAAA;AAAA,UAC1B;AAAA,UACA;AAAA,QAAA;AAAA,MAEJ;AAGA,YAAM,gBAAgB,eAAe,KAAK,KAAK;AAC/C,UAAI,eAAe;AACjB;AAAA,UACE;AAAA,UACA,eAAe,WAAW,CAAA;AAAA,UAC1B;AAAA,UACA;AAAA,QAAA;AAAA,MAEJ;AAAA,IACF;AAAA,IACA,CAAA;AAAA;AAAA,EAAC;AAIH,QAAM,WAA4B;AAAA,IAChC,CACE,UACG,SACA;AACH,UAAI,CAAC,aAAa,QAAS;AAC3B,WAAK,OAAO,GAAG,IAAI;AAAA,IACrB;AAAA,IACA,CAAC,IAAI;AAAA,EAAA;AAIP,QAAM,gBAA0C;AAAA,IAC9C,OAAO,EAAE,MAAM;IACf,CAAC,QAAQ;AAAA,EAAA;AAIX,QAAM,EAAE,YAAY;AACpB,YAAU,MAAM;AACd,mBAAe,SAAS,SAAS,eAAe,eAAe,OAAO;AAAA,EACxE,GAAG,CAAC,SAAS,SAAS,aAAa,CAAC;AAGpC,YAAU,MAAM;AACd,iBAAa,UAAU;AACvB,UAAM,QAAQ,eAAe;AAC7B,WAAO,MAAM;AACX,mBAAa,UAAU;AACvB,uBAAiB,KAAK;AAAA,IACxB;AAAA,EACF,GAAG,CAAA,CAAE;AAGL,QAAM,QAAS,QAAiC,SAAU;AAE1D,SAAO,EAAE,MAAM,UAAU,MAAA;AAC3B;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "controlled-machine",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "A controlled state machine where state lives outside the machine",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -71,5 +71,6 @@
71
71
  "vite": "^7.3.1",
72
72
  "vite-plugin-dts": "^4.5.4",
73
73
  "vitest": "^4.0.16"
74
- }
74
+ },
75
+ "packageManager": "pnpm@9.15.0+sha512.76e2379760a4328ec4415815bcd6628dee727af3779aaa4c914e3944156c4299921a89f976381ee107d41f12cfa4b66681ca9c718f0668fa0831ed4c6d8ba56c"
75
76
  }