@fieldtripjs/react 0.1.0 → 0.3.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.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  export { createFlowContext, type FlowContext } from "./context.js";
2
- export { useFlow, type FieldProps, type FlowController, type UseFlowOptions } from "./use-flow.js";
2
+ export { useFlow, type FieldProps, type FlowController, type SetFlowInputs, type UseFlowOptions } from "./use-flow.js";
@@ -1,4 +1,4 @@
1
- import { type AnyFlow, type FieldIssues, type FieldName, type FlowState, type FlowValues, type StepName } from "@fieldtripjs/core";
1
+ import { type AnyFlow, type AnyStepInputs, type FieldIssues, type FieldName, type FlowState, type FlowValues, type StepInputs, type StepName } from "@fieldtripjs/core";
2
2
  export interface UseFlowOptions<FL extends AnyFlow> {
3
3
  readonly initialState?: FlowState<FL>;
4
4
  readonly onChange?: (state: FlowState<FL>) => void;
@@ -10,10 +10,15 @@ export interface FieldProps<N extends string = string> {
10
10
  readonly visible: boolean;
11
11
  readonly onChange: (value: unknown) => void;
12
12
  }
13
+ export interface SetFlowInputs<FL extends AnyFlow> {
14
+ (inputs: AnyStepInputs<FL>): void;
15
+ <K extends StepName<FL>>(inputs: NoInfer<Partial<StepInputs<FL, K>>>, step: K): void;
16
+ }
13
17
  export interface FlowController<FL extends AnyFlow> {
14
18
  readonly state: FlowState<FL>;
15
19
  readonly step: StepName<FL>;
16
20
  readonly path: readonly StepName<FL>[];
21
+ readonly forecast: readonly StepName<FL>[];
17
22
  readonly values: FlowValues<FL>;
18
23
  readonly inputs: Readonly<Record<string, unknown>>;
19
24
  readonly issues: FieldIssues;
@@ -21,7 +26,9 @@ export interface FlowController<FL extends AnyFlow> {
21
26
  readonly isComplete: boolean;
22
27
  readonly isPending: boolean;
23
28
  readonly canGoBack: boolean;
29
+ readonly canAdvance: boolean;
24
30
  readonly setInput: (name: FieldName<FL>, value: unknown) => void;
31
+ readonly setInputs: SetFlowInputs<FL>;
25
32
  readonly field: <N extends FieldName<FL>>(name: N) => FieldProps<N>;
26
33
  readonly next: () => Promise<boolean>;
27
34
  readonly back: () => void;
package/dist/use-flow.js CHANGED
@@ -1,4 +1,4 @@
1
- import { back as backState, next as nextState, path as pathOf, result, setInputs, start, visibleFields as visibleFieldsOf, } from "@fieldtripjs/core";
1
+ import { back as backState, FlowAsyncError, forecast as forecastOf, next as nextState, path as pathOf, result, setInputs, start, validateStepSync, visibleFields as visibleFieldsOf, } from "@fieldtripjs/core";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  export function useFlow(flow, options = {}) {
4
4
  const [state, setState] = useState(() => options.initialState ?? start(flow));
@@ -14,10 +14,14 @@ export function useFlow(flow, options = {}) {
14
14
  setState(next);
15
15
  onChange.current?.(next);
16
16
  }, []);
17
- const setInput = useCallback((name, value) => {
18
- commit(setInputs(flow, latest.current, { [name]: value }));
19
- setIssues(({ [name]: _cleared, ...rest }) => rest);
17
+ const setStepInputs = useCallback((inputs, step = latest.current.current) => {
18
+ const current = latest.current;
19
+ commit(setInputs(flow, current, inputs, step));
20
+ if (step !== current.current)
21
+ return;
22
+ setIssues((issues) => Object.fromEntries(Object.entries(issues).filter(([name]) => !(name in inputs))));
20
23
  }, [flow, commit]);
24
+ const setInput = useCallback((name, value) => setStepInputs({ [name]: value }), [setStepInputs]);
21
25
  const next = useCallback(async () => {
22
26
  const submitted = latest.current;
23
27
  setPending(true);
@@ -48,6 +52,18 @@ export function useFlow(flow, options = {}) {
48
52
  }, [flow, commit]);
49
53
  const inputs = state.inputs[state.current] ?? {};
50
54
  const visibleFields = useMemo(() => visibleFieldsOf(flow, state), [flow, state]);
55
+ const forecast = useMemo(() => forecastOf(flow, state), [flow, state]);
56
+ const canAdvance = useMemo(() => {
57
+ try {
58
+ return validateStepSync(flow, state).ok;
59
+ }
60
+ catch (error) {
61
+ // An async schema can't be checked during render; next() decides.
62
+ if (error instanceof FlowAsyncError)
63
+ return true;
64
+ throw error;
65
+ }
66
+ }, [flow, state]);
51
67
  const field = useCallback((name) => ({
52
68
  name,
53
69
  value: inputs[name],
@@ -59,6 +75,7 @@ export function useFlow(flow, options = {}) {
59
75
  state,
60
76
  step: state.current,
61
77
  path: pathOf(state),
78
+ forecast,
62
79
  values: result(flow, state),
63
80
  inputs,
64
81
  issues,
@@ -66,10 +83,27 @@ export function useFlow(flow, options = {}) {
66
83
  isComplete: state.status === "complete",
67
84
  isPending,
68
85
  canGoBack: state.history.length > 0 || state.status === "complete",
86
+ canAdvance,
87
+ setInput,
88
+ setInputs: setStepInputs,
89
+ field,
90
+ next,
91
+ back,
92
+ reset,
93
+ }), [
94
+ flow,
95
+ state,
96
+ forecast,
97
+ inputs,
98
+ issues,
99
+ visibleFields,
100
+ isPending,
101
+ canAdvance,
69
102
  setInput,
103
+ setStepInputs,
70
104
  field,
71
105
  next,
72
106
  back,
73
107
  reset,
74
- }), [flow, state, inputs, issues, visibleFields, isPending, setInput, field, next, back, reset]);
108
+ ]);
75
109
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fieldtripjs/react",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Headless React bindings for @fieldtripjs/core.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -25,7 +25,7 @@
25
25
  "react": ">=18"
26
26
  },
27
27
  "dependencies": {
28
- "@fieldtripjs/core": "0.1.0"
28
+ "@fieldtripjs/core": "0.2.0"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@testing-library/react": "^16.3.3",