@myriadcodelabs/uiflow 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -1,190 +1,312 @@
1
1
  # UIFlow
2
2
 
3
- Explicit, code-first UI flow orchestration for React. UIFlow lets you define flows as plain objects with named steps, mix UI and logic steps, and move between them by returning the next step name. It’s useful when you want predictable, testable multi‑step experiences without wiring up routers, wizards, or state machines by hand.
3
+ Code-first flow orchestration for React.
4
4
 
5
- ## Why it matters
5
+ UIFlow helps you build multi-step UI without scattering state and transition logic across many components. You define steps in one place, and each step decides what comes next.
6
6
 
7
- - **Clarity:** Flows are defined in one place with explicit step names and transitions.
8
- - **Flexibility:** Combine UI steps and async logic steps in the same flow.
9
- - **Reusability:** Share cross‑flow state through event channels.
7
+ ## Why UIFlow
10
8
 
11
- ## Quick example
9
+ - Keep flow logic explicit: step names + transitions are centralized.
10
+ - Mix UI and async logic naturally: both are first-class steps.
11
+ - Share state across independent flows with channels.
12
+ - Stay in plain TypeScript objects, not custom DSLs.
13
+
14
+ ## Mental model (60 seconds)
15
+
16
+ A flow is:
17
+ - `steps`: a map of step names to step definitions
18
+ - `start`: first step name
19
+
20
+ A step is either:
21
+ - UI step: `input`, `view`, `onOutput`
22
+ - Action step: `input`, `action`, `onOutput`
23
+
24
+ Transition rule:
25
+ - `onOutput` returns next step name (string) to move forward
26
+ - returning `void` keeps the same step and re-renders
27
+
28
+ ## Install
29
+
30
+ ```bash
31
+ pnpm add @myriadcodelabs/uiflow
32
+ # or
33
+ npm i @myriadcodelabs/uiflow
34
+ # or
35
+ yarn add @myriadcodelabs/uiflow
36
+ ```
37
+
38
+ ## Imports
39
+
40
+ ```ts
41
+ import { FlowRunner, defineFlow, createFlowChannel, type OutputHandle } from "@myriadcodelabs/uiflow";
42
+ ```
43
+
44
+ Use package-root imports only.
45
+
46
+ ## Quick start (minimal runnable example)
12
47
 
13
48
  ```tsx
14
- import React from "react";
15
- import { FlowRunner, defineFlow, createFlowChannel } from "@myriadcodelabs/uiflow";
49
+ "use client";
16
50
 
17
- type StudyData = {
18
- deckId: string;
19
- cards: CardWithState[];
20
- activeCardId: string | null;
21
- };
51
+ import { FlowRunner, defineFlow, type OutputHandle } from "@myriadcodelabs/uiflow";
22
52
 
23
- type CardWithState = {
24
- id: string;
25
- question: string;
26
- answer: string;
27
- flipped: boolean;
28
- rating: "easy" | "medium" | "hard" | null;
29
- };
53
+ type Data = { name: string };
54
+ type AskNameOutput = { action: "setName"; value: string } | { action: "submit" };
55
+
56
+ function AskNameView(props: {
57
+ input: { name: string };
58
+ output: OutputHandle<AskNameOutput>;
59
+ }) {
60
+ return (
61
+ <div>
62
+ <input
63
+ value={props.input.name}
64
+ onChange={(e) => props.output.emit({ action: "setName", value: e.target.value })}
65
+ placeholder="Your name"
66
+ />
67
+ <button onClick={() => props.output.emit({ action: "submit" })}>Continue</button>
68
+ </div>
69
+ );
70
+ }
71
+
72
+ function DoneView(props: { input: { message: string }; output: OutputHandle<never> }) {
73
+ return <h2>{props.input.message}</h2>;
74
+ }
75
+
76
+ const onboardingFlow = defineFlow<Data>(
77
+ {
78
+ askName: {
79
+ input: (data) => ({ name: data.name }),
80
+ view: AskNameView,
81
+ onOutput: (data, output) => {
82
+ if (output.action === "setName") {
83
+ data.name = output.value;
84
+ return;
85
+ }
86
+ if (output.action === "submit") {
87
+ return "done";
88
+ }
89
+ },
90
+ },
91
+
92
+ done: {
93
+ input: (data) => ({ message: `Welcome, ${data.name || "friend"}!` }),
94
+ view: DoneView,
95
+ onOutput: () => {},
96
+ },
97
+ },
98
+ { start: "askName" }
99
+ );
100
+
101
+ export function App() {
102
+ return <FlowRunner flow={onboardingFlow} initialData={{ name: "" }} />;
103
+ }
104
+ ```
105
+
106
+ ## Practical pattern: study/review flow (real-world)
30
107
 
31
- export type ShowCardOutput =
32
- | { action: "flip"; cardId: string }
33
- | { action: "rate"; rating: Rating; cardId: string }
34
- | { action: "next"; cardId: string };
108
+ This pattern is taken from practical flashcards usage.
35
109
 
36
- const studiedCounter = createFlowChannel<number>(0);
110
+ ```ts
111
+ import { defineFlow } from "@myriadcodelabs/uiflow";
37
112
 
113
+ type Data = {
114
+ deckId: string;
115
+ flowData: {
116
+ cards: Array<{ id: string; flipped: boolean; rating: "easy" | "good" | "hard" | "again" | null }>;
117
+ activeCardId: string | null;
118
+ };
119
+ };
38
120
 
39
- const studyFlow = defineFlow<StudyData>(
121
+ type StudyOutput =
122
+ | { action: "flip"; cardId: string }
123
+ | { action: "rate"; cardId: string; rating: "easy" | "good" | "hard" | "again" }
124
+ | { action: "next"; cardId: string };
125
+
126
+ export const studyFlow = defineFlow<Data>(
40
127
  {
41
- // step 1
42
128
  fetchCards: {
43
129
  input: (data) => ({ deckId: data.deckId }),
44
130
  action: async ({ deckId }, data) => {
45
- const cards = await fakeFetchCards(deckId);
46
- data.cards = cards.map((card) => ({ ...card, flipped: false, rating: null }));
47
- data.activeCardId = null;
131
+ const cards = await fetchCardsListAction(deckId);
132
+ data.flowData.cards = (cards ?? []).map((c) => ({ id: c.id, flipped: false, rating: null }));
133
+ data.flowData.activeCardId = null;
48
134
  return { ok: true };
49
135
  },
50
136
  onOutput: () => "decide",
51
137
  },
52
138
 
53
- // step 2
54
139
  decide: {
55
- input: (data) => ({ hasCards: data.cards.length > 0 }),
140
+ input: (data) => ({ hasCards: data.flowData.cards.length > 0 }),
56
141
  action: ({ hasCards }) => hasCards,
57
- onOutput: (_, exists) => (exists ? "study" : "noCard"),
58
- },
59
- // if no card present
60
- noCard: {
61
- input: () => ({}),
62
- view: NoCardView,
63
- onOutput: () => {},
142
+ onOutput: (_, hasCards) => (hasCards ? "study" : "empty"),
64
143
  },
65
- // step 3
66
- study: {
67
- input: (data) => ({ cards: data.cards }),
68
- view: CardView,
69
- onOutput: (data, output, events) => {
70
- const card = data.cards.find((c) => c.id === output.cardId);
71
- if (!card) return "study";
72
144
 
145
+ study: {
146
+ input: (data) => ({ cards: data.flowData.cards, activeCardId: data.flowData.activeCardId }),
147
+ view: StudyCardsView,
148
+ onOutput: (data, output: StudyOutput, events) => {
73
149
  if (output.action === "flip") {
74
- data.activeCardId = card.id;
75
- card.flipped = true;
150
+ data.flowData.activeCardId = output.cardId;
151
+ const card = data.flowData.cards.find((c) => c.id === output.cardId);
152
+ if (card) card.flipped = true;
76
153
  return "study";
77
154
  }
78
155
 
79
156
  if (output.action === "rate") {
80
- data.activeCardId = card.id;
81
- card.rating = output.rating ?? null;
157
+ data.flowData.activeCardId = output.cardId;
158
+ const card = data.flowData.cards.find((c) => c.id === output.cardId);
159
+ if (card) card.rating = output.rating;
82
160
  return "review";
83
161
  }
84
162
 
85
163
  if (output.action === "next") {
86
- events?.studiedCounter.emit((c) => c + 1);
87
- data.activeCardId = null;
164
+ events?.studiedCounter.emit((n: number) => n + 1);
165
+ data.flowData.activeCardId = null;
88
166
  return "fetchCards";
89
167
  }
90
168
  },
91
169
  },
92
170
 
93
- // step 4: if user does review
94
171
  review: {
95
172
  input: (data) => ({
96
173
  deckId: data.deckId,
97
- cardId: data.activeCardId!,
98
- rating: data.cards.find((c) => c.id === data.activeCardId)?.rating!,
174
+ cardId: data.flowData.activeCardId,
175
+ rating: data.flowData.cards.find((c) => c.id === data.flowData.activeCardId)?.rating,
99
176
  }),
100
177
  action: async ({ deckId, cardId, rating }) => {
101
- await fakeReviewCard(deckId, cardId, rating);
178
+ await reviewCard(deckId, cardId, rating);
102
179
  return { ok: true };
103
180
  },
104
181
  onOutput: (data, _, events) => {
105
- events?.studiedCounter.emit((c) => c + 1);
106
- data.activeCardId = null;
182
+ events?.studiedCounter.emit((n: number) => n + 1);
183
+ data.flowData.activeCardId = null;
107
184
  return "fetchCards";
108
185
  },
109
186
  },
187
+
188
+ empty: {
189
+ input: () => ({}),
190
+ view: EmptyView,
191
+ onOutput: () => {},
192
+ },
110
193
  },
111
194
  { start: "fetchCards" }
112
195
  );
113
196
  ```
114
197
 
115
- The example components are defined here.
198
+ ## Cross-flow communication with channels
199
+
200
+ Use channels when two independent flows need shared reactive state.
116
201
 
117
202
  ```tsx
118
- const CardView: React.FC<{
119
- input: { cards: CardWithState[] };
120
- output: OutputHandle<ShowCardOutput>;
121
- }> = ({ input, output }) => (
122
- <div>
123
- {input.cards.map((card) => (
124
- <div key={card.id}>
125
- <div>{card.question}</div>
126
- {card.flipped ? <div>{card.answer}</div> : null}
127
- <button onClick={() => output.emit({ cardId: card.id, action: "flip" })}>Show Answer</button>
128
- <button onClick={() => output.emit({ cardId: card.id, action: "rate", rating: "easy" })}>Easy</button>
129
- <button onClick={() => output.emit({ cardId: card.id, action: "rate", rating: "medium" })}>Medium</button>
130
- <button onClick={() => output.emit({ cardId: card.id, action: "rate", rating: "hard" })}>Hard</button>
131
- <button onClick={() => output.emit({ cardId: card.id, action: "next" })}>Next</button>
132
- </div>
133
- ))}
134
- </div>
135
- );
203
+ "use client";
136
204
 
137
- const NoCardView: React.FC<{ input: {}; output: { emit: () => void } }> = () => (
138
- <div>No cards available.</div>
139
- );
140
- ```
141
- The FlowRunner is used to call the flow and set initial data and channels.
205
+ import { useMemo } from "react";
206
+ import { createFlowChannel, FlowRunner } from "@myriadcodelabs/uiflow";
207
+
208
+ export function FlashcardsScreen({ deckId }: { deckId: string }) {
209
+ const studiedCounter = useMemo(() => createFlowChannel<number>(0), []);
210
+ const channels = useMemo(() => ({ studiedCounter }), [studiedCounter]);
142
211
 
143
- ```tsx
144
- export function App() {
145
212
  return (
146
- <FlowRunner
147
- flow={studyFlow}
148
- initialData={{ deckId: "deck-1", cards: [], activeCardId: null }}
149
- eventChannels={{ studiedCounter }}
150
- />
213
+ <>
214
+ <FlowRunner flow={counterFlow} initialData={{}} eventChannels={channels} />
215
+ <FlowRunner
216
+ flow={studyFlow}
217
+ initialData={{ deckId, flowData: { cards: [], activeCardId: null } }}
218
+ eventChannels={channels}
219
+ />
220
+ </>
151
221
  );
152
222
  }
153
223
  ```
154
224
 
155
- ## API Reference (exported only)
225
+ ## API reference
156
226
 
157
- ### 1) Where the flow is called
227
+ ### `defineFlow(steps, { start })`
228
+ - Validates `start` exists in `steps`.
229
+ - Returns flow definition consumed by `FlowRunner`.
158
230
 
159
- #### `FlowRunner` (React component)
160
- Runs a flow and renders UI steps.
231
+ ### `FlowRunner`
161
232
 
162
233
  ```tsx
163
234
  <FlowRunner flow={flow} initialData={initialData} eventChannels={channels} />
164
235
  ```
165
236
 
166
- - `flow: FlowDefinition<D>` — created by `defineFlow`.
167
- - `initialData: D` — shared mutable data for this flow instance.
168
- - `eventChannels?: EventChannels` optional shared channels; emitting causes re-render.
237
+ Props:
238
+ - `flow`: flow definition
239
+ - `initialData`: mutable per-flow data object
240
+ - `eventChannels?`: optional channels map
241
+ - `eventChannelsStrategy?`: `"sticky"` (default) or `"replace"`
242
+
243
+ ### `createFlowChannel(initial)`
244
+ Creates channel with:
245
+ - `get()`
246
+ - `emit(update)`
247
+ - `subscribe(listener)`
248
+
249
+ ### `OutputHandle<O>`
250
+ UI steps emit events with:
251
+ - `output.emit(payload)`
252
+
253
+ ## How to keep flows manageable
254
+
255
+ 1. Keep views dumb: render from `input`, emit intent via `output.emit`.
256
+ 2. Keep transition logic in `onOutput` only.
257
+ 3. Use discriminated unions for UI output types.
258
+ 4. Co-locate domain state (example: card + flipped + rating in one structure).
259
+ 5. Use helper functions for repeated state ops.
260
+ 6. Split long flows into focused steps (`fetch`, `decide`, `view`, `commit`).
261
+
262
+ ## Important runtime behavior
263
+
264
+ 1. A step is treated as action step when it has `action` and does not have `view`.
265
+ 2. Action step runs automatically when it becomes current.
266
+ 3. `FlowRunner` normalizes channels before subscribing:
267
+ - `"sticky"` (default): keeps first-seen channel instance per key.
268
+ - `"replace"`: uses the latest incoming channel instances.
269
+ 4. Channel emissions trigger re-render for subscribed runners.
270
+ 5. Returning unknown step or `void` does not change current step.
271
+ 6. `initialData` is shallow-copied at runner initialization.
272
+
273
+ ## Pitfalls to avoid
274
+
275
+ 1. Creating channel instances directly in render can reset channel value if keys change or if using `"replace"` strategy.
276
+ 2. Rebuilding `eventChannels` object each render is safe; `FlowRunner` deduplicates equivalent maps internally.
277
+ 3. Using `output.done(...)` instead of `output.emit(...)`.
278
+ 4. Mixing `view` and `action` in the same step.
279
+ 5. Returning transition targets that do not exist.
280
+
281
+ ## Next.js notes
282
+
283
+ - `FlowRunner` and UI step views should be in client components.
284
+ - Add `"use client"` at the top where needed.
285
+ - Server actions can be called inside action steps.
286
+
287
+ ## FAQ
288
+
289
+ ### Why not just `useState` + `useEffect`?
169
290
 
170
- ### 2) Where the flow is defined
291
+ You can for simple screens. UIFlow is useful when screens become multi-step and transitions/side-effects spread across components.
171
292
 
172
- #### `defineFlow<D>(steps: FlowSteps<D>, options: DefineFlowOptions): FlowDefinition<D>`
173
- Creates a flow definition from a steps map and a required `start` step name.
293
+ ### Is flow data immutable?
174
294
 
175
- - `DefineFlowOptions.start: string` name of the first step.
295
+ No. Flow data is mutable by design inside step handlers.
176
296
 
177
- #### `createFlowChannel<T>(initial: T): FlowChannel<T>`
178
- Creates a shared channel for cross‑flow communication.
297
+ ### Can I have multiple flows on one page?
179
298
 
180
- - `FlowChannel.get(): T` read the current value.
181
- - `FlowChannel.emit(update: T | (prev: T) => T): void` — update value and notify subscribers.
182
- - `FlowChannel.subscribe(listener: () => void): () => void` — listen for changes.
299
+ Yes. Use channels when they need to communicate.
183
300
 
301
+ ## Complete checklist before shipping
184
302
 
185
- ### 3) A UI component
303
+ 1. `start` exists and all transitions target valid step keys.
304
+ 2. UI outputs are typed unions.
305
+ 3. Views only emit intent.
306
+ 4. Async work is in action steps.
307
+ 5. Channels are stable and reused.
308
+ 6. No internal-path imports.
186
309
 
187
- #### `OutputHandle<O>`
188
- Used by UI steps to emit output back into the flow.
310
+ ## License
189
311
 
190
- - `OutputHandle.emit(output: O): void`
312
+ MIT
package/dist/flow.d.ts CHANGED
@@ -78,6 +78,12 @@ export interface FlowRunnerProps<D extends FlowData = FlowData> {
78
78
  flow: FlowDefinition<D>;
79
79
  initialData: D;
80
80
  eventChannels?: EventChannels;
81
+ /**
82
+ * How FlowRunner treats incoming eventChannels across parent re-renders.
83
+ * - "sticky" (default): keep first-seen channel instance per key; ignore replacements for existing keys.
84
+ * - "replace": accept incoming channels as source of truth.
85
+ */
86
+ eventChannelsStrategy?: "sticky" | "replace";
81
87
  }
82
88
  /**
83
89
  * FlowRunner:
@@ -1 +1 @@
1
- {"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../src/flow.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAsC,MAAM,OAAO,CAAC;AAS3D,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC;AAE9C,MAAM,WAAW,WAAW,CAAC,CAAC;IAE1B,GAAG,EAAE,MAAM,CAAC,CAAC;IAGb,IAAI,EAAE,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;IAGnC,SAAS,EAAE,CAAC,QAAQ,EAAE,MAAM,IAAI,KAAK,MAAM,IAAI,CAAC;CACnD;AAID,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;AAI7D,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAiB/D;AAMD;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AAE3C;;;GAGG;AACH,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,GAAG;IACjC,IAAI,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,IAAI,CAAC;CAC7B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,MAAM,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG;IACnE,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,aAAa,KAAK,CAAC,CAAC;IAC9C,IAAI,EAAE,KAAK,CAAC,aAAa,CAAC;QAAE,KAAK,EAAE,CAAC,CAAC;QAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,CAAA;KAAE,CAAC,CAAC;IACjE,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,aAAa,KAAK,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACpG;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,EAAE,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,GAAG;IACvE,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,aAAa,KAAK,CAAC,CAAC;IAC9C,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,aAAa,KAAK,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IACtE,QAAQ,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,aAAa,KAAK,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;CACpG;AAED;;;;;GAKG;AACH,MAAM,MAAM,QAAQ,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAC5C,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,GACnB,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;AAE9B;;GAEG;AACH,MAAM,MAAM,SAAS,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,IAAI,MAAM,CACzD,MAAM,EACN,QAAQ,CAAC,CAAC,CAAC,CACd,CAAC;AAEF;;GAEG;AACH,MAAM,WAAW,cAAc,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ;IACzD,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAC9B,KAAK,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,EACpD,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,EACnB,OAAO,EAAE,iBAAiB,GAC3B,cAAc,CAAC,CAAC,CAAC,CAUnB;AASD,MAAM,WAAW,eAAe,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ;IAC1D,IAAI,EAAE,cAAc,CAAC,CAAC,CAAC,CAAC;IACxB,WAAW,EAAE,CAAC,CAAC;IAKf,aAAa,CAAC,EAAE,aAAa,CAAC;CACjC;AAaD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,QAAQ,GAAG,QAAQ,EACpD,KAAK,EAAE,QAAQ,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC,2CA2ItC"}
1
+ {"version":3,"file":"flow.d.ts","sourceRoot":"","sources":["../src/flow.tsx"],"names":[],"mappings":"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"}
package/dist/flow.js CHANGED
@@ -39,10 +39,38 @@ export function defineFlow(steps, options) {
39
39
  * - renders UI steps
40
40
  */
41
41
  export function FlowRunner(props) {
42
- const eventChannelsRef = useRef(undefined);
43
- eventChannelsRef.current ?? (eventChannelsRef.current = props.eventChannels);
44
- const eventChannels = eventChannelsRef.current;
45
- const { flow, initialData } = props;
42
+ const { flow, initialData, eventChannels, eventChannelsStrategy = "sticky", } = props;
43
+ const resolvedChannelsRef = useRef(undefined);
44
+ const getResolvedChannels = () => {
45
+ const prev = resolvedChannelsRef.current;
46
+ const incoming = eventChannels;
47
+ if (!incoming) {
48
+ resolvedChannelsRef.current = undefined;
49
+ return undefined;
50
+ }
51
+ const incomingEntries = Object.entries(incoming);
52
+ let candidate;
53
+ if (eventChannelsStrategy === "sticky") {
54
+ candidate = {};
55
+ for (const [key, channel] of incomingEntries) {
56
+ candidate[key] = prev?.[key] ?? channel;
57
+ }
58
+ }
59
+ else {
60
+ candidate = incoming;
61
+ }
62
+ if (prev) {
63
+ const prevKeys = Object.keys(prev);
64
+ const candidateKeys = Object.keys(candidate);
65
+ if (prevKeys.length === candidateKeys.length &&
66
+ candidateKeys.every((k) => prev[k] === candidate[k])) {
67
+ return prev;
68
+ }
69
+ }
70
+ resolvedChannelsRef.current = candidate;
71
+ return candidate;
72
+ };
73
+ const resolvedEventChannels = getResolvedChannels();
46
74
  // We keep data and currentStep in state so React re-renders on change.
47
75
  const [state, setState] = useState({
48
76
  currentStep: flow.start,
@@ -54,15 +82,14 @@ export function FlowRunner(props) {
54
82
  // This state is never used directly.
55
83
  // It only exists to force a re-render when event channels change.
56
84
  const [_tick, setTick] = useState(0);
57
- // NEW:
58
- // Subscribe to every provided channel.
59
- // When any channel emits, we trigger a re-render of this FlowRunner.
85
+ // Subscribe to every provided channel and keep subscriptions in sync
86
+ // with the current eventChannels prop.
60
87
  useEffect(() => {
61
- if (!eventChannels)
88
+ if (!resolvedEventChannels)
62
89
  return;
63
- const unsubs = Object.values(eventChannels).map((ch) => ch.subscribe(() => setTick((x) => x + 1)));
90
+ const unsubs = Object.values(resolvedEventChannels).map((ch) => ch.subscribe(() => setTick((x) => x + 1)));
64
91
  return () => unsubs.forEach((u) => u());
65
- }, []);
92
+ }, [resolvedEventChannels]);
66
93
  const { currentStep, data } = state;
67
94
  const applyTransition = (nextStepName) => {
68
95
  if (!isMountedRef.current)
@@ -97,9 +124,9 @@ export function FlowRunner(props) {
97
124
  (async () => {
98
125
  try {
99
126
  setBusy(true);
100
- const input = actionStep.input(state.data, eventChannels);
101
- const output = await actionStep.action(input, state.data, eventChannels);
102
- const next = await actionStep.onOutput(state.data, output, eventChannels);
127
+ const input = actionStep.input(state.data, resolvedEventChannels);
128
+ const output = await actionStep.action(input, state.data, resolvedEventChannels);
129
+ const next = await actionStep.onOutput(state.data, output, resolvedEventChannels);
103
130
  applyTransition(next);
104
131
  }
105
132
  catch (e) {
@@ -132,11 +159,11 @@ export function FlowRunner(props) {
132
159
  // -----------------------
133
160
  const uiStep = step;
134
161
  const ViewComponent = uiStep.view;
135
- const input = uiStep.input(data, eventChannels);
162
+ const input = uiStep.input(data, resolvedEventChannels);
136
163
  const outputHandle = {
137
164
  emit: async (output) => {
138
165
  try {
139
- const next = await uiStep.onOutput(data, output, eventChannels);
166
+ const next = await uiStep.onOutput(data, output, resolvedEventChannels);
140
167
  applyTransition(next);
141
168
  }
142
169
  catch (e) {
package/dist/flow.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"flow.js","sourceRoot":"","sources":["../src/flow.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"flow.js","sourceRoot":"","sources":["../src/flow.tsx"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myriadcodelabs/uiflow",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Explicit, code-first UI flow orchestration for React.",
5
5
  "keywords": [],
6
6
  "author": "Muhammad Ismail Khan",
@@ -9,8 +9,16 @@
9
9
  "react": ">=18"
10
10
  },
11
11
  "devDependencies": {
12
+ "@testing-library/jest-dom": "^6.9.1",
13
+ "@testing-library/react": "^16.3.2",
14
+ "@testing-library/user-event": "^14.6.1",
12
15
  "@types/react": "^19.2.10",
13
- "typescript": "^5.9.3"
16
+ "@types/react-dom": "^19.2.3",
17
+ "jsdom": "^28.0.0",
18
+ "react": "^19.2.4",
19
+ "react-dom": "^19.2.4",
20
+ "typescript": "^5.9.3",
21
+ "vitest": "^4.0.18"
14
22
  },
15
23
  "type": "module",
16
24
  "files": [
@@ -25,7 +33,8 @@
25
33
  }
26
34
  },
27
35
  "scripts": {
28
- "test": "echo \"Error: no test specified\" && exit 1",
36
+ "test": "vitest run",
37
+ "test:watch": "vitest",
29
38
  "clean": "rimraf dist",
30
39
  "build": "pnpm run clean && tsc"
31
40
  }