controlled-machine 0.1.2 → 0.2.0

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.
package/dist/react.d.cts CHANGED
@@ -1,107 +1,17 @@
1
- declare type Actions<T extends MachineTypes> = T['actions'] extends string ? T['actions'] : string;
2
-
3
- declare type Cleanup = () => void;
4
-
5
- declare type Computed<T extends MachineTypes> = T['computed'] extends ComputedConfig ? T['computed'] : Record<string, never>;
6
-
7
- declare type ComputedConfig = Record<string, unknown>;
8
-
9
- declare type Context<T extends MachineTypes> = Input<T> & Computed<T>;
10
-
11
- declare type Effect<TContext, TEvents extends EventsConfig, TWatched = unknown> = {
12
- watch: (context: TContext) => TWatched;
13
- enter?: (context: TContext, helpers: EffectHelpers<TEvents>) => void | Cleanup | Promise<void>;
14
- exit?: (context: TContext, helpers: EffectHelpers<TEvents>) => void | Cleanup;
15
- change?: (context: TContext, prev: TWatched | undefined, curr: TWatched, helpers: EffectHelpers<TEvents>) => void | Cleanup;
16
- };
17
-
18
- declare type EffectHelpers<TEvents extends EventsConfig> = {
19
- send: Send<TEvents>;
20
- };
21
-
22
- declare type Events<T extends MachineTypes> = T['events'] extends EventsConfig ? T['events'] : Record<string, undefined>;
23
-
24
- declare type EventsConfig = Record<string, unknown>;
25
-
26
- declare type Handler<TContext, TPayload = undefined, TActions extends string = string> = TActions | TActions[] | Rule<TContext, TPayload, TActions>[];
27
-
28
- declare type Input<T extends MachineTypes> = T['input'];
29
-
30
- declare type Machine<T extends MachineTypes> = {
31
- computed?: {
32
- [K in keyof Computed<T>]: (input: Input<T>) => Computed<T>[K];
33
- };
34
- on?: {
35
- [K in keyof Events<T>]?: Handler<Context<T>, Events<T>[K], Actions<T>>;
36
- };
37
- states?: StatesConfig<State<T>, Context<T>, Events<T>, Actions<T>>;
38
- always?: Rule<Context<T>, undefined, Actions<T>>[];
39
- effects?: Effect<Context<T>, Events<T>, any>[];
40
- actions?: {
41
- [K in Actions<T>]: (context: Context<T>, payload?: any) => void;
42
- };
43
- };
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
-
52
- /**
53
- * Object-based generic types - specify only needed types in any order
54
- *
55
- * @example
56
- * createMachine<{
57
- * input: MyInput
58
- * events: MyEvents
59
- * actions: 'foo' | 'bar'
60
- * }>({...})
61
- */
62
- declare type MachineTypes = {
63
- input?: unknown;
64
- events?: EventsConfig;
65
- computed?: ComputedConfig;
66
- actions?: string;
67
- state?: string;
68
- };
69
-
70
- /**
71
- * Controlled Machine
72
- *
73
- * A controlled state machine where state lives outside the machine.
74
- *
75
- * - input: External data passed in
76
- * - computed: Derived values from input
77
- * - context: input + computed (full context available in handlers)
78
- * - on: Event → conditional actions
79
- * - effects: Watch-based side effects
80
- * - always: Auto-evaluated rules on context change
81
- */
82
- declare type Rule<TContext, TPayload = undefined, TActions extends string = string> = {
83
- when?: (context: TContext, payload: TPayload) => boolean;
84
- do: TActions | TActions[];
85
- };
86
-
87
- export declare type Send<TEvents extends EventsConfig> = <K extends keyof TEvents>(event: K, ...args: TEvents[K] extends undefined ? [] : [payload: TEvents[K]]) => void;
88
-
89
- declare type State<T extends MachineTypes> = T['state'] extends string ? T['state'] : string;
90
-
91
- declare type StateConfig<TContext, TEvents extends EventsConfig, TActions extends string = string> = {
92
- on?: {
93
- [K in keyof TEvents]?: Handler<TContext, TEvents[K], TActions>;
94
- };
95
- };
96
-
97
- declare type StatesConfig<TState extends string, TContext, TEvents extends EventsConfig, TActions extends string = string> = {
98
- [K in TState]?: StateConfig<TContext, TEvents, TActions>;
99
- };
100
-
101
- export declare function useMachine<T extends MachineTypes>(machine: MachineInstance<T>, input: T['input']): {
102
- send: Send<Events<T>>;
103
- computed: Computed<T>;
104
- state: State<T>;
105
- };
106
-
107
- export { }
1
+ import { MachineTypes, Events, Computed, State, Send, Context, Actions, Guards, MachineInstance } from './index';
2
+ export type UseMachineOptions<T extends MachineTypes> = {
3
+ input: T['input'];
4
+ actions?: Partial<{
5
+ [K in Actions<T>]: (context: Context<T>, payload?: any) => void;
6
+ }>;
7
+ guards?: Partial<{
8
+ [K in Guards<T>]: (context: Context<T>, payload?: any) => boolean;
9
+ }>;
10
+ };
11
+ export declare function useMachine<T extends MachineTypes>(machine: MachineInstance<T>, options: UseMachineOptions<T>): {
12
+ send: Send<Events<T>>;
13
+ computed: Computed<T>;
14
+ state: State<T>;
15
+ };
16
+ export type { Send } from './index';
17
+ //# sourceMappingURL=react.d.ts.map
package/dist/react.d.ts CHANGED
@@ -1,107 +1,17 @@
1
- declare type Actions<T extends MachineTypes> = T['actions'] extends string ? T['actions'] : string;
2
-
3
- declare type Cleanup = () => void;
4
-
5
- declare type Computed<T extends MachineTypes> = T['computed'] extends ComputedConfig ? T['computed'] : Record<string, never>;
6
-
7
- declare type ComputedConfig = Record<string, unknown>;
8
-
9
- declare type Context<T extends MachineTypes> = Input<T> & Computed<T>;
10
-
11
- declare type Effect<TContext, TEvents extends EventsConfig, TWatched = unknown> = {
12
- watch: (context: TContext) => TWatched;
13
- enter?: (context: TContext, helpers: EffectHelpers<TEvents>) => void | Cleanup | Promise<void>;
14
- exit?: (context: TContext, helpers: EffectHelpers<TEvents>) => void | Cleanup;
15
- change?: (context: TContext, prev: TWatched | undefined, curr: TWatched, helpers: EffectHelpers<TEvents>) => void | Cleanup;
16
- };
17
-
18
- declare type EffectHelpers<TEvents extends EventsConfig> = {
19
- send: Send<TEvents>;
20
- };
21
-
22
- declare type Events<T extends MachineTypes> = T['events'] extends EventsConfig ? T['events'] : Record<string, undefined>;
23
-
24
- declare type EventsConfig = Record<string, unknown>;
25
-
26
- declare type Handler<TContext, TPayload = undefined, TActions extends string = string> = TActions | TActions[] | Rule<TContext, TPayload, TActions>[];
27
-
28
- declare type Input<T extends MachineTypes> = T['input'];
29
-
30
- declare type Machine<T extends MachineTypes> = {
31
- computed?: {
32
- [K in keyof Computed<T>]: (input: Input<T>) => Computed<T>[K];
33
- };
34
- on?: {
35
- [K in keyof Events<T>]?: Handler<Context<T>, Events<T>[K], Actions<T>>;
36
- };
37
- states?: StatesConfig<State<T>, Context<T>, Events<T>, Actions<T>>;
38
- always?: Rule<Context<T>, undefined, Actions<T>>[];
39
- effects?: Effect<Context<T>, Events<T>, any>[];
40
- actions?: {
41
- [K in Actions<T>]: (context: Context<T>, payload?: any) => void;
42
- };
43
- };
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
-
52
- /**
53
- * Object-based generic types - specify only needed types in any order
54
- *
55
- * @example
56
- * createMachine<{
57
- * input: MyInput
58
- * events: MyEvents
59
- * actions: 'foo' | 'bar'
60
- * }>({...})
61
- */
62
- declare type MachineTypes = {
63
- input?: unknown;
64
- events?: EventsConfig;
65
- computed?: ComputedConfig;
66
- actions?: string;
67
- state?: string;
68
- };
69
-
70
- /**
71
- * Controlled Machine
72
- *
73
- * A controlled state machine where state lives outside the machine.
74
- *
75
- * - input: External data passed in
76
- * - computed: Derived values from input
77
- * - context: input + computed (full context available in handlers)
78
- * - on: Event → conditional actions
79
- * - effects: Watch-based side effects
80
- * - always: Auto-evaluated rules on context change
81
- */
82
- declare type Rule<TContext, TPayload = undefined, TActions extends string = string> = {
83
- when?: (context: TContext, payload: TPayload) => boolean;
84
- do: TActions | TActions[];
85
- };
86
-
87
- export declare type Send<TEvents extends EventsConfig> = <K extends keyof TEvents>(event: K, ...args: TEvents[K] extends undefined ? [] : [payload: TEvents[K]]) => void;
88
-
89
- declare type State<T extends MachineTypes> = T['state'] extends string ? T['state'] : string;
90
-
91
- declare type StateConfig<TContext, TEvents extends EventsConfig, TActions extends string = string> = {
92
- on?: {
93
- [K in keyof TEvents]?: Handler<TContext, TEvents[K], TActions>;
94
- };
95
- };
96
-
97
- declare type StatesConfig<TState extends string, TContext, TEvents extends EventsConfig, TActions extends string = string> = {
98
- [K in TState]?: StateConfig<TContext, TEvents, TActions>;
99
- };
100
-
101
- export declare function useMachine<T extends MachineTypes>(machine: MachineInstance<T>, input: T['input']): {
102
- send: Send<Events<T>>;
103
- computed: Computed<T>;
104
- state: State<T>;
105
- };
106
-
107
- export { }
1
+ import { MachineTypes, Events, Computed, State, Send, Context, Actions, Guards, MachineInstance } from './index';
2
+ export type UseMachineOptions<T extends MachineTypes> = {
3
+ input: T['input'];
4
+ actions?: Partial<{
5
+ [K in Actions<T>]: (context: Context<T>, payload?: any) => void;
6
+ }>;
7
+ guards?: Partial<{
8
+ [K in Guards<T>]: (context: Context<T>, payload?: any) => boolean;
9
+ }>;
10
+ };
11
+ export declare function useMachine<T extends MachineTypes>(machine: MachineInstance<T>, options: UseMachineOptions<T>): {
12
+ send: Send<Events<T>>;
13
+ computed: Computed<T>;
14
+ state: State<T>;
15
+ };
16
+ export type { Send } from './index';
17
+ //# sourceMappingURL=react.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"react.d.ts","sourceRoot":"","sources":["../src/react.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,MAAM,EACX,KAAK,QAAQ,EACb,KAAK,KAAK,EACV,KAAK,IAAI,EAGT,KAAK,OAAO,EACZ,KAAK,OAAO,EACZ,KAAK,MAAM,EAMX,eAAe,EAChB,MAAM,SAAS,CAAA;AAMhB,MAAM,MAAM,iBAAiB,CAAC,CAAC,SAAS,YAAY,IAAI;IACtD,KAAK,EAAE,CAAC,CAAC,OAAO,CAAC,CAAA;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;SAEf,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,IAAI;KAChE,CAAC,CAAA;IACF,MAAM,CAAC,EAAE,OAAO,CAAC;SAEd,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,KAAK,OAAO;KAClE,CAAC,CAAA;CACH,CAAA;AAMD,wBAAgB,UAAU,CAAC,CAAC,SAAS,YAAY,EAC/C,OAAO,EAAE,eAAe,CAAC,CAAC,CAAC,EAC3B,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAC5B;IAAE,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAA;CAAE,CAwJnE;AAGD,YAAY,EAAE,IAAI,EAAE,MAAM,SAAS,CAAA"}
package/dist/react.js CHANGED
@@ -1,11 +1,24 @@
1
- import { useRef, useMemo, useCallback, useEffect } from "react";
1
+ import { useMemo, useRef, useCallback, useEffect } from "react";
2
2
  import { computeValues, executeActions, executeHandler, processEffects, clearEffectStore } from "./index.js";
3
- function useMachine(machine, input) {
3
+ function useMachine(machine, options) {
4
+ const { input, actions: optionsActions, guards: optionsGuards } = options;
5
+ const mergedActions = useMemo(
6
+ () => ({ ...machine.actions, ...optionsActions }),
7
+ [machine.actions, optionsActions]
8
+ );
9
+ const mergedGuards = useMemo(
10
+ () => optionsGuards ?? {},
11
+ [optionsGuards]
12
+ );
4
13
  const inputRef = useRef(input);
5
14
  const machineRef = useRef(machine);
15
+ const mergedActionsRef = useRef(mergedActions);
16
+ const mergedGuardsRef = useRef(mergedGuards);
6
17
  const isMountedRef = useRef(true);
7
18
  inputRef.current = input;
8
19
  machineRef.current = machine;
20
+ mergedActionsRef.current = mergedActions;
21
+ mergedGuardsRef.current = mergedGuards;
9
22
  const { computed: computedDef } = machine;
10
23
  const context = useMemo(
11
24
  () => computeValues(input, computedDef),
@@ -28,11 +41,13 @@ function useMachine(machine, input) {
28
41
  changeCleanups: /* @__PURE__ */ new Map(),
29
42
  exitCleanups: /* @__PURE__ */ new Map()
30
43
  });
31
- const { always, actions } = machine;
32
- if (prevContextRef.current !== context && always && actions) {
33
- const actionsMap = actions;
44
+ const { always } = machine;
45
+ if (prevContextRef.current !== context && always && Object.keys(mergedActions).length > 0) {
46
+ const actionsMap = mergedActions;
47
+ const guardsMap = mergedGuards;
34
48
  for (const rule of always) {
35
- if (!rule.when || rule.when(context, void 0)) {
49
+ const guardFn = typeof rule.when === "string" ? guardsMap[rule.when] : rule.when;
50
+ if (!guardFn || guardFn(context, void 0)) {
36
51
  executeActions(rule.do, actionsMap, context, void 0);
37
52
  break;
38
53
  }
@@ -43,6 +58,8 @@ function useMachine(machine, input) {
43
58
  (event, ...args) => {
44
59
  const currentMachine = machineRef.current;
45
60
  const currentInput = inputRef.current;
61
+ const currentActions = mergedActionsRef.current;
62
+ const currentGuards = mergedGuardsRef.current;
46
63
  const currentContext = computeValues(
47
64
  currentInput,
48
65
  currentMachine.computed
@@ -53,7 +70,8 @@ function useMachine(machine, input) {
53
70
  const stateHandler = currentMachine.states[state2].on[event];
54
71
  executeHandler(
55
72
  stateHandler,
56
- currentMachine.actions ?? {},
73
+ currentActions ?? {},
74
+ currentGuards,
57
75
  currentContext,
58
76
  payload
59
77
  );
@@ -62,7 +80,8 @@ function useMachine(machine, input) {
62
80
  if (globalHandler) {
63
81
  executeHandler(
64
82
  globalHandler,
65
- currentMachine.actions ?? {},
83
+ currentActions ?? {},
84
+ currentGuards,
66
85
  currentContext,
67
86
  payload
68
87
  );
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 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;"}
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 Events,\n type Computed,\n type State,\n type Send,\n type EffectHelpers,\n type EffectStore,\n type Context,\n type Actions,\n type Guards,\n computeValues,\n executeActions,\n executeHandler,\n processEffects,\n clearEffectStore,\n MachineInstance,\n} from './index'\n\n// ============================================\n// useMachine Options Type\n// ============================================\n\nexport type UseMachineOptions<T extends MachineTypes> = {\n input: T['input']\n actions?: Partial<{\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [K in Actions<T>]: (context: Context<T>, payload?: any) => void\n }>\n guards?: Partial<{\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n [K in Guards<T>]: (context: Context<T>, payload?: any) => boolean\n }>\n}\n\n// ============================================\n// React Hook\n// ============================================\n\nexport function useMachine<T extends MachineTypes>(\n machine: MachineInstance<T>,\n options: UseMachineOptions<T>,\n): { send: Send<Events<T>>; computed: Computed<T>; state: State<T> } {\n const { input, actions: optionsActions, guards: optionsGuards } = options\n\n // Merge actions and guards\n const mergedActions = useMemo(\n () => ({ ...machine.actions, ...optionsActions }),\n [machine.actions, optionsActions],\n )\n const mergedGuards = useMemo(\n () => (optionsGuards ?? {}) as Record<string, (context: Context<T>, payload?: unknown) => boolean>,\n [optionsGuards],\n )\n\n // refs for stable callbacks\n const inputRef = useRef(input)\n const machineRef = useRef(machine)\n const mergedActionsRef = useRef(mergedActions)\n const mergedGuardsRef = useRef(mergedGuards)\n const isMountedRef = useRef(true)\n\n inputRef.current = input\n machineRef.current = machine\n mergedActionsRef.current = mergedActions\n mergedGuardsRef.current = mergedGuards\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 } = machine\n if (prevContextRef.current !== context && always && Object.keys(mergedActions).length > 0) {\n const actionsMap = mergedActions as Record<string, (context: Context<T>) => void>\n const guardsMap = mergedGuards as Record<string, (context: Context<T>) => boolean>\n for (const rule of always) {\n const guardFn =\n typeof rule.when === 'string' ? guardsMap[rule.when] : rule.when\n\n if (!guardFn || guardFn(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 currentActions = mergedActionsRef.current\n const currentGuards = mergedGuardsRef.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 currentActions ?? {},\n currentGuards,\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 currentActions ?? {},\n currentGuards,\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":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "controlled-machine",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "A controlled state machine where state lives outside the machine",
5
5
  "type": "module",
6
6
  "sideEffects": false,