@contractspec/lib.presentation-runtime-react 11.0.0 → 13.0.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/index.js CHANGED
@@ -1,130 +1,367 @@
1
- import { WorkflowStepRenderer } from "./WorkflowStepRenderer.js";
2
- import { WorkflowStepper } from "./WorkflowStepper.js";
3
- import { useWorkflow } from "./useWorkflow.js";
4
- import * as React from "react";
5
- import { useForm } from "@contractspec/lib.ui-kit-web/ui/form";
1
+ // @bun
2
+ // src/WorkflowStepRenderer.tsx
3
+ import { EmptyState, LoaderBlock } from "@contractspec/lib.design-system";
4
+ import { jsxDEV } from "react/jsx-dev-runtime";
5
+ function WorkflowStepRenderer({
6
+ spec,
7
+ state,
8
+ className,
9
+ renderHumanStep,
10
+ renderAutomationStep,
11
+ renderDecisionStep,
12
+ loadingFallback,
13
+ completedFallback,
14
+ cancelledFallback,
15
+ failedFallback
16
+ }) {
17
+ const steps = spec.definition.steps;
18
+ if (!steps.length) {
19
+ return /* @__PURE__ */ jsxDEV("div", {
20
+ className,
21
+ children: /* @__PURE__ */ jsxDEV(EmptyState, {
22
+ title: "No steps defined",
23
+ description: "Add at least one step to this workflow to render it."
24
+ }, undefined, false, undefined, this)
25
+ }, undefined, false, undefined, this);
26
+ }
27
+ if (!state) {
28
+ return /* @__PURE__ */ jsxDEV("div", {
29
+ className,
30
+ children: loadingFallback ?? /* @__PURE__ */ jsxDEV(LoaderBlock, {
31
+ label: "Loading workflow",
32
+ description: "Fetching workflow state..."
33
+ }, undefined, false, undefined, this)
34
+ }, undefined, false, undefined, this);
35
+ }
36
+ const lastExecution = state.history.at(-1);
37
+ if (state.status === "failed") {
38
+ return /* @__PURE__ */ jsxDEV("div", {
39
+ className,
40
+ children: failedFallback?.(state, lastExecution) ?? /* @__PURE__ */ jsxDEV(EmptyState, {
41
+ title: "Workflow failed",
42
+ description: lastExecution?.error ?? "Fix the underlying issue and retry the step."
43
+ }, undefined, false, undefined, this)
44
+ }, undefined, false, undefined, this);
45
+ }
46
+ if (state.status === "cancelled") {
47
+ return /* @__PURE__ */ jsxDEV("div", {
48
+ className,
49
+ children: cancelledFallback ?? /* @__PURE__ */ jsxDEV(EmptyState, {
50
+ title: "Workflow cancelled",
51
+ description: "This workflow has been cancelled. Restart it to resume."
52
+ }, undefined, false, undefined, this)
53
+ }, undefined, false, undefined, this);
54
+ }
55
+ if (state.status === "completed") {
56
+ return /* @__PURE__ */ jsxDEV("div", {
57
+ className,
58
+ children: completedFallback ?? /* @__PURE__ */ jsxDEV(EmptyState, {
59
+ title: "Workflow complete",
60
+ description: "All steps have been executed successfully."
61
+ }, undefined, false, undefined, this)
62
+ }, undefined, false, undefined, this);
63
+ }
64
+ const activeStep = steps.find((step) => step.id === state.currentStep) ?? steps[0];
65
+ if (!activeStep) {
66
+ return /* @__PURE__ */ jsxDEV("div", {
67
+ className,
68
+ children: /* @__PURE__ */ jsxDEV(EmptyState, {
69
+ title: "No active step",
70
+ description: "This workflow has no active step."
71
+ }, undefined, false, undefined, this)
72
+ }, undefined, false, undefined, this);
73
+ }
74
+ switch (activeStep.type) {
75
+ case "human": {
76
+ const form = activeStep.action?.form;
77
+ if (form && renderHumanStep) {
78
+ return /* @__PURE__ */ jsxDEV("div", {
79
+ className,
80
+ children: renderHumanStep(form, activeStep)
81
+ }, undefined, false, undefined, this);
82
+ }
83
+ return /* @__PURE__ */ jsxDEV("div", {
84
+ className,
85
+ children: /* @__PURE__ */ jsxDEV(EmptyState, {
86
+ title: "Form renderer missing",
87
+ description: "Provide renderHumanStep to render this human step's form."
88
+ }, undefined, false, undefined, this)
89
+ }, undefined, false, undefined, this);
90
+ }
91
+ case "automation": {
92
+ if (renderAutomationStep) {
93
+ return /* @__PURE__ */ jsxDEV("div", {
94
+ className,
95
+ children: renderAutomationStep(activeStep)
96
+ }, undefined, false, undefined, this);
97
+ }
98
+ return /* @__PURE__ */ jsxDEV("div", {
99
+ className,
100
+ children: /* @__PURE__ */ jsxDEV(EmptyState, {
101
+ title: "Automation step in progress",
102
+ description: `Waiting for automation "${activeStep.label}" to finish.`
103
+ }, undefined, false, undefined, this)
104
+ }, undefined, false, undefined, this);
105
+ }
106
+ case "decision": {
107
+ if (renderDecisionStep) {
108
+ return /* @__PURE__ */ jsxDEV("div", {
109
+ className,
110
+ children: renderDecisionStep(activeStep)
111
+ }, undefined, false, undefined, this);
112
+ }
113
+ return /* @__PURE__ */ jsxDEV("div", {
114
+ className,
115
+ children: /* @__PURE__ */ jsxDEV(EmptyState, {
116
+ title: "Decision step awaiting input",
117
+ description: "Provide a custom decision renderer via renderDecisionStep."
118
+ }, undefined, false, undefined, this)
119
+ }, undefined, false, undefined, this);
120
+ }
121
+ default:
122
+ return /* @__PURE__ */ jsxDEV("div", {
123
+ className,
124
+ children: /* @__PURE__ */ jsxDEV(EmptyState, {
125
+ title: "Unknown step type",
126
+ description: `Step "${activeStep.id}" has an unsupported type.`
127
+ }, undefined, false, undefined, this)
128
+ }, undefined, false, undefined, this);
129
+ }
130
+ }
6
131
 
7
- //#region src/index.ts
8
- function usePresentationController({ defaults, form: formOpts, toVariables, fetcher, toChips, useUrlState, replace }) {
9
- const url = useUrlState({
10
- defaults,
11
- replace
12
- });
13
- const form = useForm({
14
- defaultValues: formOpts.defaultValues,
15
- resolver: formOpts.resolver
16
- });
17
- React.useEffect(() => {
18
- form.reset({
19
- ...form.getValues(),
20
- ...url.state.filters
21
- });
22
- }, [url.state.filters]);
23
- const submitFilters = form.handleSubmit((values) => {
24
- url.setState({
25
- filters: values,
26
- page: 1
27
- });
28
- });
29
- const setSearch = React.useCallback((q) => url.setState({
30
- q,
31
- page: 1
32
- }), [url]);
33
- const variables = React.useMemo(() => toVariables(url.state), [url.state, toVariables]);
34
- const [data, setData] = React.useState([]);
35
- const [loading, setLoading] = React.useState(false);
36
- const [error, setError] = React.useState(null);
37
- const [totalItems, setTotalItems] = React.useState(void 0);
38
- const [totalPages, setTotalPages] = React.useState(void 0);
39
- const refetch = React.useCallback(async () => {
40
- setLoading(true);
41
- setError(null);
42
- try {
43
- const out = await fetcher(variables);
44
- setData(out.items);
45
- setTotalItems(out.totalItems);
46
- setTotalPages(out.totalPages);
47
- } catch (e) {
48
- setError(e);
49
- } finally {
50
- setLoading(false);
51
- }
52
- }, [variables, fetcher]);
53
- React.useEffect(() => {
54
- refetch();
55
- }, [refetch]);
56
- return {
57
- form,
58
- url,
59
- variables,
60
- data,
61
- loading,
62
- error,
63
- totalItems,
64
- totalPages,
65
- refetch,
66
- chips: React.useMemo(() => toChips ? toChips(url.state.filters || {}, url.setFilter) : [], [url.state.filters, toChips]),
67
- setSearch,
68
- submitFilters,
69
- clearAll: React.useCallback(() => {
70
- form.reset(formOpts.defaultValues);
71
- url.setState({
72
- filters: {},
73
- page: 1
74
- });
75
- }, [
76
- form,
77
- formOpts.defaultValues,
78
- url
79
- ])
80
- };
132
+ // src/WorkflowStepper.tsx
133
+ import { Stepper } from "@contractspec/lib.design-system";
134
+ import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
135
+ function WorkflowStepper({
136
+ spec,
137
+ state,
138
+ className,
139
+ showLabels = false
140
+ }) {
141
+ const steps = spec.definition.steps;
142
+ const total = steps.length;
143
+ const current = computeCurrent(steps, state);
144
+ return /* @__PURE__ */ jsxDEV2("div", {
145
+ className: ["flex flex-col gap-2", className].filter(Boolean).join(" "),
146
+ children: [
147
+ /* @__PURE__ */ jsxDEV2(Stepper, {
148
+ current,
149
+ total
150
+ }, undefined, false, undefined, this),
151
+ showLabels && total > 0 && /* @__PURE__ */ jsxDEV2("ol", {
152
+ className: "text-muted-foreground flex flex-wrap gap-2 text-sm",
153
+ children: steps.map((step, index) => {
154
+ const isActive = state?.status === "completed" ? index === total - 1 : step.id === state?.currentStep;
155
+ return /* @__PURE__ */ jsxDEV2("li", {
156
+ className: [
157
+ "rounded border px-2 py-1",
158
+ isActive ? "border-primary text-primary" : "border-border"
159
+ ].join(" "),
160
+ children: /* @__PURE__ */ jsxDEV2("span", {
161
+ className: "font-medium",
162
+ children: step.label
163
+ }, undefined, false, undefined, this)
164
+ }, step.id, false, undefined, this);
165
+ })
166
+ }, undefined, false, undefined, this)
167
+ ]
168
+ }, undefined, true, undefined, this);
81
169
  }
82
- function useListCoordinator({ defaults, form: formOpts, toVariables, toChips, useUrlState, replace }) {
83
- const url = useUrlState({
84
- defaults,
85
- replace
86
- });
87
- const form = useForm({
88
- defaultValues: formOpts.defaultValues,
89
- resolver: formOpts.resolver
90
- });
91
- React.useEffect(() => {
92
- form.reset({
93
- ...form.getValues(),
94
- ...url.state.filters
95
- });
96
- }, [url.state.filters]);
97
- const submitFilters = form.handleSubmit((values) => {
98
- url.setState({
99
- filters: values,
100
- page: 1
101
- });
102
- });
103
- const setSearch = React.useCallback((q) => url.setState({
104
- q,
105
- page: 1
106
- }), [url]);
107
- return {
108
- form,
109
- url,
110
- variables: React.useMemo(() => toVariables(url.state), [url.state, toVariables]),
111
- chips: React.useMemo(() => toChips ? toChips(url.state.filters || {}, url.setFilter) : [], [url.state.filters, toChips]),
112
- setSearch,
113
- submitFilters,
114
- clearAll: React.useCallback(() => {
115
- form.reset(formOpts.defaultValues);
116
- url.setState({
117
- filters: {},
118
- page: 1
119
- });
120
- }, [
121
- form,
122
- formOpts.defaultValues,
123
- url
124
- ])
125
- };
170
+ function computeCurrent(steps, state) {
171
+ if (!steps.length)
172
+ return 0;
173
+ if (!state)
174
+ return 1;
175
+ if (state.status === "completed")
176
+ return steps.length;
177
+ const idx = steps.findIndex((step) => step.id === state.currentStep);
178
+ return idx === -1 ? 1 : idx + 1;
126
179
  }
127
180
 
128
- //#endregion
129
- export { WorkflowStepRenderer, WorkflowStepper, useListCoordinator, usePresentationController, useWorkflow };
130
- //# sourceMappingURL=index.js.map
181
+ // src/useWorkflow.ts
182
+ import * as React from "react";
183
+ function useWorkflow({
184
+ workflowId,
185
+ runner,
186
+ autoRefresh = true,
187
+ refreshIntervalMs = 2000
188
+ }) {
189
+ const isMounted = React.useRef(true);
190
+ const [state, setState] = React.useState(null);
191
+ const [isLoading, setIsLoading] = React.useState(true);
192
+ const [error, setError] = React.useState(null);
193
+ const [isExecuting, setIsExecuting] = React.useState(false);
194
+ const refresh = React.useCallback(async () => {
195
+ try {
196
+ setIsLoading(true);
197
+ const next = await runner.getState(workflowId);
198
+ if (!isMounted.current)
199
+ return;
200
+ setState(next);
201
+ setError(null);
202
+ } catch (err) {
203
+ if (!isMounted.current)
204
+ return;
205
+ setError(err instanceof Error ? err : new Error(String(err)));
206
+ } finally {
207
+ if (isMounted.current)
208
+ setIsLoading(false);
209
+ }
210
+ }, [runner, workflowId]);
211
+ const executeStep = React.useCallback(async (input) => {
212
+ setIsExecuting(true);
213
+ try {
214
+ await runner.executeStep(workflowId, input);
215
+ await refresh();
216
+ } catch (err) {
217
+ if (isMounted.current) {
218
+ setError(err instanceof Error ? err : new Error(String(err)));
219
+ }
220
+ throw err;
221
+ } finally {
222
+ if (isMounted.current)
223
+ setIsExecuting(false);
224
+ }
225
+ }, [runner, workflowId, refresh]);
226
+ const cancel = React.useCallback(async () => {
227
+ await runner.cancel(workflowId);
228
+ await refresh();
229
+ }, [runner, workflowId, refresh]);
230
+ React.useEffect(() => {
231
+ isMounted.current = true;
232
+ refresh();
233
+ if (!autoRefresh) {
234
+ return () => {
235
+ isMounted.current = false;
236
+ };
237
+ }
238
+ const interval = setInterval(() => {
239
+ refresh();
240
+ }, refreshIntervalMs);
241
+ return () => {
242
+ isMounted.current = false;
243
+ clearInterval(interval);
244
+ };
245
+ }, [refresh, autoRefresh, refreshIntervalMs]);
246
+ return {
247
+ state,
248
+ isLoading,
249
+ error,
250
+ isExecuting,
251
+ refresh,
252
+ executeStep,
253
+ cancel
254
+ };
255
+ }
256
+
257
+ // src/index.ts
258
+ import * as React2 from "react";
259
+ import { useForm } from "@contractspec/lib.ui-kit-web/ui/form";
260
+ function usePresentationController({
261
+ defaults,
262
+ form: formOpts,
263
+ toVariables,
264
+ fetcher,
265
+ toChips,
266
+ useUrlState,
267
+ replace
268
+ }) {
269
+ const url = useUrlState({ defaults, replace });
270
+ const form = useForm({
271
+ defaultValues: formOpts.defaultValues,
272
+ resolver: formOpts.resolver
273
+ });
274
+ React2.useEffect(() => {
275
+ form.reset({ ...form.getValues(), ...url.state.filters });
276
+ }, [url.state.filters]);
277
+ const submitFilters = form.handleSubmit((values) => {
278
+ url.setState({ filters: values, page: 1 });
279
+ });
280
+ const setSearch = React2.useCallback((q) => url.setState({ q, page: 1 }), [url]);
281
+ const variables = React2.useMemo(() => toVariables(url.state), [url.state, toVariables]);
282
+ const [data, setData] = React2.useState([]);
283
+ const [loading, setLoading] = React2.useState(false);
284
+ const [error, setError] = React2.useState(null);
285
+ const [totalItems, setTotalItems] = React2.useState(undefined);
286
+ const [totalPages, setTotalPages] = React2.useState(undefined);
287
+ const refetch = React2.useCallback(async () => {
288
+ setLoading(true);
289
+ setError(null);
290
+ try {
291
+ const out = await fetcher(variables);
292
+ setData(out.items);
293
+ setTotalItems(out.totalItems);
294
+ setTotalPages(out.totalPages);
295
+ } catch (e) {
296
+ setError(e);
297
+ } finally {
298
+ setLoading(false);
299
+ }
300
+ }, [variables, fetcher]);
301
+ React2.useEffect(() => {
302
+ refetch();
303
+ }, [refetch]);
304
+ const chips = React2.useMemo(() => toChips ? toChips(url.state.filters || {}, url.setFilter) : [], [url.state.filters, toChips]);
305
+ const clearAll = React2.useCallback(() => {
306
+ form.reset(formOpts.defaultValues);
307
+ url.setState({ filters: {}, page: 1 });
308
+ }, [form, formOpts.defaultValues, url]);
309
+ return {
310
+ form,
311
+ url,
312
+ variables,
313
+ data,
314
+ loading,
315
+ error,
316
+ totalItems,
317
+ totalPages,
318
+ refetch,
319
+ chips,
320
+ setSearch,
321
+ submitFilters,
322
+ clearAll
323
+ };
324
+ }
325
+ function useListCoordinator({
326
+ defaults,
327
+ form: formOpts,
328
+ toVariables,
329
+ toChips,
330
+ useUrlState,
331
+ replace
332
+ }) {
333
+ const url = useUrlState({ defaults, replace });
334
+ const form = useForm({
335
+ defaultValues: formOpts.defaultValues,
336
+ resolver: formOpts.resolver
337
+ });
338
+ React2.useEffect(() => {
339
+ form.reset({ ...form.getValues(), ...url.state.filters });
340
+ }, [url.state.filters]);
341
+ const submitFilters = form.handleSubmit((values) => {
342
+ url.setState({ filters: values, page: 1 });
343
+ });
344
+ const setSearch = React2.useCallback((q) => url.setState({ q, page: 1 }), [url]);
345
+ const variables = React2.useMemo(() => toVariables(url.state), [url.state, toVariables]);
346
+ const chips = React2.useMemo(() => toChips ? toChips(url.state.filters || {}, url.setFilter) : [], [url.state.filters, toChips]);
347
+ const clearAll = React2.useCallback(() => {
348
+ form.reset(formOpts.defaultValues);
349
+ url.setState({ filters: {}, page: 1 });
350
+ }, [form, formOpts.defaultValues, url]);
351
+ return {
352
+ form,
353
+ url,
354
+ variables,
355
+ chips,
356
+ setSearch,
357
+ submitFilters,
358
+ clearAll
359
+ };
360
+ }
361
+ export {
362
+ useWorkflow,
363
+ usePresentationController,
364
+ useListCoordinator,
365
+ WorkflowStepper,
366
+ WorkflowStepRenderer
367
+ };
@@ -0,0 +1 @@
1
+ // @bun
@@ -1,27 +1,18 @@
1
- import { WorkflowRunner, WorkflowState } from "@contractspec/lib.contracts/workflow";
2
-
3
- //#region src/useWorkflow.d.ts
4
- interface UseWorkflowOptions {
5
- workflowId: string;
6
- runner: WorkflowRunner;
7
- autoRefresh?: boolean;
8
- refreshIntervalMs?: number;
1
+ import type { WorkflowRunner, WorkflowState } from '@contractspec/lib.contracts/workflow';
2
+ export interface UseWorkflowOptions {
3
+ workflowId: string;
4
+ runner: WorkflowRunner;
5
+ autoRefresh?: boolean;
6
+ refreshIntervalMs?: number;
9
7
  }
10
- interface UseWorkflowResult {
11
- state: WorkflowState | null;
12
- isLoading: boolean;
13
- error: Error | null;
14
- isExecuting: boolean;
15
- refresh: () => Promise<void>;
16
- executeStep: (input?: unknown) => Promise<void>;
17
- cancel: () => Promise<void>;
8
+ export interface UseWorkflowResult {
9
+ state: WorkflowState | null;
10
+ isLoading: boolean;
11
+ error: Error | null;
12
+ isExecuting: boolean;
13
+ refresh: () => Promise<void>;
14
+ executeStep: (input?: unknown) => Promise<void>;
15
+ cancel: () => Promise<void>;
18
16
  }
19
- declare function useWorkflow({
20
- workflowId,
21
- runner,
22
- autoRefresh,
23
- refreshIntervalMs
24
- }: UseWorkflowOptions): UseWorkflowResult;
25
- //#endregion
26
- export { UseWorkflowOptions, UseWorkflowResult, useWorkflow };
17
+ export declare function useWorkflow({ workflowId, runner, autoRefresh, refreshIntervalMs, }: UseWorkflowOptions): UseWorkflowResult;
27
18
  //# sourceMappingURL=useWorkflow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useWorkflow.d.ts","names":[],"sources":["../src/useWorkflow.ts"],"mappings":";;;UAMiB,kBAAA;EACf,UAAA;EACA,MAAA,EAAQ,cAAA;EACR,WAAA;EACA,iBAAA;AAAA;AAAA,UAGe,iBAAA;EACf,KAAA,EAAO,aAAA;EACP,SAAA;EACA,KAAA,EAAO,KAAA;EACP,WAAA;EACA,OAAA,QAAe,OAAA;EACf,WAAA,GAAc,KAAA,eAAoB,OAAA;EAClC,MAAA,QAAc,OAAA;AAAA;AAAA,iBAGA,WAAA,CAAA;EACd,UAAA;EACA,MAAA;EACA,WAAA;EACA;AAAA,GACC,kBAAA,GAAqB,iBAAA"}
1
+ {"version":3,"file":"useWorkflow.d.ts","sourceRoot":"","sources":["../src/useWorkflow.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,cAAc,EACd,aAAa,EACd,MAAM,sCAAsC,CAAC;AAE9C,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,cAAc,CAAC;IACvB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,WAAW,EAAE,OAAO,CAAC;IACrB,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,WAAW,EAAE,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED,wBAAgB,WAAW,CAAC,EAC1B,UAAU,EACV,MAAM,EACN,WAAkB,EAClB,iBAAwB,GACzB,EAAE,kBAAkB,GAAG,iBAAiB,CAuExC"}