@lssm/lib.presentation-runtime-react 0.0.0-canary-20251120170226

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 ADDED
@@ -0,0 +1,43 @@
1
+ # @lssm/lib.presentation-runtime-react
2
+
3
+ React bindings for ContractSpec presentations (Workflows, DataViews).
4
+
5
+ ## Purpose
6
+
7
+ To render ContractSpec-defined UIs in standard React web applications.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ npm install @lssm/lib.presentation-runtime-react
13
+ # or
14
+ bun add @lssm/lib.presentation-runtime-react
15
+ ```
16
+
17
+ ## Key Concepts
18
+
19
+ - **useWorkflow**: Hook to drive a multi-step workflow.
20
+ - **WorkflowStepper**: UI component to show progress.
21
+ - **WorkflowStepRenderer**: Component to render the current step's form or content.
22
+
23
+ ## Usage
24
+
25
+ ```tsx
26
+ import {
27
+ useWorkflow,
28
+ WorkflowStepRenderer,
29
+ } from '@lssm/lib.presentation-runtime-react';
30
+ import { MyWorkflowSpec } from './specs';
31
+
32
+ export function WorkflowPage() {
33
+ const workflow = useWorkflow(MyWorkflowSpec);
34
+
35
+ return (
36
+ <div>
37
+ <WorkflowStepRenderer workflow={workflow} />
38
+ <button onClick={workflow.next}>Next</button>
39
+ </div>
40
+ );
41
+ }
42
+ ```
43
+
@@ -0,0 +1,32 @@
1
+ import * as React from "react";
2
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
3
+ import { FormRef, Step, StepExecution, WorkflowSpec, WorkflowState } from "@lssm/lib.contracts/workflow";
4
+
5
+ //#region src/WorkflowStepRenderer.d.ts
6
+ interface WorkflowStepRendererProps {
7
+ spec: WorkflowSpec;
8
+ state: WorkflowState | null;
9
+ className?: string;
10
+ renderHumanStep?: (form: FormRef, step: Step) => React.ReactNode;
11
+ renderAutomationStep?: (step: Step) => React.ReactNode;
12
+ renderDecisionStep?: (step: Step) => React.ReactNode;
13
+ loadingFallback?: React.ReactNode;
14
+ completedFallback?: React.ReactNode;
15
+ cancelledFallback?: React.ReactNode;
16
+ failedFallback?: (state: WorkflowState, last: StepExecution | undefined) => React.ReactNode;
17
+ }
18
+ declare function WorkflowStepRenderer({
19
+ spec,
20
+ state,
21
+ className,
22
+ renderHumanStep,
23
+ renderAutomationStep,
24
+ renderDecisionStep,
25
+ loadingFallback,
26
+ completedFallback,
27
+ cancelledFallback,
28
+ failedFallback
29
+ }: WorkflowStepRendererProps): react_jsx_runtime0.JSX.Element;
30
+ //#endregion
31
+ export { WorkflowStepRenderer, WorkflowStepRendererProps };
32
+ //# sourceMappingURL=WorkflowStepRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkflowStepRenderer.d.ts","names":[],"sources":["../src/WorkflowStepRenderer.tsx"],"sourcesContent":[],"mappings":";;;;;UAUiB,yBAAA;QACT;SACC;EAFQ,SAAA,CAAA,EAAA,MAAA;EACT,eAAA,CAAA,EAAA,CAAA,IAAA,EAGmB,OAHnB,EAAA,IAAA,EAGkC,IAHlC,EAAA,GAG2C,KAAA,CAAM,SAHjD;EACC,oBAAA,CAAA,EAAA,CAAA,IAAA,EAGuB,IAHvB,EAAA,GAGgC,KAAA,CAAM,SAHtC;EAEkB,kBAAA,CAAA,EAAA,CAAA,IAAA,EAEG,IAFH,EAAA,GAEY,KAAA,CAAM,SAFlB;EAAe,eAAA,CAAA,EAGtB,KAAA,CAAM,SAHgB;EAAS,iBAAM,CAAA,EAInC,KAAA,CAAM,SAJ6B;EACzB,iBAAA,CAAA,EAIV,KAAA,CAAM,SAJI;EAAS,cAAM,CAAA,EAAA,CAAA,KAAA,EAMpC,aANoC,EAAA,IAAA,EAOrC,aAPqC,GAAA,SAAA,EAAA,GAQxC,KAAA,CAAM,SARkC;;AACF,iBAU7B,oBAAA,CAV6B;EAAA,IAAA;EAAA,KAAA;EAAA,SAAA;EAAA,eAAA;EAAA,oBAAA;EAAA,kBAAA;EAAA,eAAA;EAAA,iBAAA;EAAA,iBAAA;EAAA;AAAA,CAAA,EAqB1C,yBArB0C,CAAA,EAqBjB,kBAAA,CAAA,GAAA,CAAA,OArBiB"}
@@ -0,0 +1,103 @@
1
+ import "react";
2
+ import { EmptyState, LoaderBlock } from "@lssm/lib.design-system";
3
+ import { jsx } from "react/jsx-runtime";
4
+
5
+ //#region src/WorkflowStepRenderer.tsx
6
+ function WorkflowStepRenderer({ spec, state, className, renderHumanStep, renderAutomationStep, renderDecisionStep, loadingFallback, completedFallback, cancelledFallback, failedFallback }) {
7
+ const steps = spec.definition.steps;
8
+ if (!steps.length) return /* @__PURE__ */ jsx("div", {
9
+ className,
10
+ children: /* @__PURE__ */ jsx(EmptyState, {
11
+ title: "No steps defined",
12
+ description: "Add at least one step to this workflow to render it."
13
+ })
14
+ });
15
+ if (!state) return /* @__PURE__ */ jsx("div", {
16
+ className,
17
+ children: loadingFallback ?? /* @__PURE__ */ jsx(LoaderBlock, {
18
+ label: "Loading workflow",
19
+ description: "Fetching workflow state..."
20
+ })
21
+ });
22
+ const lastExecution = state.history.at(-1);
23
+ if (state.status === "failed") return /* @__PURE__ */ jsx("div", {
24
+ className,
25
+ children: failedFallback?.(state, lastExecution) ?? /* @__PURE__ */ jsx(EmptyState, {
26
+ title: "Workflow failed",
27
+ description: lastExecution?.error ?? "Fix the underlying issue and retry the step."
28
+ })
29
+ });
30
+ if (state.status === "cancelled") return /* @__PURE__ */ jsx("div", {
31
+ className,
32
+ children: cancelledFallback ?? /* @__PURE__ */ jsx(EmptyState, {
33
+ title: "Workflow cancelled",
34
+ description: "This workflow has been cancelled. Restart it to resume."
35
+ })
36
+ });
37
+ if (state.status === "completed") return /* @__PURE__ */ jsx("div", {
38
+ className,
39
+ children: completedFallback ?? /* @__PURE__ */ jsx(EmptyState, {
40
+ title: "Workflow complete",
41
+ description: "All steps have been executed successfully."
42
+ })
43
+ });
44
+ const activeStep = steps.find((step) => step.id === state.currentStep) ?? steps[0];
45
+ if (!activeStep) return /* @__PURE__ */ jsx("div", {
46
+ className,
47
+ children: /* @__PURE__ */ jsx(EmptyState, {
48
+ title: "No active step",
49
+ description: "This workflow has no active step."
50
+ })
51
+ });
52
+ switch (activeStep.type) {
53
+ case "human": {
54
+ const form = activeStep.action?.form;
55
+ if (form && renderHumanStep) return /* @__PURE__ */ jsx("div", {
56
+ className,
57
+ children: renderHumanStep(form, activeStep)
58
+ });
59
+ return /* @__PURE__ */ jsx("div", {
60
+ className,
61
+ children: /* @__PURE__ */ jsx(EmptyState, {
62
+ title: "Form renderer missing",
63
+ description: "Provide renderHumanStep to render this human step's form."
64
+ })
65
+ });
66
+ }
67
+ case "automation":
68
+ if (renderAutomationStep) return /* @__PURE__ */ jsx("div", {
69
+ className,
70
+ children: renderAutomationStep(activeStep)
71
+ });
72
+ return /* @__PURE__ */ jsx("div", {
73
+ className,
74
+ children: /* @__PURE__ */ jsx(EmptyState, {
75
+ title: "Automation step in progress",
76
+ description: `Waiting for automation "${activeStep.label}" to finish.`
77
+ })
78
+ });
79
+ case "decision":
80
+ if (renderDecisionStep) return /* @__PURE__ */ jsx("div", {
81
+ className,
82
+ children: renderDecisionStep(activeStep)
83
+ });
84
+ return /* @__PURE__ */ jsx("div", {
85
+ className,
86
+ children: /* @__PURE__ */ jsx(EmptyState, {
87
+ title: "Decision step awaiting input",
88
+ description: "Provide a custom decision renderer via renderDecisionStep."
89
+ })
90
+ });
91
+ default: return /* @__PURE__ */ jsx("div", {
92
+ className,
93
+ children: /* @__PURE__ */ jsx(EmptyState, {
94
+ title: "Unknown step type",
95
+ description: `Step "${activeStep.id}" has an unsupported type.`
96
+ })
97
+ });
98
+ }
99
+ }
100
+
101
+ //#endregion
102
+ export { WorkflowStepRenderer };
103
+ //# sourceMappingURL=WorkflowStepRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkflowStepRenderer.js","names":[],"sources":["../src/WorkflowStepRenderer.tsx"],"sourcesContent":["import * as React from 'react';\nimport { EmptyState, LoaderBlock } from '@lssm/lib.design-system';\nimport type {\n FormRef,\n Step,\n WorkflowSpec,\n WorkflowState,\n StepExecution,\n} from '@lssm/lib.contracts/workflow';\n\nexport interface WorkflowStepRendererProps {\n spec: WorkflowSpec;\n state: WorkflowState | null;\n className?: string;\n renderHumanStep?: (form: FormRef, step: Step) => React.ReactNode;\n renderAutomationStep?: (step: Step) => React.ReactNode;\n renderDecisionStep?: (step: Step) => React.ReactNode;\n loadingFallback?: React.ReactNode;\n completedFallback?: React.ReactNode;\n cancelledFallback?: React.ReactNode;\n failedFallback?: (\n state: WorkflowState,\n last: StepExecution | undefined\n ) => React.ReactNode;\n}\n\nexport function WorkflowStepRenderer({\n spec,\n state,\n className,\n renderHumanStep,\n renderAutomationStep,\n renderDecisionStep,\n loadingFallback,\n completedFallback,\n cancelledFallback,\n failedFallback,\n}: WorkflowStepRendererProps) {\n const steps = spec.definition.steps;\n if (!steps.length) {\n return (\n <div className={className}>\n <EmptyState\n title=\"No steps defined\"\n description=\"Add at least one step to this workflow to render it.\"\n />\n </div>\n );\n }\n\n if (!state) {\n return (\n <div className={className}>\n {loadingFallback ?? (\n <LoaderBlock\n label=\"Loading workflow\"\n description=\"Fetching workflow state...\"\n />\n )}\n </div>\n );\n }\n\n const lastExecution = state.history.at(-1);\n\n if (state.status === 'failed') {\n return (\n <div className={className}>\n {failedFallback?.(state, lastExecution) ?? (\n <EmptyState\n title=\"Workflow failed\"\n description={\n lastExecution?.error ??\n 'Fix the underlying issue and retry the step.'\n }\n />\n )}\n </div>\n );\n }\n\n if (state.status === 'cancelled') {\n return (\n <div className={className}>\n {cancelledFallback ?? (\n <EmptyState\n title=\"Workflow cancelled\"\n description=\"This workflow has been cancelled. Restart it to resume.\"\n />\n )}\n </div>\n );\n }\n\n if (state.status === 'completed') {\n return (\n <div className={className}>\n {completedFallback ?? (\n <EmptyState\n title=\"Workflow complete\"\n description=\"All steps have been executed successfully.\"\n />\n )}\n </div>\n );\n }\n\n const activeStep =\n steps.find((step) => step.id === state.currentStep) ?? steps[0];\n\n if (!activeStep) {\n return (\n <div className={className}>\n <EmptyState\n title=\"No active step\"\n description=\"This workflow has no active step.\"\n />\n </div>\n );\n }\n\n switch (activeStep.type) {\n case 'human': {\n const form = activeStep.action?.form;\n if (form && renderHumanStep) {\n return (\n <div className={className}>{renderHumanStep(form, activeStep)}</div>\n );\n }\n return (\n <div className={className}>\n <EmptyState\n title=\"Form renderer missing\"\n description=\"Provide renderHumanStep to render this human step's form.\"\n />\n </div>\n );\n }\n case 'automation': {\n if (renderAutomationStep) {\n return (\n <div className={className}>{renderAutomationStep(activeStep)}</div>\n );\n }\n return (\n <div className={className}>\n <EmptyState\n title=\"Automation step in progress\"\n description={`Waiting for automation \"${activeStep.label}\" to finish.`}\n />\n </div>\n );\n }\n case 'decision': {\n if (renderDecisionStep) {\n return (\n <div className={className}>{renderDecisionStep(activeStep)}</div>\n );\n }\n return (\n <div className={className}>\n <EmptyState\n title=\"Decision step awaiting input\"\n description=\"Provide a custom decision renderer via renderDecisionStep.\"\n />\n </div>\n );\n }\n default:\n return (\n <div className={className}>\n <EmptyState\n title=\"Unknown step type\"\n description={`Step \"${activeStep.id}\" has an unsupported type.`}\n />\n </div>\n );\n }\n}\n"],"mappings":";;;;;AA0BA,SAAgB,qBAAqB,EACnC,MACA,OACA,WACA,iBACA,sBACA,oBACA,iBACA,mBACA,mBACA,kBAC4B;CAC5B,MAAM,QAAQ,KAAK,WAAW;AAC9B,KAAI,CAAC,MAAM,OACT,QACE,oBAAC;EAAe;YACd,oBAAC;GACC,OAAM;GACN,aAAY;IACZ;GACE;AAIV,KAAI,CAAC,MACH,QACE,oBAAC;EAAe;YACb,mBACC,oBAAC;GACC,OAAM;GACN,aAAY;IACZ;GAEA;CAIV,MAAM,gBAAgB,MAAM,QAAQ,GAAG,GAAG;AAE1C,KAAI,MAAM,WAAW,SACnB,QACE,oBAAC;EAAe;YACb,iBAAiB,OAAO,cAAc,IACrC,oBAAC;GACC,OAAM;GACN,aACE,eAAe,SACf;IAEF;GAEA;AAIV,KAAI,MAAM,WAAW,YACnB,QACE,oBAAC;EAAe;YACb,qBACC,oBAAC;GACC,OAAM;GACN,aAAY;IACZ;GAEA;AAIV,KAAI,MAAM,WAAW,YACnB,QACE,oBAAC;EAAe;YACb,qBACC,oBAAC;GACC,OAAM;GACN,aAAY;IACZ;GAEA;CAIV,MAAM,aACJ,MAAM,MAAM,SAAS,KAAK,OAAO,MAAM,YAAY,IAAI,MAAM;AAE/D,KAAI,CAAC,WACH,QACE,oBAAC;EAAe;YACd,oBAAC;GACC,OAAM;GACN,aAAY;IACZ;GACE;AAIV,SAAQ,WAAW,MAAnB;EACE,KAAK,SAAS;GACZ,MAAM,OAAO,WAAW,QAAQ;AAChC,OAAI,QAAQ,gBACV,QACE,oBAAC;IAAe;cAAY,gBAAgB,MAAM,WAAW;KAAO;AAGxE,UACE,oBAAC;IAAe;cACd,oBAAC;KACC,OAAM;KACN,aAAY;MACZ;KACE;;EAGV,KAAK;AACH,OAAI,qBACF,QACE,oBAAC;IAAe;cAAY,qBAAqB,WAAW;KAAO;AAGvE,UACE,oBAAC;IAAe;cACd,oBAAC;KACC,OAAM;KACN,aAAa,2BAA2B,WAAW,MAAM;MACzD;KACE;EAGV,KAAK;AACH,OAAI,mBACF,QACE,oBAAC;IAAe;cAAY,mBAAmB,WAAW;KAAO;AAGrE,UACE,oBAAC;IAAe;cACd,oBAAC;KACC,OAAM;KACN,aAAY;MACZ;KACE;EAGV,QACE,QACE,oBAAC;GAAe;aACd,oBAAC;IACC,OAAM;IACN,aAAa,SAAS,WAAW,GAAG;KACpC;IACE"}
@@ -0,0 +1,19 @@
1
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
2
+ import { WorkflowSpec, WorkflowState } from "@lssm/lib.contracts/workflow";
3
+
4
+ //#region src/WorkflowStepper.d.ts
5
+ interface WorkflowStepperProps {
6
+ spec: WorkflowSpec;
7
+ state: WorkflowState | null;
8
+ className?: string;
9
+ showLabels?: boolean;
10
+ }
11
+ declare function WorkflowStepper({
12
+ spec,
13
+ state,
14
+ className,
15
+ showLabels
16
+ }: WorkflowStepperProps): react_jsx_runtime0.JSX.Element;
17
+ //#endregion
18
+ export { WorkflowStepper, WorkflowStepperProps };
19
+ //# sourceMappingURL=WorkflowStepper.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkflowStepper.d.ts","names":[],"sources":["../src/WorkflowStepper.tsx"],"sourcesContent":[],"mappings":";;;;UAIiB,oBAAA;QACT;SACC;EAFQ,SAAA,CAAA,EAAA,MAAA;EAOD,UAAA,CAAA,EAAA,OAAe;;AAE7B,iBAFc,eAAA,CAEd;EAAA,IAAA;EAAA,KAAA;EAAA,SAAA;EAAA;AAAA,CAAA,EAGC,oBAHD,CAAA,EAGqB,kBAAA,CAAA,GAAA,CAAA,OAHrB"}
@@ -0,0 +1,39 @@
1
+ import "react";
2
+ import { Stepper } from "@lssm/lib.design-system";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+
5
+ //#region src/WorkflowStepper.tsx
6
+ function WorkflowStepper({ spec, state, className, showLabels = false }) {
7
+ const steps = spec.definition.steps;
8
+ const total = steps.length;
9
+ const current = computeCurrent(steps, state);
10
+ return /* @__PURE__ */ jsxs("div", {
11
+ className: ["flex flex-col gap-2", className].filter(Boolean).join(" "),
12
+ children: [/* @__PURE__ */ jsx(Stepper, {
13
+ current,
14
+ total
15
+ }), showLabels && total > 0 && /* @__PURE__ */ jsx("ol", {
16
+ className: "text-muted-foreground flex flex-wrap gap-2 text-sm",
17
+ children: steps.map((step, index) => {
18
+ return /* @__PURE__ */ jsx("li", {
19
+ className: ["rounded border px-2 py-1", (state?.status === "completed" ? index === total - 1 : step.id === state?.currentStep) ? "border-primary text-primary" : "border-border"].join(" "),
20
+ children: /* @__PURE__ */ jsx("span", {
21
+ className: "font-medium",
22
+ children: step.label
23
+ })
24
+ }, step.id);
25
+ })
26
+ })]
27
+ });
28
+ }
29
+ function computeCurrent(steps, state) {
30
+ if (!steps.length) return 0;
31
+ if (!state) return 1;
32
+ if (state.status === "completed") return steps.length;
33
+ const idx = steps.findIndex((step) => step.id === state.currentStep);
34
+ return idx === -1 ? 1 : idx + 1;
35
+ }
36
+
37
+ //#endregion
38
+ export { WorkflowStepper };
39
+ //# sourceMappingURL=WorkflowStepper.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WorkflowStepper.js","names":[],"sources":["../src/WorkflowStepper.tsx"],"sourcesContent":["import * as React from 'react';\nimport type { WorkflowSpec, WorkflowState } from '@lssm/lib.contracts/workflow';\nimport { Stepper } from '@lssm/lib.design-system';\n\nexport interface WorkflowStepperProps {\n spec: WorkflowSpec;\n state: WorkflowState | null;\n className?: string;\n showLabels?: boolean;\n}\n\nexport function WorkflowStepper({\n spec,\n state,\n className,\n showLabels = false,\n}: WorkflowStepperProps) {\n const steps = spec.definition.steps;\n const total = steps.length;\n const current = computeCurrent(steps, state);\n\n return (\n <div\n className={['flex flex-col gap-2', className].filter(Boolean).join(' ')}\n >\n <Stepper current={current} total={total} />\n {showLabels && total > 0 && (\n <ol className=\"text-muted-foreground flex flex-wrap gap-2 text-sm\">\n {steps.map((step, index) => {\n const isActive =\n state?.status === 'completed'\n ? index === total - 1\n : step.id === state?.currentStep;\n return (\n <li\n key={step.id}\n className={[\n 'rounded border px-2 py-1',\n isActive ? 'border-primary text-primary' : 'border-border',\n ].join(' ')}\n >\n <span className=\"font-medium\">{step.label}</span>\n </li>\n );\n })}\n </ol>\n )}\n </div>\n );\n}\n\nfunction computeCurrent(\n steps: WorkflowSpec['definition']['steps'],\n state: WorkflowState | null\n) {\n if (!steps.length) return 0;\n if (!state) return 1;\n\n if (state.status === 'completed') return steps.length;\n\n const idx = steps.findIndex((step) => step.id === state.currentStep);\n return idx === -1 ? 1 : idx + 1;\n}\n"],"mappings":";;;;;AAWA,SAAgB,gBAAgB,EAC9B,MACA,OACA,WACA,aAAa,SACU;CACvB,MAAM,QAAQ,KAAK,WAAW;CAC9B,MAAM,QAAQ,MAAM;CACpB,MAAM,UAAU,eAAe,OAAO,MAAM;AAE5C,QACE,qBAAC;EACC,WAAW,CAAC,uBAAuB,UAAU,CAAC,OAAO,QAAQ,CAAC,KAAK,IAAI;aAEvE,oBAAC;GAAiB;GAAgB;IAAS,EAC1C,cAAc,QAAQ,KACrB,oBAAC;GAAG,WAAU;aACX,MAAM,KAAK,MAAM,UAAU;AAK1B,WACE,oBAAC;KAEC,WAAW,CACT,6BAPJ,OAAO,WAAW,cACd,UAAU,QAAQ,IAClB,KAAK,OAAO,OAAO,eAMR,gCAAgC,gBAC5C,CAAC,KAAK,IAAI;eAEX,oBAAC;MAAK,WAAU;gBAAe,KAAK;OAAa;OAN5C,KAAK,GAOP;KAEP;IACC;GAEH;;AAIV,SAAS,eACP,OACA,OACA;AACA,KAAI,CAAC,MAAM,OAAQ,QAAO;AAC1B,KAAI,CAAC,MAAO,QAAO;AAEnB,KAAI,MAAM,WAAW,YAAa,QAAO,MAAM;CAE/C,MAAM,MAAM,MAAM,WAAW,SAAS,KAAK,OAAO,MAAM,YAAY;AACpE,QAAO,QAAQ,KAAK,IAAI,MAAM"}
@@ -0,0 +1,115 @@
1
+ import { WorkflowStepRenderer } from "./WorkflowStepRenderer.js";
2
+ import { WorkflowStepper } from "./WorkflowStepper.js";
3
+ import { UseWorkflowOptions, UseWorkflowResult, useWorkflow } from "./useWorkflow.js";
4
+ import * as React from "react";
5
+ import { DefaultValues, Resolver, UseFormReturn } from "react-hook-form";
6
+ import { ListFetcher, ListState } from "@lssm/lib.presentation-runtime-core";
7
+
8
+ //#region src/index.d.ts
9
+ interface UsePresentationControllerOpts<TFilters extends Record<string, unknown>, TVars, TItem> {
10
+ defaults: ListState<TFilters>;
11
+ form: {
12
+ defaultValues: DefaultValues<TFilters> | TFilters;
13
+ resolver?: Resolver<TFilters>;
14
+ };
15
+ toVariables: (input: ListState<TFilters>) => TVars;
16
+ fetcher: ListFetcher<TVars, TItem>;
17
+ toChips?: (filters: TFilters, setFilter: (key: keyof TFilters, value: TFilters[keyof TFilters] | null) => void) => {
18
+ key: string;
19
+ label: React.ReactNode;
20
+ onRemove?: () => void;
21
+ }[];
22
+ useUrlState: (args: {
23
+ defaults: ListState<TFilters>;
24
+ replace?: boolean;
25
+ }) => {
26
+ state: ListState<TFilters>;
27
+ setState: (next: Partial<ListState<TFilters>>) => void;
28
+ setFilter: (key: keyof TFilters, value: TFilters[keyof TFilters] | null) => void;
29
+ clearFilters: () => void;
30
+ };
31
+ replace?: boolean;
32
+ }
33
+ declare function usePresentationController<TFilters extends Record<string, unknown>, TVars, TItem>({
34
+ defaults,
35
+ form: formOpts,
36
+ toVariables,
37
+ fetcher,
38
+ toChips,
39
+ useUrlState,
40
+ replace
41
+ }: UsePresentationControllerOpts<TFilters, TVars, TItem>): {
42
+ readonly form: UseFormReturn<TFilters>;
43
+ readonly url: {
44
+ state: ListState<TFilters>;
45
+ setState: (next: Partial<ListState<TFilters>>) => void;
46
+ setFilter: (key: keyof TFilters, value: TFilters[keyof TFilters] | null) => void;
47
+ clearFilters: () => void;
48
+ };
49
+ readonly variables: TVars;
50
+ readonly data: TItem[];
51
+ readonly loading: boolean;
52
+ readonly error: unknown;
53
+ readonly totalItems: number | undefined;
54
+ readonly totalPages: number | undefined;
55
+ readonly refetch: () => Promise<void>;
56
+ readonly chips: {
57
+ key: string;
58
+ label: React.ReactNode;
59
+ onRemove?: () => void;
60
+ }[];
61
+ readonly setSearch: (q: string) => void;
62
+ readonly submitFilters: (e?: React.BaseSyntheticEvent) => Promise<void>;
63
+ readonly clearAll: () => void;
64
+ };
65
+ interface UseListCoordinatorOpts<TFilters extends Record<string, unknown>, TVars> {
66
+ defaults: ListState<TFilters>;
67
+ form: {
68
+ defaultValues: DefaultValues<TFilters>;
69
+ resolver?: Resolver<TFilters>;
70
+ };
71
+ toVariables: (input: ListState<TFilters>) => TVars;
72
+ toChips?: (filters: TFilters, setFilter: (key: keyof TFilters, value: TFilters[keyof TFilters] | null) => void) => {
73
+ key: string;
74
+ label: React.ReactNode;
75
+ onRemove?: () => void;
76
+ }[];
77
+ useUrlState: (args: {
78
+ defaults: ListState<TFilters>;
79
+ replace?: boolean;
80
+ }) => {
81
+ state: ListState<TFilters>;
82
+ setState: (next: Partial<ListState<TFilters>>) => void;
83
+ setFilter: (key: keyof TFilters, value: TFilters[keyof TFilters] | null) => void;
84
+ clearFilters: () => void;
85
+ };
86
+ replace?: boolean;
87
+ }
88
+ declare function useListCoordinator<TFilters extends Record<string, unknown>, TVars>({
89
+ defaults,
90
+ form: formOpts,
91
+ toVariables,
92
+ toChips,
93
+ useUrlState,
94
+ replace
95
+ }: UseListCoordinatorOpts<TFilters, TVars>): {
96
+ readonly form: UseFormReturn<TFilters>;
97
+ readonly url: {
98
+ state: ListState<TFilters>;
99
+ setState: (next: Partial<ListState<TFilters>>) => void;
100
+ setFilter: (key: keyof TFilters, value: TFilters[keyof TFilters] | null) => void;
101
+ clearFilters: () => void;
102
+ };
103
+ readonly variables: TVars;
104
+ readonly chips: {
105
+ key: string;
106
+ label: React.ReactNode;
107
+ onRemove?: () => void;
108
+ }[];
109
+ readonly setSearch: (q: string) => void;
110
+ readonly submitFilters: (e?: React.BaseSyntheticEvent) => Promise<void>;
111
+ readonly clearAll: () => void;
112
+ };
113
+ //#endregion
114
+ export { UseListCoordinatorOpts, UsePresentationControllerOpts, type UseWorkflowOptions, type UseWorkflowResult, WorkflowStepRenderer, WorkflowStepper, useListCoordinator, usePresentationController, useWorkflow };
115
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,130 @@
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 "@lssm/lib.ui-kit-web/ui/form";
6
+
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
+ };
81
+ }
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
+ };
126
+ }
127
+
128
+ //#endregion
129
+ export { WorkflowStepRenderer, WorkflowStepper, useListCoordinator, usePresentationController, useWorkflow };
130
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import * as React from 'react';\nimport type { DefaultValues, Resolver, UseFormReturn } from 'react-hook-form';\nimport { useForm } from '@lssm/lib.ui-kit-web/ui/form';\nimport type {\n ListFetcher,\n ListState,\n} from '@lssm/lib.presentation-runtime-core';\n\nexport interface UsePresentationControllerOpts<\n TFilters extends Record<string, unknown>,\n TVars,\n TItem,\n> {\n defaults: ListState<TFilters>;\n form: {\n defaultValues: DefaultValues<TFilters> | TFilters;\n resolver?: Resolver<TFilters>;\n };\n toVariables: (input: ListState<TFilters>) => TVars;\n fetcher: ListFetcher<TVars, TItem>;\n toChips?: (\n filters: TFilters,\n setFilter: (\n key: keyof TFilters,\n value: TFilters[keyof TFilters] | null\n ) => void\n ) => { key: string; label: React.ReactNode; onRemove?: () => void }[];\n useUrlState: (args: { defaults: ListState<TFilters>; replace?: boolean }) => {\n state: ListState<TFilters>;\n setState: (next: Partial<ListState<TFilters>>) => void;\n setFilter: (\n key: keyof TFilters,\n value: TFilters[keyof TFilters] | null\n ) => void;\n clearFilters: () => void;\n };\n replace?: boolean;\n}\n\nexport function usePresentationController<\n TFilters extends Record<string, unknown>,\n TVars,\n TItem,\n>({\n defaults,\n form: formOpts,\n toVariables,\n fetcher,\n toChips,\n useUrlState,\n replace,\n}: UsePresentationControllerOpts<TFilters, TVars, TItem>) {\n const url = useUrlState({ defaults, replace });\n const form = useForm<TFilters>({\n defaultValues: formOpts.defaultValues,\n resolver: formOpts.resolver as any,\n } as any);\n\n React.useEffect(() => {\n form.reset({ ...(form.getValues() as any), ...(url.state.filters as any) });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url.state.filters]);\n\n const submitFilters = form.handleSubmit((values) => {\n url.setState({ filters: values as TFilters, page: 1 });\n });\n\n const setSearch = React.useCallback(\n (q: string) => url.setState({ q, page: 1 }),\n [url]\n );\n const variables = React.useMemo(\n () => toVariables(url.state),\n [url.state, toVariables]\n );\n\n const [data, setData] = React.useState<TItem[]>([]);\n const [loading, setLoading] = React.useState(false);\n const [error, setError] = React.useState<unknown>(null);\n const [totalItems, setTotalItems] = React.useState<number | undefined>(\n undefined\n );\n const [totalPages, setTotalPages] = React.useState<number | undefined>(\n undefined\n );\n\n const refetch = React.useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const out = await fetcher(variables);\n setData(out.items);\n setTotalItems(out.totalItems);\n setTotalPages(out.totalPages);\n } catch (e) {\n setError(e);\n } finally {\n setLoading(false);\n }\n }, [variables, fetcher]);\n\n React.useEffect(() => {\n void refetch();\n }, [refetch]);\n\n const chips = React.useMemo(\n () =>\n toChips\n ? toChips(\n (url.state.filters as TFilters) || ({} as any),\n url.setFilter as any\n )\n : [],\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [url.state.filters, toChips]\n );\n\n const clearAll = React.useCallback(() => {\n form.reset(formOpts.defaultValues as any);\n url.setState({ filters: {} as TFilters, page: 1 });\n }, [form, formOpts.defaultValues, url]);\n\n return {\n form: form as UseFormReturn<TFilters>,\n url,\n variables,\n data,\n loading,\n error,\n totalItems,\n totalPages,\n refetch,\n chips,\n setSearch,\n submitFilters,\n clearAll,\n } as const;\n}\n\nexport interface UseListCoordinatorOpts<\n TFilters extends Record<string, unknown>,\n TVars,\n> {\n defaults: ListState<TFilters>;\n form: {\n defaultValues: DefaultValues<TFilters>;\n resolver?: Resolver<TFilters>;\n };\n toVariables: (input: ListState<TFilters>) => TVars;\n toChips?: (\n filters: TFilters,\n setFilter: (\n key: keyof TFilters,\n value: TFilters[keyof TFilters] | null\n ) => void\n ) => { key: string; label: React.ReactNode; onRemove?: () => void }[];\n useUrlState: (args: { defaults: ListState<TFilters>; replace?: boolean }) => {\n state: ListState<TFilters>;\n setState: (next: Partial<ListState<TFilters>>) => void;\n setFilter: (\n key: keyof TFilters,\n value: TFilters[keyof TFilters] | null\n ) => void;\n clearFilters: () => void;\n };\n replace?: boolean;\n}\n\nexport function useListCoordinator<\n TFilters extends Record<string, unknown>,\n TVars,\n>({\n defaults,\n form: formOpts,\n toVariables,\n toChips,\n useUrlState,\n replace,\n}: UseListCoordinatorOpts<TFilters, TVars>) {\n const url = useUrlState({ defaults, replace });\n const form = useForm<TFilters>({\n defaultValues: formOpts.defaultValues,\n resolver: formOpts.resolver,\n } as any);\n\n React.useEffect(() => {\n form.reset({ ...(form.getValues() as any), ...(url.state.filters as any) });\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url.state.filters]);\n\n const submitFilters = form.handleSubmit((values) => {\n url.setState({ filters: values as TFilters, page: 1 });\n });\n\n const setSearch = React.useCallback(\n (q: string) => url.setState({ q, page: 1 }),\n [url]\n );\n const variables = React.useMemo(\n () => toVariables(url.state),\n [url.state, toVariables]\n );\n\n const chips = React.useMemo(\n () =>\n toChips\n ? toChips(\n (url.state.filters as TFilters) || ({} as any),\n url.setFilter as any\n )\n : [],\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [url.state.filters, toChips]\n );\n\n const clearAll = React.useCallback(() => {\n form.reset(formOpts.defaultValues as any);\n url.setState({ filters: {} as TFilters, page: 1 });\n }, [form, formOpts.defaultValues, url]);\n\n return {\n form: form as UseFormReturn<TFilters>,\n url,\n variables,\n chips,\n setSearch,\n submitFilters,\n clearAll,\n } as const;\n}\n\nexport { useWorkflow } from './useWorkflow';\nexport type {\n UseWorkflowOptions,\n UseWorkflowResult,\n} from './useWorkflow';\nexport { WorkflowStepper } from './WorkflowStepper';\nexport { WorkflowStepRenderer } from './WorkflowStepRenderer';\n"],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export { };
@@ -0,0 +1,27 @@
1
+ import { WorkflowRunner, WorkflowState } from "@lssm/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;
9
+ }
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>;
18
+ }
19
+ declare function useWorkflow({
20
+ workflowId,
21
+ runner,
22
+ autoRefresh,
23
+ refreshIntervalMs
24
+ }: UseWorkflowOptions): UseWorkflowResult;
25
+ //#endregion
26
+ export { UseWorkflowOptions, UseWorkflowResult, useWorkflow };
27
+ //# sourceMappingURL=useWorkflow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWorkflow.d.ts","names":[],"sources":["../src/useWorkflow.ts"],"sourcesContent":[],"mappings":";;;UAMiB,kBAAA;;EAAA,MAAA,EAEP,cAFyB;EAOlB,WAAA,CAAA,EAAA,OAAiB;EACzB,iBAAA,CAAA,EAAA,MAAA;;AAIQ,UALA,iBAAA,CAKA;EACmB,KAAA,EAL3B,aAK2B,GAAA,IAAA;EACpB,SAAA,EAAA,OAAA;EAAO,KAAA,EAJd,KAIc,GAAA,IAAA;EAGP,WAAA,EAAA,OAAW;EACzB,OAAA,EAAA,GAAA,GANe,OAMf,CAAA,IAAA,CAAA;EACA,WAAA,EAAA,CAAA,KAAA,CAAA,EAAA,OAAA,EAAA,GANkC,OAMlC,CAAA,IAAA,CAAA;EACA,MAAA,EAAA,GAAA,GANc,OAMd,CAAA,IAAA,CAAA;;AAEC,iBALa,WAAA,CAKb;EAAA,UAAA;EAAA,MAAA;EAAA,WAAA;EAAA;AAAA,CAAA,EAAA,kBAAA,CAAA,EAAqB,iBAArB"}
@@ -0,0 +1,79 @@
1
+ import * as React from "react";
2
+
3
+ //#region src/useWorkflow.ts
4
+ function useWorkflow({ workflowId, runner, autoRefresh = true, refreshIntervalMs = 2e3 }) {
5
+ const isMounted = React.useRef(true);
6
+ const [state, setState] = React.useState(null);
7
+ const [isLoading, setIsLoading] = React.useState(true);
8
+ const [error, setError] = React.useState(null);
9
+ const [isExecuting, setIsExecuting] = React.useState(false);
10
+ const refresh = React.useCallback(async () => {
11
+ try {
12
+ setIsLoading(true);
13
+ const next = await runner.getState(workflowId);
14
+ if (!isMounted.current) return;
15
+ setState(next);
16
+ setError(null);
17
+ } catch (err) {
18
+ if (!isMounted.current) return;
19
+ setError(err instanceof Error ? err : new Error(String(err)));
20
+ } finally {
21
+ if (isMounted.current) setIsLoading(false);
22
+ }
23
+ }, [runner, workflowId]);
24
+ const executeStep = React.useCallback(async (input) => {
25
+ setIsExecuting(true);
26
+ try {
27
+ await runner.executeStep(workflowId, input);
28
+ await refresh();
29
+ } catch (err) {
30
+ if (isMounted.current) setError(err instanceof Error ? err : new Error(String(err)));
31
+ throw err;
32
+ } finally {
33
+ if (isMounted.current) setIsExecuting(false);
34
+ }
35
+ }, [
36
+ runner,
37
+ workflowId,
38
+ refresh
39
+ ]);
40
+ const cancel = React.useCallback(async () => {
41
+ await runner.cancel(workflowId);
42
+ await refresh();
43
+ }, [
44
+ runner,
45
+ workflowId,
46
+ refresh
47
+ ]);
48
+ React.useEffect(() => {
49
+ isMounted.current = true;
50
+ refresh();
51
+ if (!autoRefresh) return () => {
52
+ isMounted.current = false;
53
+ };
54
+ const interval = setInterval(() => {
55
+ refresh();
56
+ }, refreshIntervalMs);
57
+ return () => {
58
+ isMounted.current = false;
59
+ clearInterval(interval);
60
+ };
61
+ }, [
62
+ refresh,
63
+ autoRefresh,
64
+ refreshIntervalMs
65
+ ]);
66
+ return {
67
+ state,
68
+ isLoading,
69
+ error,
70
+ isExecuting,
71
+ refresh,
72
+ executeStep,
73
+ cancel
74
+ };
75
+ }
76
+
77
+ //#endregion
78
+ export { useWorkflow };
79
+ //# sourceMappingURL=useWorkflow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useWorkflow.js","names":[],"sources":["../src/useWorkflow.ts"],"sourcesContent":["import * as React from 'react';\nimport type {\n WorkflowRunner,\n WorkflowState,\n} from '@lssm/lib.contracts/workflow';\n\nexport interface UseWorkflowOptions {\n workflowId: string;\n runner: WorkflowRunner;\n autoRefresh?: boolean;\n refreshIntervalMs?: number;\n}\n\nexport interface UseWorkflowResult {\n state: WorkflowState | null;\n isLoading: boolean;\n error: Error | null;\n isExecuting: boolean;\n refresh: () => Promise<void>;\n executeStep: (input?: unknown) => Promise<void>;\n cancel: () => Promise<void>;\n}\n\nexport function useWorkflow({\n workflowId,\n runner,\n autoRefresh = true,\n refreshIntervalMs = 2000,\n}: UseWorkflowOptions): UseWorkflowResult {\n const isMounted = React.useRef(true);\n const [state, setState] = React.useState<WorkflowState | null>(null);\n const [isLoading, setIsLoading] = React.useState(true);\n const [error, setError] = React.useState<Error | null>(null);\n const [isExecuting, setIsExecuting] = React.useState(false);\n\n const refresh = React.useCallback(async () => {\n try {\n setIsLoading(true);\n const next = await runner.getState(workflowId);\n if (!isMounted.current) return;\n setState(next);\n setError(null);\n } catch (err) {\n if (!isMounted.current) return;\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n if (isMounted.current) setIsLoading(false);\n }\n }, [runner, workflowId]);\n\n const executeStep = React.useCallback(\n async (input?: unknown) => {\n setIsExecuting(true);\n try {\n await runner.executeStep(workflowId, input);\n await refresh();\n } catch (err) {\n if (isMounted.current) {\n setError(err instanceof Error ? err : new Error(String(err)));\n }\n throw err;\n } finally {\n if (isMounted.current) setIsExecuting(false);\n }\n },\n [runner, workflowId, refresh]\n );\n\n const cancel = React.useCallback(async () => {\n await runner.cancel(workflowId);\n await refresh();\n }, [runner, workflowId, refresh]);\n\n React.useEffect(() => {\n isMounted.current = true;\n void refresh();\n if (!autoRefresh) {\n return () => {\n isMounted.current = false;\n };\n }\n const interval = setInterval(() => {\n void refresh();\n }, refreshIntervalMs);\n return () => {\n isMounted.current = false;\n clearInterval(interval);\n };\n }, [refresh, autoRefresh, refreshIntervalMs]);\n\n return {\n state,\n isLoading,\n error,\n isExecuting,\n refresh,\n executeStep,\n cancel,\n };\n}\n\n"],"mappings":";;;AAuBA,SAAgB,YAAY,EAC1B,YACA,QACA,cAAc,MACd,oBAAoB,OACoB;CACxC,MAAM,YAAY,MAAM,OAAO,KAAK;CACpC,MAAM,CAAC,OAAO,YAAY,MAAM,SAA+B,KAAK;CACpE,MAAM,CAAC,WAAW,gBAAgB,MAAM,SAAS,KAAK;CACtD,MAAM,CAAC,OAAO,YAAY,MAAM,SAAuB,KAAK;CAC5D,MAAM,CAAC,aAAa,kBAAkB,MAAM,SAAS,MAAM;CAE3D,MAAM,UAAU,MAAM,YAAY,YAAY;AAC5C,MAAI;AACF,gBAAa,KAAK;GAClB,MAAM,OAAO,MAAM,OAAO,SAAS,WAAW;AAC9C,OAAI,CAAC,UAAU,QAAS;AACxB,YAAS,KAAK;AACd,YAAS,KAAK;WACP,KAAK;AACZ,OAAI,CAAC,UAAU,QAAS;AACxB,YAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,CAAC;YACrD;AACR,OAAI,UAAU,QAAS,cAAa,MAAM;;IAE3C,CAAC,QAAQ,WAAW,CAAC;CAExB,MAAM,cAAc,MAAM,YACxB,OAAO,UAAoB;AACzB,iBAAe,KAAK;AACpB,MAAI;AACF,SAAM,OAAO,YAAY,YAAY,MAAM;AAC3C,SAAM,SAAS;WACR,KAAK;AACZ,OAAI,UAAU,QACZ,UAAS,eAAe,QAAQ,MAAM,IAAI,MAAM,OAAO,IAAI,CAAC,CAAC;AAE/D,SAAM;YACE;AACR,OAAI,UAAU,QAAS,gBAAe,MAAM;;IAGhD;EAAC;EAAQ;EAAY;EAAQ,CAC9B;CAED,MAAM,SAAS,MAAM,YAAY,YAAY;AAC3C,QAAM,OAAO,OAAO,WAAW;AAC/B,QAAM,SAAS;IACd;EAAC;EAAQ;EAAY;EAAQ,CAAC;AAEjC,OAAM,gBAAgB;AACpB,YAAU,UAAU;AACpB,EAAK,SAAS;AACd,MAAI,CAAC,YACH,cAAa;AACX,aAAU,UAAU;;EAGxB,MAAM,WAAW,kBAAkB;AACjC,GAAK,SAAS;KACb,kBAAkB;AACrB,eAAa;AACX,aAAU,UAAU;AACpB,iBAAc,SAAS;;IAExB;EAAC;EAAS;EAAa;EAAkB,CAAC;AAE7C,QAAO;EACL;EACA;EACA;EACA;EACA;EACA;EACA;EACD"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@lssm/lib.presentation-runtime-react",
3
+ "version": "0.0.0-canary-20251120170226",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "sideEffects": false,
9
+ "scripts": {
10
+ "build": "bun build:bundle && bun build:types",
11
+ "build:bundle": "tsdown",
12
+ "build:types": "tsc --noEmit -p tsconfig.json",
13
+ "dev": "bun run build:bundle --watch",
14
+ "lint": "bun run lint:fix",
15
+ "lint:fix": "eslint src --fix",
16
+ "lint:check": "eslint src"
17
+ },
18
+ "peerDependencies": {
19
+ "react": "^19.1.0",
20
+ "react-hook-form": "7.66.0",
21
+ "zod": "^4.1.5",
22
+ "@lssm/lib.presentation-runtime-core": "0.0.0-canary-20251120170226"
23
+ },
24
+ "dependencies": {
25
+ "@lssm/lib.presentation-runtime-core": "0.0.0-canary-20251120170226",
26
+ "@lssm/lib.contracts": "0.0.0-canary-20251120170226",
27
+ "@lssm/lib.design-system": "0.0.0-canary-20251120170226",
28
+ "@lssm/lib.ui-kit-web": "0.0.0-canary-20251120170226"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md"
33
+ ],
34
+ "exports": {
35
+ ".": "./dist/index.js",
36
+ "./useWorkflow": "./dist/useWorkflow.js",
37
+ "./WorkflowStepper": "./dist/WorkflowStepper.js",
38
+ "./WorkflowStepRenderer": "./dist/WorkflowStepRenderer.js",
39
+ "./*": "./*"
40
+ },
41
+ "publishConfig": {
42
+ "exports": {
43
+ ".": "./dist/index.js",
44
+ "./useWorkflow": "./dist/useWorkflow.js",
45
+ "./WorkflowStepper": "./dist/WorkflowStepper.js",
46
+ "./WorkflowStepRenderer": "./dist/WorkflowStepRenderer.js",
47
+ "./*": "./*"
48
+ }
49
+ }
50
+ }