@waypointjs/next 0.1.4 → 0.1.6
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.cjs +60 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +18 -2
- package/dist/index.d.ts +18 -2
- package/dist/index.js +60 -11
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,9 @@ function WaypointRunner({
|
|
|
26
26
|
onComplete,
|
|
27
27
|
onStepComplete,
|
|
28
28
|
onDataChange,
|
|
29
|
+
onStepSkipped,
|
|
30
|
+
customFieldTypes,
|
|
31
|
+
externalEnums,
|
|
29
32
|
children
|
|
30
33
|
}) {
|
|
31
34
|
const router = navigation.useRouter();
|
|
@@ -49,7 +52,7 @@ function WaypointRunner({
|
|
|
49
52
|
store.getState().resume(schema, externalVars);
|
|
50
53
|
const state2 = store.getState();
|
|
51
54
|
if (state2.currentStepId) {
|
|
52
|
-
const tree2 = core.resolveTree(schema, state2.data, state2.externalVars);
|
|
55
|
+
const tree2 = core.resolveTree(schema, state2.data, state2.externalVars, externalEnums);
|
|
53
56
|
const step = tree2.steps.find(
|
|
54
57
|
(s) => s.definition.id === state2.currentStepId
|
|
55
58
|
);
|
|
@@ -73,7 +76,7 @@ function WaypointRunner({
|
|
|
73
76
|
if (cancelled) return;
|
|
74
77
|
store.getState().init(schema, { data, externalVars });
|
|
75
78
|
const state = store.getState();
|
|
76
|
-
const tree = core.resolveTree(schema, state.data, state.externalVars);
|
|
79
|
+
const tree = core.resolveTree(schema, state.data, state.externalVars, externalEnums);
|
|
77
80
|
const lastValid = core.findLastValidStep(tree.steps, state.data, state.externalVars);
|
|
78
81
|
if (lastValid && lastValid.definition.url !== pathname) {
|
|
79
82
|
router.push(lastValid.definition.url);
|
|
@@ -95,10 +98,13 @@ function WaypointRunner({
|
|
|
95
98
|
store,
|
|
96
99
|
onComplete,
|
|
97
100
|
onStepComplete,
|
|
98
|
-
onDataChange
|
|
101
|
+
onDataChange,
|
|
102
|
+
onStepSkipped,
|
|
103
|
+
customFieldTypes,
|
|
104
|
+
externalEnums
|
|
99
105
|
}),
|
|
100
106
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
101
|
-
[schema, store]
|
|
107
|
+
[schema, store, externalEnums]
|
|
102
108
|
);
|
|
103
109
|
if (missingVars.length > 0) {
|
|
104
110
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -127,21 +133,22 @@ function WaypointRunner({
|
|
|
127
133
|
return /* @__PURE__ */ jsxRuntime.jsx(WaypointRuntimeContext.Provider, { value: contextValue, children });
|
|
128
134
|
}
|
|
129
135
|
function useWaypointStep() {
|
|
130
|
-
const { schema, store, onComplete, onStepComplete, onDataChange } = useWaypointRuntimeContext();
|
|
136
|
+
const { schema, store, onComplete, onStepComplete, onDataChange, onStepSkipped, externalEnums } = useWaypointRuntimeContext();
|
|
131
137
|
const router = navigation.useRouter();
|
|
132
138
|
const pathname = navigation.usePathname();
|
|
133
|
-
const { data, externalVars, currentStepId, isSubmitting } = zustand.useStore(
|
|
139
|
+
const { data, externalVars, currentStepId, skippedSteps, isSubmitting } = zustand.useStore(
|
|
134
140
|
store,
|
|
135
141
|
(s) => ({
|
|
136
142
|
data: s.data,
|
|
137
143
|
externalVars: s.externalVars,
|
|
138
144
|
currentStepId: s.currentStepId,
|
|
145
|
+
skippedSteps: s.skippedSteps,
|
|
139
146
|
isSubmitting: s.isSubmitting
|
|
140
147
|
})
|
|
141
148
|
);
|
|
142
149
|
const tree = react.useMemo(
|
|
143
|
-
() => core.resolveTree(schema, data, externalVars),
|
|
144
|
-
[schema, data, externalVars]
|
|
150
|
+
() => core.resolveTree(schema, data, externalVars, externalEnums, skippedSteps),
|
|
151
|
+
[schema, data, externalVars, externalEnums, skippedSteps]
|
|
145
152
|
);
|
|
146
153
|
const currentStep = react.useMemo(() => {
|
|
147
154
|
return tree.steps.find((s) => {
|
|
@@ -158,9 +165,25 @@ function useWaypointStep() {
|
|
|
158
165
|
() => currentStep?.fields.filter((f) => f.visible) ?? [],
|
|
159
166
|
[currentStep]
|
|
160
167
|
);
|
|
161
|
-
const zodSchema = react.useMemo(() => core.buildZodSchema(visibleFields), [visibleFields]);
|
|
168
|
+
const zodSchema = react.useMemo(() => core.buildZodSchema(visibleFields, externalEnums, data), [visibleFields, externalEnums, data]);
|
|
162
169
|
const defaultValues = react.useMemo(
|
|
163
|
-
() =>
|
|
170
|
+
() => {
|
|
171
|
+
if (!currentStep) return {};
|
|
172
|
+
const stored = data[currentStep.definition.id] ?? {};
|
|
173
|
+
const merged = { ...stored };
|
|
174
|
+
for (const field of currentStep.fields) {
|
|
175
|
+
const fid = field.definition.id;
|
|
176
|
+
if (merged[fid] === void 0 || merged[fid] === null || merged[fid] === "") {
|
|
177
|
+
const dynDefault = field.resolvedDefaultValue;
|
|
178
|
+
const staticDefault = field.definition.defaultValue;
|
|
179
|
+
const resolved = dynDefault ?? staticDefault;
|
|
180
|
+
if (resolved !== void 0) {
|
|
181
|
+
merged[fid] = resolved;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return merged;
|
|
186
|
+
},
|
|
164
187
|
// Only recompute when the step changes (not on every data write)
|
|
165
188
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
166
189
|
[currentStep?.definition.id]
|
|
@@ -178,6 +201,7 @@ function useWaypointStep() {
|
|
|
178
201
|
const isFirstStep = stepIndex === 0;
|
|
179
202
|
const isLastStep = stepIndex === tree.steps.length - 1;
|
|
180
203
|
const progress = currentStep ? core.calculateProgress(tree.steps, currentStep.definition.id) : 0;
|
|
204
|
+
const canSkip = !!currentStep?.definition.skippable;
|
|
181
205
|
const goBack = react.useCallback(() => {
|
|
182
206
|
if (!currentStep) return;
|
|
183
207
|
const prev = core.getPreviousStep(tree.steps, currentStep.definition.id);
|
|
@@ -185,6 +209,23 @@ function useWaypointStep() {
|
|
|
185
209
|
router.push(prev.definition.url);
|
|
186
210
|
}
|
|
187
211
|
}, [currentStep, tree.steps, router]);
|
|
212
|
+
const skipStep = react.useCallback(() => {
|
|
213
|
+
if (!currentStep || !currentStep.definition.skippable) return;
|
|
214
|
+
const stepId = currentStep.definition.id;
|
|
215
|
+
store.getState().skipStep(stepId);
|
|
216
|
+
const updatedTree = core.resolveTree(schema, store.getState().data, externalVars, externalEnums, [
|
|
217
|
+
...skippedSteps,
|
|
218
|
+
stepId
|
|
219
|
+
]);
|
|
220
|
+
const nextStep = core.getNextStep(updatedTree.steps, stepId);
|
|
221
|
+
onStepSkipped?.(stepId);
|
|
222
|
+
if (nextStep) {
|
|
223
|
+
router.push(nextStep.definition.url);
|
|
224
|
+
} else {
|
|
225
|
+
store.getState().setCompleted(true);
|
|
226
|
+
onComplete?.(store.getState().data);
|
|
227
|
+
}
|
|
228
|
+
}, [currentStep, store, schema, externalVars, externalEnums, skippedSteps, onStepSkipped, onComplete, router]);
|
|
188
229
|
const handleSubmit = react.useCallback(async () => {
|
|
189
230
|
if (!currentStep) return;
|
|
190
231
|
const isValid = await form.trigger();
|
|
@@ -194,8 +235,12 @@ function useWaypointStep() {
|
|
|
194
235
|
try {
|
|
195
236
|
const oldVisibleIds = tree.steps.map((s) => s.definition.id).join(",");
|
|
196
237
|
store.getState().setStepData(currentStep.definition.id, values);
|
|
238
|
+
if (skippedSteps.includes(currentStep.definition.id)) {
|
|
239
|
+
store.getState().unskipStep(currentStep.definition.id);
|
|
240
|
+
}
|
|
197
241
|
const allData = store.getState().data;
|
|
198
|
-
const
|
|
242
|
+
const updatedSkipped = store.getState().skippedSteps;
|
|
243
|
+
const updatedTree = core.resolveTree(schema, allData, externalVars, externalEnums, updatedSkipped);
|
|
199
244
|
const newVisibleIds = updatedTree.steps.map((s) => s.definition.id).join(",");
|
|
200
245
|
if (oldVisibleIds !== newVisibleIds) {
|
|
201
246
|
store.getState().truncateHistoryAt(currentStep.definition.id);
|
|
@@ -221,6 +266,8 @@ function useWaypointStep() {
|
|
|
221
266
|
schema,
|
|
222
267
|
tree.steps,
|
|
223
268
|
externalVars,
|
|
269
|
+
externalEnums,
|
|
270
|
+
skippedSteps,
|
|
224
271
|
onDataChange,
|
|
225
272
|
onStepComplete,
|
|
226
273
|
onComplete,
|
|
@@ -235,7 +282,9 @@ function useWaypointStep() {
|
|
|
235
282
|
fields: visibleFields,
|
|
236
283
|
handleSubmit,
|
|
237
284
|
goBack,
|
|
285
|
+
skipStep,
|
|
238
286
|
isSubmitting,
|
|
287
|
+
canSkip,
|
|
239
288
|
errors: form.formState.errors
|
|
240
289
|
};
|
|
241
290
|
}
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/context.ts","../src/WaypointRunner.tsx","../src/useWaypointStep.ts"],"names":["createContext","useContext","useRouter","usePathname","useRef","createRuntimeStore","useStore","resolveTree","useEffect","hasPersistedState","state","tree","findLastValidStep","useMemo","jsxs","jsx","buildZodSchema","useForm","zodResolver","findStepIndex","calculateProgress","useCallback","getPreviousStep","getNextStep"],"mappings":";;;;;;;;;;AAyBO,IAAM,sBAAA,GACXA,oBAAkD,IAAI;AAMjD,SAAS,yBAAA,GAAyD;AACvE,EAAA,MAAM,GAAA,GAAMC,iBAAW,sBAAsB,CAAA;AAC7C,EAAA,IAAI,CAAC,GAAA,EAAK;AACR,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,KACF;AAAA,EACF;AACA,EAAA,OAAO,GAAA;AACT;AC8BO,SAAS,cAAA,CAAe;AAAA,EAC7B,MAAA;AAAA,EACA,eAAe,EAAC;AAAA,EAChB,gBAAgB,EAAC;AAAA,EACjB,SAAA;AAAA,EACA,UAAA;AAAA,EACA,cAAA;AAAA,EACA,YAAA;AAAA,EACA;AACF,CAAA,EAAwB;AACtB,EAAA,MAAM,SAASC,oBAAA,EAAU;AACzB,EAAA,MAAM,WAAWC,sBAAA,EAAY;AAK7B,EAAA,MAAM,QAAA,GAAWC,aAAqD,IAAI,CAAA;AAC1E,EAAA,IAAI,QAAA,CAAS,YAAY,IAAA,EAAM;AAC7B,IAAA,QAAA,CAAS,UAAUC,uBAAA,CAAmB;AAAA,MACpC,iBAAiB,MAAA,CAAO,eAAA;AAAA,MACxB,UAAU,MAAA,CAAO;AAAA,KAClB,CAAA;AAAA,EACH;AACA,EAAA,MAAM,QAAQ,QAAA,CAAS,OAAA;AAGvB,EAAA,MAAM,WAAA,GAAcC,gBAAA,CAAS,KAAA,EAAO,CAAC,CAAA,KAA4B;AAC/D,IAAA,IAAI,CAAC,CAAA,CAAE,MAAA,EAAQ,OAAO,EAAC;AACvB,IAAA,OAAOC,iBAAY,CAAA,CAAE,MAAA,EAAQ,EAAE,IAAA,EAAM,CAAA,CAAE,YAAY,CAAA,CAAE,mBAAA;AAAA,EACvD,CAAC,CAAA;AAKD,EAAAC,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,SAAA,GAAY,KAAA;AAEhB,IAAA,eAAe,UAAA,GAAa;AAM1B,MAAA,IACE,OAAO,eAAA,KAAoB,SAAA,IAC3BC,uBAAkB,KAAA,EAAO,MAAA,CAAO,EAAE,CAAA,EAClC;AAEA,QAAA,KAAA,CAAM,QAAA,EAAS,CAAE,MAAA,CAAO,MAAA,EAAQ,YAAY,CAAA;AAG5C,QAAA,MAAMC,MAAAA,GAAQ,MAAM,QAAA,EAAS;AAC7B,QAAA,IAAIA,OAAM,aAAA,EAAe;AACvB,UAAA,MAAMC,QAAOJ,gBAAA,CAAY,MAAA,EAAQG,MAAAA,CAAM,IAAA,EAAMA,OAAM,YAAY,CAAA;AAC/D,UAAA,MAAM,IAAA,GAAOC,MAAK,KAAA,CAAM,IAAA;AAAA,YACtB,CAAC,CAAA,KAAM,CAAA,CAAE,UAAA,CAAW,OAAOD,MAAAA,CAAM;AAAA,WACnC;AACA,UAAA,IAAI,IAAA,IAAQ,IAAA,CAAK,UAAA,CAAW,GAAA,KAAQ,QAAA,EAAU;AAC5C,YAAA,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA;AAAA,UACjC;AAAA,QACF;AACA,QAAA;AAAA,MACF;AAGA,MAAA,IAAI,IAAA,GAAO,EAAE,GAAG,aAAA,EAAc;AAE9B,MAAA,IAAI,SAAA,EAAW;AACb,QAAA,IAAI;AACF,UAAA,MAAM,OAAA,GAAU,MAAM,SAAA,EAAU;AAChC,UAAA,IAAI,CAAC,SAAA,EAAW;AACd,YAAA,IAAA,GAAO,EAAE,GAAG,IAAA,EAAM,GAAG,OAAA,EAAQ;AAAA,UAC/B;AAAA,QACF,SAAS,GAAA,EAAK;AACZ,UAAA,OAAA,CAAQ,KAAA,CAAM,8BAA8B,GAAG,CAAA;AAAA,QACjD;AAAA,MACF;AAEA,MAAA,IAAI,SAAA,EAAW;AAEf,MAAA,KAAA,CAAM,UAAS,CAAE,IAAA,CAAK,QAAQ,EAAE,IAAA,EAAM,cAAc,CAAA;AAIpD,MAAA,MAAM,KAAA,GAAQ,MAAM,QAAA,EAAS;AAC7B,MAAA,MAAM,OAAOH,gBAAA,CAAY,MAAA,EAAQ,KAAA,CAAM,IAAA,EAAM,MAAM,YAAY,CAAA;AAC/D,MAAA,MAAM,YAAYK,sBAAA,CAAkB,IAAA,CAAK,OAAO,KAAA,CAAM,IAAA,EAAM,MAAM,YAAY,CAAA;AAE9E,MAAA,IAAI,SAAA,IAAa,SAAA,CAAU,UAAA,CAAW,GAAA,KAAQ,QAAA,EAAU;AACtD,QAAA,MAAA,CAAO,IAAA,CAAK,SAAA,CAAU,UAAA,CAAW,GAAG,CAAA;AAAA,MACtC;AAAA,IACF;AAEA,IAAA,UAAA,EAAW;AAEX,IAAA,OAAO,MAAM;AACX,MAAA,SAAA,GAAY,IAAA;AAAA,IACd,CAAA;AAAA,EAEF,CAAA,EAAG,EAAE,CAAA;AAGL,EAAAJ,eAAA,CAAU,MAAM;AACd,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,YAAY,CAAA,EAAG;AACvD,MAAA,KAAA,CAAM,QAAA,EAAS,CAAE,cAAA,CAAe,GAAA,EAAK,KAAK,CAAA;AAAA,IAC5C;AAAA,EACF,CAAA,EAAG,CAAC,YAAA,EAAc,KAAK,CAAC,CAAA;AAExB,EAAA,MAAM,YAAA,GAAeK,aAAA;AAAA,IACnB,OAAO;AAAA,MACL,MAAA;AAAA,MACA,KAAA;AAAA,MACA,UAAA;AAAA,MACA,cAAA;AAAA,MACA;AAAA,KACF,CAAA;AAAA;AAAA,IAEA,CAAC,QAAQ,KAAK;AAAA,GAChB;AAGA,EAAA,IAAI,WAAA,CAAY,SAAS,CAAA,EAAG;AAC1B,IAAA,uBACEC,eAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QACC,IAAA,EAAK,OAAA;AAAA,QACL,KAAA,EAAO;AAAA,UACL,KAAA,EAAO,SAAA;AAAA,UACP,UAAA,EAAY,SAAA;AAAA,UACZ,MAAA,EAAQ,mBAAA;AAAA,UACR,YAAA,EAAc,CAAA;AAAA,UACd,OAAA,EAAS,cAAA;AAAA,UACT,UAAA,EAAY;AAAA,SACd;AAAA,QAEA,QAAA,EAAA;AAAA,0BAAAC,cAAA,CAAC,YAAO,QAAA,EAAA,wBAAA,EAAsB,CAAA;AAAA,0CAC7B,GAAA,EAAA,EAAE,KAAA,EAAO,EAAE,MAAA,EAAQ,cAAa,EAAG,QAAA,EAAA;AAAA,YAAA,sCAAA;AAAA,YACG,GAAA;AAAA,4BACrCA,cAAA,CAAC,MAAA,EAAA,EAAM,QAAA,EAAA,WAAA,CAAY,IAAA,CAAK,IAAI,CAAA,EAAE;AAAA,WAAA,EAChC;AAAA;AAAA;AAAA,KACF;AAAA,EAEJ;AAEA,EAAA,sCACG,sBAAA,CAAuB,QAAA,EAAvB,EAAgC,KAAA,EAAO,cACrC,QAAA,EACH,CAAA;AAEJ;AC5JO,SAAS,eAAA,GAAsC;AACpD,EAAA,MAAM,EAAE,MAAA,EAAQ,KAAA,EAAO,YAAY,cAAA,EAAgB,YAAA,KACjD,yBAAA,EAA0B;AAC5B,EAAA,MAAM,SAASb,oBAAAA,EAAU;AACzB,EAAA,MAAM,WAAWC,sBAAAA,EAAY;AAG7B,EAAA,MAAM,EAAE,IAAA,EAAM,YAAA,EAAc,aAAA,EAAe,cAAa,GAAIG,gBAAAA;AAAA,IAC1D,KAAA;AAAA,IACA,CAAC,CAAA,MAA6B;AAAA,MAC5B,MAAM,CAAA,CAAE,IAAA;AAAA,MACR,cAAc,CAAA,CAAE,YAAA;AAAA,MAChB,eAAe,CAAA,CAAE,aAAA;AAAA,MACjB,cAAc,CAAA,CAAE;AAAA,KAClB;AAAA,GACF;AAGA,EAAA,MAAM,IAAA,GAAOO,aAAAA;AAAA,IACX,MAAMN,gBAAAA,CAAY,MAAA,EAAQ,IAAA,EAAM,YAAY,CAAA;AAAA,IAC5C,CAAC,MAAA,EAAQ,IAAA,EAAM,YAAY;AAAA,GAC7B;AAGA,EAAA,MAAM,WAAA,GAAcM,cAAQ,MAAM;AAChC,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,CAAC,CAAA,KAAM;AAC5B,MAAA,MAAM,OAAA,GAAU,EAAE,UAAA,CAAW,GAAA;AAE7B,MAAA,OAAO,QAAA,KAAa,OAAA,IAAW,QAAA,CAAS,QAAA,CAAS,OAAO,CAAA;AAAA,IAC1D,CAAC,CAAA;AAAA,EACH,CAAA,EAAG,CAAC,IAAA,CAAK,KAAA,EAAO,QAAQ,CAAC,CAAA;AAGzB,EAAAL,gBAAU,MAAM;AACd,IAAA,IAAI,WAAA,IAAe,WAAA,CAAY,UAAA,CAAW,EAAA,KAAO,aAAA,EAAe;AAC9D,MAAA,KAAA,CAAM,QAAA,EAAS,CAAE,cAAA,CAAe,WAAA,CAAY,WAAW,EAAE,CAAA;AAAA,IAC3D;AAAA,EACF,CAAA,EAAG,CAAC,WAAA,EAAa,aAAA,EAAe,KAAK,CAAC,CAAA;AAGtC,EAAA,MAAM,aAAA,GAAgBK,aAAAA;AAAA,IACpB,MAAM,aAAa,MAAA,CAAO,MAAA,CAAO,CAAC,CAAA,KAAM,CAAA,CAAE,OAAO,CAAA,IAAK,EAAC;AAAA,IACvD,CAAC,WAAW;AAAA,GACd;AAGA,EAAA,MAAM,SAAA,GAAYA,cAAQ,MAAMG,mBAAA,CAAe,aAAa,CAAA,EAAG,CAAC,aAAa,CAAC,CAAA;AAG9E,EAAA,MAAM,aAAA,GAAgBH,aAAAA;AAAA,IACpB,MAAO,cAAe,IAAA,CAAK,WAAA,CAAY,WAAW,EAAE,CAAA,IAAK,EAAC,GAAK,EAAC;AAAA;AAAA;AAAA,IAGhE,CAAC,WAAA,EAAa,UAAA,CAAW,EAAE;AAAA,GAC7B;AAGA,EAAA,MAAM,OAAOI,qBAAA,CAAqB;AAAA,IAChC,QAAA,EAAUC,gBAAY,SAAS,CAAA;AAAA,IAC/B;AAAA,GACD,CAAA;AAGD,EAAAV,gBAAU,MAAM;AACd,IAAA,IAAI,WAAA,EAAa;AACf,MAAA,IAAA,CAAK,MAAM,IAAA,CAAK,WAAA,CAAY,WAAW,EAAE,CAAA,IAAK,EAAE,CAAA;AAAA,IAClD;AAAA,EAEF,CAAA,EAAG,CAAC,WAAA,EAAa,UAAA,CAAW,EAAE,CAAC,CAAA;AAG/B,EAAA,MAAM,SAAA,GAAY,cACdW,kBAAA,CAAc,IAAA,CAAK,OAAO,WAAA,CAAY,UAAA,CAAW,EAAE,CAAA,GACnD,EAAA;AACJ,EAAA,MAAM,cAAc,SAAA,KAAc,CAAA;AAClC,EAAA,MAAM,UAAA,GAAa,SAAA,KAAc,IAAA,CAAK,KAAA,CAAM,MAAA,GAAS,CAAA;AACrD,EAAA,MAAM,QAAA,GAAW,cACbC,sBAAA,CAAkB,IAAA,CAAK,OAAO,WAAA,CAAY,UAAA,CAAW,EAAE,CAAA,GACvD,CAAA;AAGJ,EAAA,MAAM,MAAA,GAASC,kBAAY,MAAM;AAC/B,IAAA,IAAI,CAAC,WAAA,EAAa;AAClB,IAAA,MAAM,OAAOC,oBAAA,CAAgB,IAAA,CAAK,KAAA,EAAO,WAAA,CAAY,WAAW,EAAE,CAAA;AAClE,IAAA,IAAI,IAAA,EAAM;AACR,MAAA,MAAA,CAAO,IAAA,CAAK,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA;AAAA,IACjC;AAAA,EACF,GAAG,CAAC,WAAA,EAAa,IAAA,CAAK,KAAA,EAAO,MAAM,CAAC,CAAA;AAGpC,EAAA,MAAM,YAAA,GAAeD,kBAAY,YAAY;AAC3C,IAAA,IAAI,CAAC,WAAA,EAAa;AAGlB,IAAA,MAAM,OAAA,GAAU,MAAM,IAAA,CAAK,OAAA,EAAQ;AACnC,IAAA,IAAI,CAAC,OAAA,EAAS;AAEd,IAAA,MAAM,MAAA,GAAS,KAAK,SAAA,EAAU;AAG9B,IAAA,KAAA,CAAM,QAAA,EAAS,CAAE,eAAA,CAAgB,IAAI,CAAA;AAErC,IAAA,IAAI;AAEF,MAAA,MAAM,aAAA,GAAgB,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,UAAA,CAAW,EAAE,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAGrE,MAAA,KAAA,CAAM,UAAS,CAAE,WAAA,CAAY,WAAA,CAAY,UAAA,CAAW,IAAI,MAAM,CAAA;AAG9D,MAAA,MAAM,OAAA,GAAU,KAAA,CAAM,QAAA,EAAS,CAAE,IAAA;AACjC,MAAA,MAAM,WAAA,GAAcd,gBAAAA,CAAY,MAAA,EAAQ,OAAA,EAAS,YAAY,CAAA;AAC7D,MAAA,MAAM,aAAA,GAAgB,WAAA,CAAY,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,KAAM,CAAA,CAAE,UAAA,CAAW,EAAE,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAI5E,MAAA,IAAI,kBAAkB,aAAA,EAAe;AACnC,QAAA,KAAA,CAAM,QAAA,EAAS,CAAE,iBAAA,CAAkB,WAAA,CAAY,WAAW,EAAE,CAAA;AAAA,MAC9D;AAGA,MAAA,YAAA,GAAe,OAAO,CAAA;AAGtB,MAAA,IAAI,cAAA,EAAgB;AAClB,QAAA,MAAM,cAAA,CAAe,WAAA,CAAY,UAAA,CAAW,EAAA,EAAI,MAAM,CAAA;AAAA,MACxD;AAGA,MAAA,MAAM,WAAWgB,gBAAA,CAAY,WAAA,CAAY,KAAA,EAAO,WAAA,CAAY,WAAW,EAAE,CAAA;AACzE,MAAA,IAAI,QAAA,EAAU;AACZ,QAAA,MAAA,CAAO,IAAA,CAAK,QAAA,CAAS,UAAA,CAAW,GAAG,CAAA;AAAA,MACrC,CAAA,MAAO;AACL,QAAA,KAAA,CAAM,QAAA,EAAS,CAAE,YAAA,CAAa,IAAI,CAAA;AAClC,QAAA,MAAM,aAAa,OAAO,CAAA;AAAA,MAC5B;AAAA,IACF,CAAA,SAAE;AACA,MAAA,KAAA,CAAM,QAAA,EAAS,CAAE,eAAA,CAAgB,KAAK,CAAA;AAAA,IACxC;AAAA,EACF,CAAA,EAAG;AAAA,IACD,WAAA;AAAA,IACA,IAAA;AAAA,IACA,KAAA;AAAA,IACA,MAAA;AAAA,IACA,IAAA,CAAK,KAAA;AAAA,IACL,YAAA;AAAA,IACA,YAAA;AAAA,IACA,cAAA;AAAA,IACA,UAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,OAAO;AAAA,IACL,WAAA;AAAA,IACA,QAAA;AAAA,IACA,WAAA;AAAA,IACA,UAAA;AAAA,IACA,IAAA;AAAA,IACA,MAAA,EAAQ,aAAA;AAAA,IACR,YAAA;AAAA,IACA,MAAA;AAAA,IACA,YAAA;AAAA,IACA,MAAA,EAAQ,KAAK,SAAA,CAAU;AAAA,GACzB;AACF","file":"index.cjs","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { StoreApi } from \"zustand\";\n\nimport type { WaypointSchema } from \"@waypointjs/core\";\nimport type { WaypointRuntimeStore } from \"@waypointjs/core\";\n\n// ---------------------------------------------------------------------------\n// Context value\n// ---------------------------------------------------------------------------\n\nexport interface WaypointRuntimeContextValue {\n schema: WaypointSchema;\n store: StoreApi<WaypointRuntimeStore>;\n onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;\n onStepComplete?: (\n stepId: string,\n data: Record<string, unknown>\n ) => void | Promise<void>;\n onDataChange?: (data: Record<string, Record<string, unknown>>) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nexport const WaypointRuntimeContext =\n createContext<WaypointRuntimeContextValue | null>(null);\n\n/**\n * Returns the current WaypointRunner context.\n * Must be used inside a `<WaypointRunner>` component.\n */\nexport function useWaypointRuntimeContext(): WaypointRuntimeContextValue {\n const ctx = useContext(WaypointRuntimeContext);\n if (!ctx) {\n throw new Error(\n \"useWaypointRuntimeContext must be called inside a <WaypointRunner> component.\"\n );\n }\n return ctx;\n}\n","\"use client\";\n\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { useStore } from \"zustand\";\nimport { useRouter, usePathname } from \"next/navigation\";\n\nimport {\n createRuntimeStore,\n hasPersistedState,\n resolveTree,\n findLastValidStep,\n} from \"@waypointjs/core\";\nimport type { WaypointSchema, WaypointRuntimeStore } from \"@waypointjs/core\";\n\nimport { WaypointRuntimeContext } from \"./context\";\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface WaypointRunnerProps {\n schema: WaypointSchema;\n externalVars?: Record<string, unknown>;\n defaultValues?: Record<string, Record<string, unknown>>;\n /** Async function to load previously-saved data (for deep-link resume) */\n fetchData?: () => Promise<Record<string, Record<string, unknown>>>;\n /** Called when the user completes the last step */\n onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;\n /** Called after each step is validated and submitted */\n onStepComplete?: (\n stepId: string,\n data: Record<string, unknown>\n ) => void | Promise<void>;\n /** Called whenever any field value changes */\n onDataChange?: (data: Record<string, Record<string, unknown>>) => void;\n children: React.ReactNode;\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n/**\n * Context provider that initialises the runtime store and wires up callbacks.\n *\n * ### Multi-journey support\n * Each `<WaypointRunner>` creates its own **isolated** Zustand store instance,\n * so multiple runners with different schemas can coexist in the same app\n * without any state interference:\n *\n * ```tsx\n * // Both journeys live side-by-side — each has its own store\n * <WaypointRunner schema={projectSchema}>…</WaypointRunner>\n * <WaypointRunner schema={depositSchema}>…</WaypointRunner>\n * ```\n *\n * ### Pause & Resume\n * When `schema.persistenceMode === \"zustand\"`, each journey's state\n * (`data`, `currentStepId`, `history`) is saved to localStorage under a\n * per-schema key (`waypoint-runtime-<schemaId>`).\n *\n * On remount, `WaypointRunner` detects the saved state and calls `resume()`\n * instead of `init()`, so the user lands back exactly where they left off —\n * navigation state and form data intact.\n *\n * @example\n * <WaypointRunner schema={mySchema} onComplete={handleComplete}>\n * {children}\n * </WaypointRunner>\n */\nexport function WaypointRunner({\n schema,\n externalVars = {},\n defaultValues = {},\n fetchData,\n onComplete,\n onStepComplete,\n onDataChange,\n children,\n}: WaypointRunnerProps) {\n const router = useRouter();\n const pathname = usePathname();\n\n // Create store once per runner instance.\n // The persist middleware (when active) hydrates synchronously from localStorage,\n // so by the time the first useEffect fires, persisted state is already in the store.\n const storeRef = useRef<ReturnType<typeof createRuntimeStore> | null>(null);\n if (storeRef.current === null) {\n storeRef.current = createRuntimeStore({\n persistenceMode: schema.persistenceMode,\n schemaId: schema.id,\n });\n }\n const store = storeRef.current;\n\n // Subscribe to missing blocking vars to show error UI reactively\n const missingVars = useStore(store, (s: WaypointRuntimeStore) => {\n if (!s.schema) return [];\n return resolveTree(s.schema, s.data, s.externalVars).missingExternalVars;\n });\n\n // ---------------------------------------------------------------------------\n // Init / Resume on mount\n // ---------------------------------------------------------------------------\n useEffect(() => {\n let cancelled = false;\n\n async function initialize() {\n // ── Resume path ────────────────────────────────────────────────────────\n // When persistenceMode is \"zustand\", the persist middleware has already\n // synchronously hydrated the store from localStorage by the time this\n // effect runs. If the saved schemaId matches, we resume instead of\n // resetting so the user picks up exactly where they left off.\n if (\n schema.persistenceMode === \"zustand\" &&\n hasPersistedState(store, schema.id)\n ) {\n // Keep data + currentStepId + history — just update schema & externalVars\n store.getState().resume(schema, externalVars);\n\n // Navigate to the persisted step\n const state = store.getState();\n if (state.currentStepId) {\n const tree = resolveTree(schema, state.data, state.externalVars);\n const step = tree.steps.find(\n (s) => s.definition.id === state.currentStepId\n );\n if (step && step.definition.url !== pathname) {\n router.push(step.definition.url);\n }\n }\n return;\n }\n\n // ── Fresh-start path ───────────────────────────────────────────────────\n let data = { ...defaultValues };\n\n if (fetchData) {\n try {\n const fetched = await fetchData();\n if (!cancelled) {\n data = { ...data, ...fetched };\n }\n } catch (err) {\n console.error(\"Waypoint: fetchData failed\", err);\n }\n }\n\n if (cancelled) return;\n\n store.getState().init(schema, { data, externalVars });\n\n // Deep-link resume: if the user already has data, redirect to the last\n // valid step instead of forcing them back to step 1.\n const state = store.getState();\n const tree = resolveTree(schema, state.data, state.externalVars);\n const lastValid = findLastValidStep(tree.steps, state.data, state.externalVars);\n\n if (lastValid && lastValid.definition.url !== pathname) {\n router.push(lastValid.definition.url);\n }\n }\n\n initialize();\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Sync externalVars into the store when the prop changes after mount\n useEffect(() => {\n for (const [key, value] of Object.entries(externalVars)) {\n store.getState().setExternalVar(key, value);\n }\n }, [externalVars, store]);\n\n const contextValue = useMemo(\n () => ({\n schema,\n store,\n onComplete,\n onStepComplete,\n onDataChange,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [schema, store]\n );\n\n // Show a blocking error if required external variables are missing after init\n if (missingVars.length > 0) {\n return (\n <div\n role=\"alert\"\n style={{\n color: \"#b91c1c\",\n background: \"#fef2f2\",\n border: \"1px solid #fca5a5\",\n borderRadius: 8,\n padding: \"1rem 1.25rem\",\n fontFamily: \"sans-serif\",\n }}\n >\n <strong>Waypoint Runtime Error</strong>\n <p style={{ margin: \"0.5rem 0 0\" }}>\n Missing required external variables:{\" \"}\n <code>{missingVars.join(\", \")}</code>\n </p>\n </div>\n );\n }\n\n return (\n <WaypointRuntimeContext.Provider value={contextValue}>\n {children}\n </WaypointRuntimeContext.Provider>\n );\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useMemo } from \"react\";\nimport { useRouter, usePathname } from \"next/navigation\";\nimport { useStore } from \"zustand\";\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport type { FieldErrors, FieldValues, UseFormReturn } from \"react-hook-form\";\n\nimport {\n resolveTree,\n buildZodSchema,\n calculateProgress,\n getNextStep,\n getPreviousStep,\n findStepIndex,\n} from \"@waypointjs/core\";\nimport type { ResolvedField, ResolvedStep, WaypointRuntimeStore } from \"@waypointjs/core\";\n\nimport { useWaypointRuntimeContext } from \"./context\";\n\n// ---------------------------------------------------------------------------\n// Return type\n// ---------------------------------------------------------------------------\n\nexport interface WaypointStepReturn {\n // Step context\n currentStep: ResolvedStep | undefined;\n progress: number;\n isFirstStep: boolean;\n isLastStep: boolean;\n\n // React Hook Form\n form: UseFormReturn<FieldValues>;\n /** Visible fields for the current step */\n fields: ResolvedField[];\n\n // Actions\n /** Validate → persist → onStepComplete → navigate next (or onComplete on last step) */\n handleSubmit: () => Promise<void>;\n goBack: () => void;\n\n // State\n isSubmitting: boolean;\n errors: FieldErrors;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Per-page hook for multi-step forms powered by WaypointRunner.\n *\n * Derives the current step from the URL, provides react-hook-form wired to\n * the step's Zod schema, and handles navigation automatically.\n *\n * Must be used inside a `<WaypointRunner>` component.\n *\n * @example\n * const { form, fields, handleSubmit, progress } = useWaypointStep();\n */\nexport function useWaypointStep(): WaypointStepReturn {\n const { schema, store, onComplete, onStepComplete, onDataChange } =\n useWaypointRuntimeContext();\n const router = useRouter();\n const pathname = usePathname();\n\n // Subscribe to store state\n const { data, externalVars, currentStepId, isSubmitting } = useStore(\n store,\n (s: WaypointRuntimeStore) => ({\n data: s.data,\n externalVars: s.externalVars,\n currentStepId: s.currentStepId,\n isSubmitting: s.isSubmitting,\n })\n );\n\n // Resolve the full tree\n const tree = useMemo(\n () => resolveTree(schema, data, externalVars),\n [schema, data, externalVars]\n );\n\n // Find the step matching the current pathname\n const currentStep = useMemo(() => {\n return tree.steps.find((s) => {\n const stepUrl = s.definition.url;\n // Exact match, or pathname ends with step URL (handles leading slash variants)\n return pathname === stepUrl || pathname.endsWith(stepUrl);\n });\n }, [tree.steps, pathname]);\n\n // Sync currentStepId into the store whenever the page changes\n useEffect(() => {\n if (currentStep && currentStep.definition.id !== currentStepId) {\n store.getState().setCurrentStep(currentStep.definition.id);\n }\n }, [currentStep, currentStepId, store]);\n\n // Visible fields for the current step\n const visibleFields = useMemo(\n () => currentStep?.fields.filter((f) => f.visible) ?? [],\n [currentStep]\n );\n\n // Build the Zod schema from visible fields\n const zodSchema = useMemo(() => buildZodSchema(visibleFields), [visibleFields]);\n\n // Existing step data used as default values\n const defaultValues = useMemo(\n () => (currentStep ? (data[currentStep.definition.id] ?? {}) : {}),\n // Only recompute when the step changes (not on every data write)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [currentStep?.definition.id]\n );\n\n // React Hook Form instance\n const form = useForm<FieldValues>({\n resolver: zodResolver(zodSchema),\n defaultValues,\n });\n\n // Reset form defaults when step changes\n useEffect(() => {\n if (currentStep) {\n form.reset(data[currentStep.definition.id] ?? {});\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [currentStep?.definition.id]);\n\n // Navigation position\n const stepIndex = currentStep\n ? findStepIndex(tree.steps, currentStep.definition.id)\n : -1;\n const isFirstStep = stepIndex === 0;\n const isLastStep = stepIndex === tree.steps.length - 1;\n const progress = currentStep\n ? calculateProgress(tree.steps, currentStep.definition.id)\n : 0;\n\n // goBack\n const goBack = useCallback(() => {\n if (!currentStep) return;\n const prev = getPreviousStep(tree.steps, currentStep.definition.id);\n if (prev) {\n router.push(prev.definition.url);\n }\n }, [currentStep, tree.steps, router]);\n\n // handleSubmit\n const handleSubmit = useCallback(async () => {\n if (!currentStep) return;\n\n // 1. Validate via RHF + Zod\n const isValid = await form.trigger();\n if (!isValid) return;\n\n const values = form.getValues();\n\n // 2. Persist data + update submitting state\n store.getState().setIsSubmitting(true);\n\n try {\n // 3. Snapshot visible step IDs before writing (to detect tree changes)\n const oldVisibleIds = tree.steps.map((s) => s.definition.id).join(\",\");\n\n // 4. Write validated data into the store\n store.getState().setStepData(currentStep.definition.id, values);\n\n // 5. Re-resolve tree with updated data — step visibility may have changed\n const allData = store.getState().data;\n const updatedTree = resolveTree(schema, allData, externalVars);\n const newVisibleIds = updatedTree.steps.map((s) => s.definition.id).join(\",\");\n\n // 6. If the visible tree changed, truncate stale forward history\n // (e.g. user went back, changed a dep value, tree changed → old path is invalid)\n if (oldVisibleIds !== newVisibleIds) {\n store.getState().truncateHistoryAt(currentStep.definition.id);\n }\n\n // 7. onDataChange callback\n onDataChange?.(allData);\n\n // 8. onStepComplete callback (may be async, e.g. backend-step mode)\n if (onStepComplete) {\n await onStepComplete(currentStep.definition.id, values);\n }\n\n // 9. Navigate using the UPDATED tree so we follow the new step order\n const nextStep = getNextStep(updatedTree.steps, currentStep.definition.id);\n if (nextStep) {\n router.push(nextStep.definition.url);\n } else {\n store.getState().setCompleted(true);\n await onComplete?.(allData);\n }\n } finally {\n store.getState().setIsSubmitting(false);\n }\n }, [\n currentStep,\n form,\n store,\n schema,\n tree.steps,\n externalVars,\n onDataChange,\n onStepComplete,\n onComplete,\n router,\n ]);\n\n return {\n currentStep,\n progress,\n isFirstStep,\n isLastStep,\n form,\n fields: visibleFields,\n handleSubmit,\n goBack,\n isSubmitting,\n errors: form.formState.errors,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/context.ts","../src/WaypointRunner.tsx","../src/useWaypointStep.ts"],"names":["createContext","useContext","useRouter","usePathname","useRef","createRuntimeStore","useStore","resolveTree","useEffect","hasPersistedState","state","tree","findLastValidStep","useMemo","jsxs","jsx","buildZodSchema","useForm","zodResolver","findStepIndex","calculateProgress","useCallback","getPreviousStep","getNextStep"],"mappings":"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","file":"index.cjs","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { StoreApi } from \"zustand\";\n\nimport type { WaypointSchema, CustomTypeDefinition, ExternalEnum } from \"@waypointjs/core\";\nimport type { WaypointRuntimeStore } from \"@waypointjs/core\";\n\n// ---------------------------------------------------------------------------\n// Context value\n// ---------------------------------------------------------------------------\n\nexport interface WaypointRuntimeContextValue {\n schema: WaypointSchema;\n store: StoreApi<WaypointRuntimeStore>;\n onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;\n onStepComplete?: (\n stepId: string,\n data: Record<string, unknown>\n ) => void | Promise<void>;\n onDataChange?: (data: Record<string, Record<string, unknown>>) => void;\n /** Called when the user skips a step */\n onStepSkipped?: (stepId: string) => void;\n /** App-provided custom field types — available for rendering custom fields */\n customFieldTypes?: CustomTypeDefinition[];\n /** App-provided external enum lists — resolved into ResolvedField.resolvedOptions by the tree resolver */\n externalEnums?: ExternalEnum[];\n}\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nexport const WaypointRuntimeContext =\n createContext<WaypointRuntimeContextValue | null>(null);\n\n/**\n * Returns the current WaypointRunner context.\n * Must be used inside a `<WaypointRunner>` component.\n */\nexport function useWaypointRuntimeContext(): WaypointRuntimeContextValue {\n const ctx = useContext(WaypointRuntimeContext);\n if (!ctx) {\n throw new Error(\n \"useWaypointRuntimeContext must be called inside a <WaypointRunner> component.\"\n );\n }\n return ctx;\n}\n","\"use client\";\n\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { useStore } from \"zustand\";\nimport { useRouter, usePathname } from \"next/navigation\";\n\nimport {\n createRuntimeStore,\n hasPersistedState,\n resolveTree,\n findLastValidStep,\n} from \"@waypointjs/core\";\nimport type { WaypointSchema, WaypointRuntimeStore, CustomTypeDefinition, ExternalEnum } from \"@waypointjs/core\";\n\nimport { WaypointRuntimeContext } from \"./context\";\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface WaypointRunnerProps {\n schema: WaypointSchema;\n externalVars?: Record<string, unknown>;\n defaultValues?: Record<string, Record<string, unknown>>;\n /** Async function to load previously-saved data (for deep-link resume) */\n fetchData?: () => Promise<Record<string, Record<string, unknown>>>;\n /** Called when the user completes the last step */\n onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;\n /** Called after each step is validated and submitted */\n onStepComplete?: (\n stepId: string,\n data: Record<string, unknown>\n ) => void | Promise<void>;\n /** Called whenever any field value changes */\n onDataChange?: (data: Record<string, Record<string, unknown>>) => void;\n /** Called when the user skips a step */\n onStepSkipped?: (stepId: string) => void;\n /** App-provided custom field types — exposed via context for custom field rendering */\n customFieldTypes?: CustomTypeDefinition[];\n /** App-provided external enum lists — resolved into ResolvedField.resolvedOptions */\n externalEnums?: ExternalEnum[];\n children: React.ReactNode;\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n/**\n * Context provider that initialises the runtime store and wires up callbacks.\n *\n * ### Multi-journey support\n * Each `<WaypointRunner>` creates its own **isolated** Zustand store instance,\n * so multiple runners with different schemas can coexist in the same app\n * without any state interference:\n *\n * ```tsx\n * // Both journeys live side-by-side — each has its own store\n * <WaypointRunner schema={projectSchema}>…</WaypointRunner>\n * <WaypointRunner schema={depositSchema}>…</WaypointRunner>\n * ```\n *\n * ### Pause & Resume\n * When `schema.persistenceMode === \"zustand\"`, each journey's state\n * (`data`, `currentStepId`, `history`) is saved to localStorage under a\n * per-schema key (`waypoint-runtime-<schemaId>`).\n *\n * On remount, `WaypointRunner` detects the saved state and calls `resume()`\n * instead of `init()`, so the user lands back exactly where they left off —\n * navigation state and form data intact.\n *\n * @example\n * <WaypointRunner schema={mySchema} onComplete={handleComplete}>\n * {children}\n * </WaypointRunner>\n */\nexport function WaypointRunner({\n schema,\n externalVars = {},\n defaultValues = {},\n fetchData,\n onComplete,\n onStepComplete,\n onDataChange,\n onStepSkipped,\n customFieldTypes,\n externalEnums,\n children,\n}: WaypointRunnerProps) {\n const router = useRouter();\n const pathname = usePathname();\n\n // Create store once per runner instance.\n // The persist middleware (when active) hydrates synchronously from localStorage,\n // so by the time the first useEffect fires, persisted state is already in the store.\n const storeRef = useRef<ReturnType<typeof createRuntimeStore> | null>(null);\n if (storeRef.current === null) {\n storeRef.current = createRuntimeStore({\n persistenceMode: schema.persistenceMode,\n schemaId: schema.id,\n });\n }\n const store = storeRef.current;\n\n // Subscribe to missing blocking vars to show error UI reactively\n const missingVars = useStore(store, (s: WaypointRuntimeStore) => {\n if (!s.schema) return [];\n return resolveTree(s.schema, s.data, s.externalVars).missingExternalVars;\n });\n\n // ---------------------------------------------------------------------------\n // Init / Resume on mount\n // ---------------------------------------------------------------------------\n useEffect(() => {\n let cancelled = false;\n\n async function initialize() {\n // ── Resume path ────────────────────────────────────────────────────────\n // When persistenceMode is \"zustand\", the persist middleware has already\n // synchronously hydrated the store from localStorage by the time this\n // effect runs. If the saved schemaId matches, we resume instead of\n // resetting so the user picks up exactly where they left off.\n if (\n schema.persistenceMode === \"zustand\" &&\n hasPersistedState(store, schema.id)\n ) {\n // Keep data + currentStepId + history — just update schema & externalVars\n store.getState().resume(schema, externalVars);\n\n // Navigate to the persisted step\n const state = store.getState();\n if (state.currentStepId) {\n const tree = resolveTree(schema, state.data, state.externalVars, externalEnums);\n const step = tree.steps.find(\n (s) => s.definition.id === state.currentStepId\n );\n if (step && step.definition.url !== pathname) {\n router.push(step.definition.url);\n }\n }\n return;\n }\n\n // ── Fresh-start path ───────────────────────────────────────────────────\n let data = { ...defaultValues };\n\n if (fetchData) {\n try {\n const fetched = await fetchData();\n if (!cancelled) {\n data = { ...data, ...fetched };\n }\n } catch (err) {\n console.error(\"Waypoint: fetchData failed\", err);\n }\n }\n\n if (cancelled) return;\n\n store.getState().init(schema, { data, externalVars });\n\n // Deep-link resume: if the user already has data, redirect to the last\n // valid step instead of forcing them back to step 1.\n const state = store.getState();\n const tree = resolveTree(schema, state.data, state.externalVars, externalEnums);\n const lastValid = findLastValidStep(tree.steps, state.data, state.externalVars);\n\n if (lastValid && lastValid.definition.url !== pathname) {\n router.push(lastValid.definition.url);\n }\n }\n\n initialize();\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Sync externalVars into the store when the prop changes after mount\n useEffect(() => {\n for (const [key, value] of Object.entries(externalVars)) {\n store.getState().setExternalVar(key, value);\n }\n }, [externalVars, store]);\n\n const contextValue = useMemo(\n () => ({\n schema,\n store,\n onComplete,\n onStepComplete,\n onDataChange,\n onStepSkipped,\n customFieldTypes,\n externalEnums,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [schema, store, externalEnums]\n );\n\n // Show a blocking error if required external variables are missing after init\n if (missingVars.length > 0) {\n return (\n <div\n role=\"alert\"\n style={{\n color: \"#b91c1c\",\n background: \"#fef2f2\",\n border: \"1px solid #fca5a5\",\n borderRadius: 8,\n padding: \"1rem 1.25rem\",\n fontFamily: \"sans-serif\",\n }}\n >\n <strong>Waypoint Runtime Error</strong>\n <p style={{ margin: \"0.5rem 0 0\" }}>\n Missing required external variables:{\" \"}\n <code>{missingVars.join(\", \")}</code>\n </p>\n </div>\n );\n }\n\n return (\n <WaypointRuntimeContext.Provider value={contextValue}>\n {children}\n </WaypointRuntimeContext.Provider>\n );\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useMemo } from \"react\";\nimport { useRouter, usePathname } from \"next/navigation\";\nimport { useStore } from \"zustand\";\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport type { FieldErrors, FieldValues, UseFormReturn } from \"react-hook-form\";\n\nimport {\n resolveTree,\n buildZodSchema,\n calculateProgress,\n getNextStep,\n getPreviousStep,\n findStepIndex,\n} from \"@waypointjs/core\";\nimport type { ResolvedField, ResolvedStep, WaypointRuntimeStore } from \"@waypointjs/core\";\n\nimport { useWaypointRuntimeContext } from \"./context\";\n\n// ---------------------------------------------------------------------------\n// Return type\n// ---------------------------------------------------------------------------\n\nexport interface WaypointStepReturn {\n // Step context\n currentStep: ResolvedStep | undefined;\n progress: number;\n isFirstStep: boolean;\n isLastStep: boolean;\n\n // React Hook Form\n form: UseFormReturn<FieldValues>;\n /** Visible fields for the current step */\n fields: ResolvedField[];\n\n // Actions\n /** Validate → persist → onStepComplete → navigate next (or onComplete on last step) */\n handleSubmit: () => Promise<void>;\n goBack: () => void;\n /** Skip this step without validation — only available when step.skippable is true */\n skipStep: () => void;\n\n // State\n isSubmitting: boolean;\n /** Whether the current step can be skipped */\n canSkip: boolean;\n errors: FieldErrors;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Per-page hook for multi-step forms powered by WaypointRunner.\n *\n * Derives the current step from the URL, provides react-hook-form wired to\n * the step's Zod schema, and handles navigation automatically.\n *\n * Must be used inside a `<WaypointRunner>` component.\n *\n * @example\n * const { form, fields, handleSubmit, progress } = useWaypointStep();\n */\nexport function useWaypointStep(): WaypointStepReturn {\n const { schema, store, onComplete, onStepComplete, onDataChange, onStepSkipped, externalEnums } =\n useWaypointRuntimeContext();\n const router = useRouter();\n const pathname = usePathname();\n\n // Subscribe to store state\n const { data, externalVars, currentStepId, skippedSteps, isSubmitting } = useStore(\n store,\n (s: WaypointRuntimeStore) => ({\n data: s.data,\n externalVars: s.externalVars,\n currentStepId: s.currentStepId,\n skippedSteps: s.skippedSteps,\n isSubmitting: s.isSubmitting,\n })\n );\n\n // Resolve the full tree\n const tree = useMemo(\n () => resolveTree(schema, data, externalVars, externalEnums, skippedSteps),\n [schema, data, externalVars, externalEnums, skippedSteps]\n );\n\n // Find the step matching the current pathname\n const currentStep = useMemo(() => {\n return tree.steps.find((s) => {\n const stepUrl = s.definition.url;\n // Exact match, or pathname ends with step URL (handles leading slash variants)\n return pathname === stepUrl || pathname.endsWith(stepUrl);\n });\n }, [tree.steps, pathname]);\n\n // Sync currentStepId into the store whenever the page changes\n useEffect(() => {\n if (currentStep && currentStep.definition.id !== currentStepId) {\n store.getState().setCurrentStep(currentStep.definition.id);\n }\n }, [currentStep, currentStepId, store]);\n\n // Visible fields for the current step\n const visibleFields = useMemo(\n () => currentStep?.fields.filter((f) => f.visible) ?? [],\n [currentStep]\n );\n\n // Build the Zod schema from visible fields (pass data for cross-field validation)\n const zodSchema = useMemo(() => buildZodSchema(visibleFields, externalEnums, data), [visibleFields, externalEnums, data]);\n\n // Existing step data used as default values, with dynamic defaults as fallback\n const defaultValues = useMemo(\n () => {\n if (!currentStep) return {};\n const stored = data[currentStep.definition.id] ?? {};\n // Merge dynamic/static defaults for fields that have no stored value\n const merged: Record<string, unknown> = { ...stored };\n for (const field of currentStep.fields) {\n const fid = field.definition.id;\n if (merged[fid] === undefined || merged[fid] === null || merged[fid] === \"\") {\n const dynDefault = field.resolvedDefaultValue;\n const staticDefault = field.definition.defaultValue;\n const resolved = dynDefault ?? staticDefault;\n if (resolved !== undefined) {\n merged[fid] = resolved;\n }\n }\n }\n return merged;\n },\n // Only recompute when the step changes (not on every data write)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [currentStep?.definition.id]\n );\n\n // React Hook Form instance\n const form = useForm<FieldValues>({\n resolver: zodResolver(zodSchema),\n defaultValues,\n });\n\n // Reset form defaults when step changes\n useEffect(() => {\n if (currentStep) {\n form.reset(data[currentStep.definition.id] ?? {});\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [currentStep?.definition.id]);\n\n // Navigation position\n const stepIndex = currentStep\n ? findStepIndex(tree.steps, currentStep.definition.id)\n : -1;\n const isFirstStep = stepIndex === 0;\n const isLastStep = stepIndex === tree.steps.length - 1;\n const progress = currentStep\n ? calculateProgress(tree.steps, currentStep.definition.id)\n : 0;\n\n // Whether the current step can be skipped\n const canSkip = !!currentStep?.definition.skippable;\n\n // goBack\n const goBack = useCallback(() => {\n if (!currentStep) return;\n const prev = getPreviousStep(tree.steps, currentStep.definition.id);\n if (prev) {\n router.push(prev.definition.url);\n }\n }, [currentStep, tree.steps, router]);\n\n // skipStep — bypass validation, mark as skipped, navigate to next\n const skipStep = useCallback(() => {\n if (!currentStep || !currentStep.definition.skippable) return;\n\n const stepId = currentStep.definition.id;\n store.getState().skipStep(stepId);\n\n // Re-resolve tree after marking as skipped (conditions may depend on $step.X.skipped)\n const updatedTree = resolveTree(schema, store.getState().data, externalVars, externalEnums, [\n ...skippedSteps,\n stepId,\n ]);\n const nextStep = getNextStep(updatedTree.steps, stepId);\n\n onStepSkipped?.(stepId);\n\n if (nextStep) {\n router.push(nextStep.definition.url);\n } else {\n store.getState().setCompleted(true);\n onComplete?.(store.getState().data);\n }\n }, [currentStep, store, schema, externalVars, externalEnums, skippedSteps, onStepSkipped, onComplete, router]);\n\n // handleSubmit\n const handleSubmit = useCallback(async () => {\n if (!currentStep) return;\n\n // 1. Validate via RHF + Zod\n const isValid = await form.trigger();\n if (!isValid) return;\n\n const values = form.getValues();\n\n // 2. Persist data + update submitting state\n store.getState().setIsSubmitting(true);\n\n try {\n // 3. Snapshot visible step IDs before writing (to detect tree changes)\n const oldVisibleIds = tree.steps.map((s) => s.definition.id).join(\",\");\n\n // 4. Write validated data into the store\n store.getState().setStepData(currentStep.definition.id, values);\n\n // 4b. If this step was previously skipped, un-skip it (user filled it properly)\n if (skippedSteps.includes(currentStep.definition.id)) {\n store.getState().unskipStep(currentStep.definition.id);\n }\n\n // 5. Re-resolve tree with updated data — step visibility may have changed\n const allData = store.getState().data;\n const updatedSkipped = store.getState().skippedSteps;\n const updatedTree = resolveTree(schema, allData, externalVars, externalEnums, updatedSkipped);\n const newVisibleIds = updatedTree.steps.map((s) => s.definition.id).join(\",\");\n\n // 6. If the visible tree changed, truncate stale forward history\n // (e.g. user went back, changed a dep value, tree changed → old path is invalid)\n if (oldVisibleIds !== newVisibleIds) {\n store.getState().truncateHistoryAt(currentStep.definition.id);\n }\n\n // 7. onDataChange callback\n onDataChange?.(allData);\n\n // 8. onStepComplete callback (may be async, e.g. backend-step mode)\n if (onStepComplete) {\n await onStepComplete(currentStep.definition.id, values);\n }\n\n // 9. Navigate using the UPDATED tree so we follow the new step order\n const nextStep = getNextStep(updatedTree.steps, currentStep.definition.id);\n if (nextStep) {\n router.push(nextStep.definition.url);\n } else {\n store.getState().setCompleted(true);\n await onComplete?.(allData);\n }\n } finally {\n store.getState().setIsSubmitting(false);\n }\n }, [\n currentStep,\n form,\n store,\n schema,\n tree.steps,\n externalVars,\n externalEnums,\n skippedSteps,\n onDataChange,\n onStepComplete,\n onComplete,\n router,\n ]);\n\n return {\n currentStep,\n progress,\n isFirstStep,\n isLastStep,\n form,\n fields: visibleFields,\n handleSubmit,\n goBack,\n skipStep,\n isSubmitting,\n canSkip,\n errors: form.formState.errors,\n };\n}\n"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { WaypointSchema, ResolvedStep, ResolvedField, WaypointRuntimeStore } from '@waypointjs/core';
|
|
2
|
+
import { WaypointSchema, CustomTypeDefinition, ExternalEnum, ResolvedStep, ResolvedField, WaypointRuntimeStore } from '@waypointjs/core';
|
|
3
3
|
import { UseFormReturn, FieldValues, FieldErrors } from 'react-hook-form';
|
|
4
4
|
import * as react from 'react';
|
|
5
5
|
import { StoreApi } from 'zustand';
|
|
@@ -16,6 +16,12 @@ interface WaypointRunnerProps {
|
|
|
16
16
|
onStepComplete?: (stepId: string, data: Record<string, unknown>) => void | Promise<void>;
|
|
17
17
|
/** Called whenever any field value changes */
|
|
18
18
|
onDataChange?: (data: Record<string, Record<string, unknown>>) => void;
|
|
19
|
+
/** Called when the user skips a step */
|
|
20
|
+
onStepSkipped?: (stepId: string) => void;
|
|
21
|
+
/** App-provided custom field types — exposed via context for custom field rendering */
|
|
22
|
+
customFieldTypes?: CustomTypeDefinition[];
|
|
23
|
+
/** App-provided external enum lists — resolved into ResolvedField.resolvedOptions */
|
|
24
|
+
externalEnums?: ExternalEnum[];
|
|
19
25
|
children: React.ReactNode;
|
|
20
26
|
}
|
|
21
27
|
/**
|
|
@@ -46,7 +52,7 @@ interface WaypointRunnerProps {
|
|
|
46
52
|
* {children}
|
|
47
53
|
* </WaypointRunner>
|
|
48
54
|
*/
|
|
49
|
-
declare function WaypointRunner({ schema, externalVars, defaultValues, fetchData, onComplete, onStepComplete, onDataChange, children, }: WaypointRunnerProps): react_jsx_runtime.JSX.Element;
|
|
55
|
+
declare function WaypointRunner({ schema, externalVars, defaultValues, fetchData, onComplete, onStepComplete, onDataChange, onStepSkipped, customFieldTypes, externalEnums, children, }: WaypointRunnerProps): react_jsx_runtime.JSX.Element;
|
|
50
56
|
|
|
51
57
|
interface WaypointStepReturn {
|
|
52
58
|
currentStep: ResolvedStep | undefined;
|
|
@@ -59,7 +65,11 @@ interface WaypointStepReturn {
|
|
|
59
65
|
/** Validate → persist → onStepComplete → navigate next (or onComplete on last step) */
|
|
60
66
|
handleSubmit: () => Promise<void>;
|
|
61
67
|
goBack: () => void;
|
|
68
|
+
/** Skip this step without validation — only available when step.skippable is true */
|
|
69
|
+
skipStep: () => void;
|
|
62
70
|
isSubmitting: boolean;
|
|
71
|
+
/** Whether the current step can be skipped */
|
|
72
|
+
canSkip: boolean;
|
|
63
73
|
errors: FieldErrors;
|
|
64
74
|
}
|
|
65
75
|
/**
|
|
@@ -81,6 +91,12 @@ interface WaypointRuntimeContextValue {
|
|
|
81
91
|
onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;
|
|
82
92
|
onStepComplete?: (stepId: string, data: Record<string, unknown>) => void | Promise<void>;
|
|
83
93
|
onDataChange?: (data: Record<string, Record<string, unknown>>) => void;
|
|
94
|
+
/** Called when the user skips a step */
|
|
95
|
+
onStepSkipped?: (stepId: string) => void;
|
|
96
|
+
/** App-provided custom field types — available for rendering custom fields */
|
|
97
|
+
customFieldTypes?: CustomTypeDefinition[];
|
|
98
|
+
/** App-provided external enum lists — resolved into ResolvedField.resolvedOptions by the tree resolver */
|
|
99
|
+
externalEnums?: ExternalEnum[];
|
|
84
100
|
}
|
|
85
101
|
declare const WaypointRuntimeContext: react.Context<WaypointRuntimeContextValue | null>;
|
|
86
102
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
-
import { WaypointSchema, ResolvedStep, ResolvedField, WaypointRuntimeStore } from '@waypointjs/core';
|
|
2
|
+
import { WaypointSchema, CustomTypeDefinition, ExternalEnum, ResolvedStep, ResolvedField, WaypointRuntimeStore } from '@waypointjs/core';
|
|
3
3
|
import { UseFormReturn, FieldValues, FieldErrors } from 'react-hook-form';
|
|
4
4
|
import * as react from 'react';
|
|
5
5
|
import { StoreApi } from 'zustand';
|
|
@@ -16,6 +16,12 @@ interface WaypointRunnerProps {
|
|
|
16
16
|
onStepComplete?: (stepId: string, data: Record<string, unknown>) => void | Promise<void>;
|
|
17
17
|
/** Called whenever any field value changes */
|
|
18
18
|
onDataChange?: (data: Record<string, Record<string, unknown>>) => void;
|
|
19
|
+
/** Called when the user skips a step */
|
|
20
|
+
onStepSkipped?: (stepId: string) => void;
|
|
21
|
+
/** App-provided custom field types — exposed via context for custom field rendering */
|
|
22
|
+
customFieldTypes?: CustomTypeDefinition[];
|
|
23
|
+
/** App-provided external enum lists — resolved into ResolvedField.resolvedOptions */
|
|
24
|
+
externalEnums?: ExternalEnum[];
|
|
19
25
|
children: React.ReactNode;
|
|
20
26
|
}
|
|
21
27
|
/**
|
|
@@ -46,7 +52,7 @@ interface WaypointRunnerProps {
|
|
|
46
52
|
* {children}
|
|
47
53
|
* </WaypointRunner>
|
|
48
54
|
*/
|
|
49
|
-
declare function WaypointRunner({ schema, externalVars, defaultValues, fetchData, onComplete, onStepComplete, onDataChange, children, }: WaypointRunnerProps): react_jsx_runtime.JSX.Element;
|
|
55
|
+
declare function WaypointRunner({ schema, externalVars, defaultValues, fetchData, onComplete, onStepComplete, onDataChange, onStepSkipped, customFieldTypes, externalEnums, children, }: WaypointRunnerProps): react_jsx_runtime.JSX.Element;
|
|
50
56
|
|
|
51
57
|
interface WaypointStepReturn {
|
|
52
58
|
currentStep: ResolvedStep | undefined;
|
|
@@ -59,7 +65,11 @@ interface WaypointStepReturn {
|
|
|
59
65
|
/** Validate → persist → onStepComplete → navigate next (or onComplete on last step) */
|
|
60
66
|
handleSubmit: () => Promise<void>;
|
|
61
67
|
goBack: () => void;
|
|
68
|
+
/** Skip this step without validation — only available when step.skippable is true */
|
|
69
|
+
skipStep: () => void;
|
|
62
70
|
isSubmitting: boolean;
|
|
71
|
+
/** Whether the current step can be skipped */
|
|
72
|
+
canSkip: boolean;
|
|
63
73
|
errors: FieldErrors;
|
|
64
74
|
}
|
|
65
75
|
/**
|
|
@@ -81,6 +91,12 @@ interface WaypointRuntimeContextValue {
|
|
|
81
91
|
onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;
|
|
82
92
|
onStepComplete?: (stepId: string, data: Record<string, unknown>) => void | Promise<void>;
|
|
83
93
|
onDataChange?: (data: Record<string, Record<string, unknown>>) => void;
|
|
94
|
+
/** Called when the user skips a step */
|
|
95
|
+
onStepSkipped?: (stepId: string) => void;
|
|
96
|
+
/** App-provided custom field types — available for rendering custom fields */
|
|
97
|
+
customFieldTypes?: CustomTypeDefinition[];
|
|
98
|
+
/** App-provided external enum lists — resolved into ResolvedField.resolvedOptions by the tree resolver */
|
|
99
|
+
externalEnums?: ExternalEnum[];
|
|
84
100
|
}
|
|
85
101
|
declare const WaypointRuntimeContext: react.Context<WaypointRuntimeContextValue | null>;
|
|
86
102
|
/**
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,9 @@ function WaypointRunner({
|
|
|
24
24
|
onComplete,
|
|
25
25
|
onStepComplete,
|
|
26
26
|
onDataChange,
|
|
27
|
+
onStepSkipped,
|
|
28
|
+
customFieldTypes,
|
|
29
|
+
externalEnums,
|
|
27
30
|
children
|
|
28
31
|
}) {
|
|
29
32
|
const router = useRouter();
|
|
@@ -47,7 +50,7 @@ function WaypointRunner({
|
|
|
47
50
|
store.getState().resume(schema, externalVars);
|
|
48
51
|
const state2 = store.getState();
|
|
49
52
|
if (state2.currentStepId) {
|
|
50
|
-
const tree2 = resolveTree(schema, state2.data, state2.externalVars);
|
|
53
|
+
const tree2 = resolveTree(schema, state2.data, state2.externalVars, externalEnums);
|
|
51
54
|
const step = tree2.steps.find(
|
|
52
55
|
(s) => s.definition.id === state2.currentStepId
|
|
53
56
|
);
|
|
@@ -71,7 +74,7 @@ function WaypointRunner({
|
|
|
71
74
|
if (cancelled) return;
|
|
72
75
|
store.getState().init(schema, { data, externalVars });
|
|
73
76
|
const state = store.getState();
|
|
74
|
-
const tree = resolveTree(schema, state.data, state.externalVars);
|
|
77
|
+
const tree = resolveTree(schema, state.data, state.externalVars, externalEnums);
|
|
75
78
|
const lastValid = findLastValidStep(tree.steps, state.data, state.externalVars);
|
|
76
79
|
if (lastValid && lastValid.definition.url !== pathname) {
|
|
77
80
|
router.push(lastValid.definition.url);
|
|
@@ -93,10 +96,13 @@ function WaypointRunner({
|
|
|
93
96
|
store,
|
|
94
97
|
onComplete,
|
|
95
98
|
onStepComplete,
|
|
96
|
-
onDataChange
|
|
99
|
+
onDataChange,
|
|
100
|
+
onStepSkipped,
|
|
101
|
+
customFieldTypes,
|
|
102
|
+
externalEnums
|
|
97
103
|
}),
|
|
98
104
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
99
|
-
[schema, store]
|
|
105
|
+
[schema, store, externalEnums]
|
|
100
106
|
);
|
|
101
107
|
if (missingVars.length > 0) {
|
|
102
108
|
return /* @__PURE__ */ jsxs(
|
|
@@ -125,21 +131,22 @@ function WaypointRunner({
|
|
|
125
131
|
return /* @__PURE__ */ jsx(WaypointRuntimeContext.Provider, { value: contextValue, children });
|
|
126
132
|
}
|
|
127
133
|
function useWaypointStep() {
|
|
128
|
-
const { schema, store, onComplete, onStepComplete, onDataChange } = useWaypointRuntimeContext();
|
|
134
|
+
const { schema, store, onComplete, onStepComplete, onDataChange, onStepSkipped, externalEnums } = useWaypointRuntimeContext();
|
|
129
135
|
const router = useRouter();
|
|
130
136
|
const pathname = usePathname();
|
|
131
|
-
const { data, externalVars, currentStepId, isSubmitting } = useStore(
|
|
137
|
+
const { data, externalVars, currentStepId, skippedSteps, isSubmitting } = useStore(
|
|
132
138
|
store,
|
|
133
139
|
(s) => ({
|
|
134
140
|
data: s.data,
|
|
135
141
|
externalVars: s.externalVars,
|
|
136
142
|
currentStepId: s.currentStepId,
|
|
143
|
+
skippedSteps: s.skippedSteps,
|
|
137
144
|
isSubmitting: s.isSubmitting
|
|
138
145
|
})
|
|
139
146
|
);
|
|
140
147
|
const tree = useMemo(
|
|
141
|
-
() => resolveTree(schema, data, externalVars),
|
|
142
|
-
[schema, data, externalVars]
|
|
148
|
+
() => resolveTree(schema, data, externalVars, externalEnums, skippedSteps),
|
|
149
|
+
[schema, data, externalVars, externalEnums, skippedSteps]
|
|
143
150
|
);
|
|
144
151
|
const currentStep = useMemo(() => {
|
|
145
152
|
return tree.steps.find((s) => {
|
|
@@ -156,9 +163,25 @@ function useWaypointStep() {
|
|
|
156
163
|
() => currentStep?.fields.filter((f) => f.visible) ?? [],
|
|
157
164
|
[currentStep]
|
|
158
165
|
);
|
|
159
|
-
const zodSchema = useMemo(() => buildZodSchema(visibleFields), [visibleFields]);
|
|
166
|
+
const zodSchema = useMemo(() => buildZodSchema(visibleFields, externalEnums, data), [visibleFields, externalEnums, data]);
|
|
160
167
|
const defaultValues = useMemo(
|
|
161
|
-
() =>
|
|
168
|
+
() => {
|
|
169
|
+
if (!currentStep) return {};
|
|
170
|
+
const stored = data[currentStep.definition.id] ?? {};
|
|
171
|
+
const merged = { ...stored };
|
|
172
|
+
for (const field of currentStep.fields) {
|
|
173
|
+
const fid = field.definition.id;
|
|
174
|
+
if (merged[fid] === void 0 || merged[fid] === null || merged[fid] === "") {
|
|
175
|
+
const dynDefault = field.resolvedDefaultValue;
|
|
176
|
+
const staticDefault = field.definition.defaultValue;
|
|
177
|
+
const resolved = dynDefault ?? staticDefault;
|
|
178
|
+
if (resolved !== void 0) {
|
|
179
|
+
merged[fid] = resolved;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return merged;
|
|
184
|
+
},
|
|
162
185
|
// Only recompute when the step changes (not on every data write)
|
|
163
186
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
164
187
|
[currentStep?.definition.id]
|
|
@@ -176,6 +199,7 @@ function useWaypointStep() {
|
|
|
176
199
|
const isFirstStep = stepIndex === 0;
|
|
177
200
|
const isLastStep = stepIndex === tree.steps.length - 1;
|
|
178
201
|
const progress = currentStep ? calculateProgress(tree.steps, currentStep.definition.id) : 0;
|
|
202
|
+
const canSkip = !!currentStep?.definition.skippable;
|
|
179
203
|
const goBack = useCallback(() => {
|
|
180
204
|
if (!currentStep) return;
|
|
181
205
|
const prev = getPreviousStep(tree.steps, currentStep.definition.id);
|
|
@@ -183,6 +207,23 @@ function useWaypointStep() {
|
|
|
183
207
|
router.push(prev.definition.url);
|
|
184
208
|
}
|
|
185
209
|
}, [currentStep, tree.steps, router]);
|
|
210
|
+
const skipStep = useCallback(() => {
|
|
211
|
+
if (!currentStep || !currentStep.definition.skippable) return;
|
|
212
|
+
const stepId = currentStep.definition.id;
|
|
213
|
+
store.getState().skipStep(stepId);
|
|
214
|
+
const updatedTree = resolveTree(schema, store.getState().data, externalVars, externalEnums, [
|
|
215
|
+
...skippedSteps,
|
|
216
|
+
stepId
|
|
217
|
+
]);
|
|
218
|
+
const nextStep = getNextStep(updatedTree.steps, stepId);
|
|
219
|
+
onStepSkipped?.(stepId);
|
|
220
|
+
if (nextStep) {
|
|
221
|
+
router.push(nextStep.definition.url);
|
|
222
|
+
} else {
|
|
223
|
+
store.getState().setCompleted(true);
|
|
224
|
+
onComplete?.(store.getState().data);
|
|
225
|
+
}
|
|
226
|
+
}, [currentStep, store, schema, externalVars, externalEnums, skippedSteps, onStepSkipped, onComplete, router]);
|
|
186
227
|
const handleSubmit = useCallback(async () => {
|
|
187
228
|
if (!currentStep) return;
|
|
188
229
|
const isValid = await form.trigger();
|
|
@@ -192,8 +233,12 @@ function useWaypointStep() {
|
|
|
192
233
|
try {
|
|
193
234
|
const oldVisibleIds = tree.steps.map((s) => s.definition.id).join(",");
|
|
194
235
|
store.getState().setStepData(currentStep.definition.id, values);
|
|
236
|
+
if (skippedSteps.includes(currentStep.definition.id)) {
|
|
237
|
+
store.getState().unskipStep(currentStep.definition.id);
|
|
238
|
+
}
|
|
195
239
|
const allData = store.getState().data;
|
|
196
|
-
const
|
|
240
|
+
const updatedSkipped = store.getState().skippedSteps;
|
|
241
|
+
const updatedTree = resolveTree(schema, allData, externalVars, externalEnums, updatedSkipped);
|
|
197
242
|
const newVisibleIds = updatedTree.steps.map((s) => s.definition.id).join(",");
|
|
198
243
|
if (oldVisibleIds !== newVisibleIds) {
|
|
199
244
|
store.getState().truncateHistoryAt(currentStep.definition.id);
|
|
@@ -219,6 +264,8 @@ function useWaypointStep() {
|
|
|
219
264
|
schema,
|
|
220
265
|
tree.steps,
|
|
221
266
|
externalVars,
|
|
267
|
+
externalEnums,
|
|
268
|
+
skippedSteps,
|
|
222
269
|
onDataChange,
|
|
223
270
|
onStepComplete,
|
|
224
271
|
onComplete,
|
|
@@ -233,7 +280,9 @@ function useWaypointStep() {
|
|
|
233
280
|
fields: visibleFields,
|
|
234
281
|
handleSubmit,
|
|
235
282
|
goBack,
|
|
283
|
+
skipStep,
|
|
236
284
|
isSubmitting,
|
|
285
|
+
canSkip,
|
|
237
286
|
errors: form.formState.errors
|
|
238
287
|
};
|
|
239
288
|
}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/context.ts","../src/WaypointRunner.tsx","../src/useWaypointStep.ts"],"names":["state","tree","useRouter","usePathname","useStore","useMemo","resolveTree","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { StoreApi } from \"zustand\";\n\nimport type { WaypointSchema } from \"@waypointjs/core\";\nimport type { WaypointRuntimeStore } from \"@waypointjs/core\";\n\n// ---------------------------------------------------------------------------\n// Context value\n// ---------------------------------------------------------------------------\n\nexport interface WaypointRuntimeContextValue {\n schema: WaypointSchema;\n store: StoreApi<WaypointRuntimeStore>;\n onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;\n onStepComplete?: (\n stepId: string,\n data: Record<string, unknown>\n ) => void | Promise<void>;\n onDataChange?: (data: Record<string, Record<string, unknown>>) => void;\n}\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nexport const WaypointRuntimeContext =\n createContext<WaypointRuntimeContextValue | null>(null);\n\n/**\n * Returns the current WaypointRunner context.\n * Must be used inside a `<WaypointRunner>` component.\n */\nexport function useWaypointRuntimeContext(): WaypointRuntimeContextValue {\n const ctx = useContext(WaypointRuntimeContext);\n if (!ctx) {\n throw new Error(\n \"useWaypointRuntimeContext must be called inside a <WaypointRunner> component.\"\n );\n }\n return ctx;\n}\n","\"use client\";\n\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { useStore } from \"zustand\";\nimport { useRouter, usePathname } from \"next/navigation\";\n\nimport {\n createRuntimeStore,\n hasPersistedState,\n resolveTree,\n findLastValidStep,\n} from \"@waypointjs/core\";\nimport type { WaypointSchema, WaypointRuntimeStore } from \"@waypointjs/core\";\n\nimport { WaypointRuntimeContext } from \"./context\";\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface WaypointRunnerProps {\n schema: WaypointSchema;\n externalVars?: Record<string, unknown>;\n defaultValues?: Record<string, Record<string, unknown>>;\n /** Async function to load previously-saved data (for deep-link resume) */\n fetchData?: () => Promise<Record<string, Record<string, unknown>>>;\n /** Called when the user completes the last step */\n onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;\n /** Called after each step is validated and submitted */\n onStepComplete?: (\n stepId: string,\n data: Record<string, unknown>\n ) => void | Promise<void>;\n /** Called whenever any field value changes */\n onDataChange?: (data: Record<string, Record<string, unknown>>) => void;\n children: React.ReactNode;\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n/**\n * Context provider that initialises the runtime store and wires up callbacks.\n *\n * ### Multi-journey support\n * Each `<WaypointRunner>` creates its own **isolated** Zustand store instance,\n * so multiple runners with different schemas can coexist in the same app\n * without any state interference:\n *\n * ```tsx\n * // Both journeys live side-by-side — each has its own store\n * <WaypointRunner schema={projectSchema}>…</WaypointRunner>\n * <WaypointRunner schema={depositSchema}>…</WaypointRunner>\n * ```\n *\n * ### Pause & Resume\n * When `schema.persistenceMode === \"zustand\"`, each journey's state\n * (`data`, `currentStepId`, `history`) is saved to localStorage under a\n * per-schema key (`waypoint-runtime-<schemaId>`).\n *\n * On remount, `WaypointRunner` detects the saved state and calls `resume()`\n * instead of `init()`, so the user lands back exactly where they left off —\n * navigation state and form data intact.\n *\n * @example\n * <WaypointRunner schema={mySchema} onComplete={handleComplete}>\n * {children}\n * </WaypointRunner>\n */\nexport function WaypointRunner({\n schema,\n externalVars = {},\n defaultValues = {},\n fetchData,\n onComplete,\n onStepComplete,\n onDataChange,\n children,\n}: WaypointRunnerProps) {\n const router = useRouter();\n const pathname = usePathname();\n\n // Create store once per runner instance.\n // The persist middleware (when active) hydrates synchronously from localStorage,\n // so by the time the first useEffect fires, persisted state is already in the store.\n const storeRef = useRef<ReturnType<typeof createRuntimeStore> | null>(null);\n if (storeRef.current === null) {\n storeRef.current = createRuntimeStore({\n persistenceMode: schema.persistenceMode,\n schemaId: schema.id,\n });\n }\n const store = storeRef.current;\n\n // Subscribe to missing blocking vars to show error UI reactively\n const missingVars = useStore(store, (s: WaypointRuntimeStore) => {\n if (!s.schema) return [];\n return resolveTree(s.schema, s.data, s.externalVars).missingExternalVars;\n });\n\n // ---------------------------------------------------------------------------\n // Init / Resume on mount\n // ---------------------------------------------------------------------------\n useEffect(() => {\n let cancelled = false;\n\n async function initialize() {\n // ── Resume path ────────────────────────────────────────────────────────\n // When persistenceMode is \"zustand\", the persist middleware has already\n // synchronously hydrated the store from localStorage by the time this\n // effect runs. If the saved schemaId matches, we resume instead of\n // resetting so the user picks up exactly where they left off.\n if (\n schema.persistenceMode === \"zustand\" &&\n hasPersistedState(store, schema.id)\n ) {\n // Keep data + currentStepId + history — just update schema & externalVars\n store.getState().resume(schema, externalVars);\n\n // Navigate to the persisted step\n const state = store.getState();\n if (state.currentStepId) {\n const tree = resolveTree(schema, state.data, state.externalVars);\n const step = tree.steps.find(\n (s) => s.definition.id === state.currentStepId\n );\n if (step && step.definition.url !== pathname) {\n router.push(step.definition.url);\n }\n }\n return;\n }\n\n // ── Fresh-start path ───────────────────────────────────────────────────\n let data = { ...defaultValues };\n\n if (fetchData) {\n try {\n const fetched = await fetchData();\n if (!cancelled) {\n data = { ...data, ...fetched };\n }\n } catch (err) {\n console.error(\"Waypoint: fetchData failed\", err);\n }\n }\n\n if (cancelled) return;\n\n store.getState().init(schema, { data, externalVars });\n\n // Deep-link resume: if the user already has data, redirect to the last\n // valid step instead of forcing them back to step 1.\n const state = store.getState();\n const tree = resolveTree(schema, state.data, state.externalVars);\n const lastValid = findLastValidStep(tree.steps, state.data, state.externalVars);\n\n if (lastValid && lastValid.definition.url !== pathname) {\n router.push(lastValid.definition.url);\n }\n }\n\n initialize();\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Sync externalVars into the store when the prop changes after mount\n useEffect(() => {\n for (const [key, value] of Object.entries(externalVars)) {\n store.getState().setExternalVar(key, value);\n }\n }, [externalVars, store]);\n\n const contextValue = useMemo(\n () => ({\n schema,\n store,\n onComplete,\n onStepComplete,\n onDataChange,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [schema, store]\n );\n\n // Show a blocking error if required external variables are missing after init\n if (missingVars.length > 0) {\n return (\n <div\n role=\"alert\"\n style={{\n color: \"#b91c1c\",\n background: \"#fef2f2\",\n border: \"1px solid #fca5a5\",\n borderRadius: 8,\n padding: \"1rem 1.25rem\",\n fontFamily: \"sans-serif\",\n }}\n >\n <strong>Waypoint Runtime Error</strong>\n <p style={{ margin: \"0.5rem 0 0\" }}>\n Missing required external variables:{\" \"}\n <code>{missingVars.join(\", \")}</code>\n </p>\n </div>\n );\n }\n\n return (\n <WaypointRuntimeContext.Provider value={contextValue}>\n {children}\n </WaypointRuntimeContext.Provider>\n );\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useMemo } from \"react\";\nimport { useRouter, usePathname } from \"next/navigation\";\nimport { useStore } from \"zustand\";\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport type { FieldErrors, FieldValues, UseFormReturn } from \"react-hook-form\";\n\nimport {\n resolveTree,\n buildZodSchema,\n calculateProgress,\n getNextStep,\n getPreviousStep,\n findStepIndex,\n} from \"@waypointjs/core\";\nimport type { ResolvedField, ResolvedStep, WaypointRuntimeStore } from \"@waypointjs/core\";\n\nimport { useWaypointRuntimeContext } from \"./context\";\n\n// ---------------------------------------------------------------------------\n// Return type\n// ---------------------------------------------------------------------------\n\nexport interface WaypointStepReturn {\n // Step context\n currentStep: ResolvedStep | undefined;\n progress: number;\n isFirstStep: boolean;\n isLastStep: boolean;\n\n // React Hook Form\n form: UseFormReturn<FieldValues>;\n /** Visible fields for the current step */\n fields: ResolvedField[];\n\n // Actions\n /** Validate → persist → onStepComplete → navigate next (or onComplete on last step) */\n handleSubmit: () => Promise<void>;\n goBack: () => void;\n\n // State\n isSubmitting: boolean;\n errors: FieldErrors;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Per-page hook for multi-step forms powered by WaypointRunner.\n *\n * Derives the current step from the URL, provides react-hook-form wired to\n * the step's Zod schema, and handles navigation automatically.\n *\n * Must be used inside a `<WaypointRunner>` component.\n *\n * @example\n * const { form, fields, handleSubmit, progress } = useWaypointStep();\n */\nexport function useWaypointStep(): WaypointStepReturn {\n const { schema, store, onComplete, onStepComplete, onDataChange } =\n useWaypointRuntimeContext();\n const router = useRouter();\n const pathname = usePathname();\n\n // Subscribe to store state\n const { data, externalVars, currentStepId, isSubmitting } = useStore(\n store,\n (s: WaypointRuntimeStore) => ({\n data: s.data,\n externalVars: s.externalVars,\n currentStepId: s.currentStepId,\n isSubmitting: s.isSubmitting,\n })\n );\n\n // Resolve the full tree\n const tree = useMemo(\n () => resolveTree(schema, data, externalVars),\n [schema, data, externalVars]\n );\n\n // Find the step matching the current pathname\n const currentStep = useMemo(() => {\n return tree.steps.find((s) => {\n const stepUrl = s.definition.url;\n // Exact match, or pathname ends with step URL (handles leading slash variants)\n return pathname === stepUrl || pathname.endsWith(stepUrl);\n });\n }, [tree.steps, pathname]);\n\n // Sync currentStepId into the store whenever the page changes\n useEffect(() => {\n if (currentStep && currentStep.definition.id !== currentStepId) {\n store.getState().setCurrentStep(currentStep.definition.id);\n }\n }, [currentStep, currentStepId, store]);\n\n // Visible fields for the current step\n const visibleFields = useMemo(\n () => currentStep?.fields.filter((f) => f.visible) ?? [],\n [currentStep]\n );\n\n // Build the Zod schema from visible fields\n const zodSchema = useMemo(() => buildZodSchema(visibleFields), [visibleFields]);\n\n // Existing step data used as default values\n const defaultValues = useMemo(\n () => (currentStep ? (data[currentStep.definition.id] ?? {}) : {}),\n // Only recompute when the step changes (not on every data write)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [currentStep?.definition.id]\n );\n\n // React Hook Form instance\n const form = useForm<FieldValues>({\n resolver: zodResolver(zodSchema),\n defaultValues,\n });\n\n // Reset form defaults when step changes\n useEffect(() => {\n if (currentStep) {\n form.reset(data[currentStep.definition.id] ?? {});\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [currentStep?.definition.id]);\n\n // Navigation position\n const stepIndex = currentStep\n ? findStepIndex(tree.steps, currentStep.definition.id)\n : -1;\n const isFirstStep = stepIndex === 0;\n const isLastStep = stepIndex === tree.steps.length - 1;\n const progress = currentStep\n ? calculateProgress(tree.steps, currentStep.definition.id)\n : 0;\n\n // goBack\n const goBack = useCallback(() => {\n if (!currentStep) return;\n const prev = getPreviousStep(tree.steps, currentStep.definition.id);\n if (prev) {\n router.push(prev.definition.url);\n }\n }, [currentStep, tree.steps, router]);\n\n // handleSubmit\n const handleSubmit = useCallback(async () => {\n if (!currentStep) return;\n\n // 1. Validate via RHF + Zod\n const isValid = await form.trigger();\n if (!isValid) return;\n\n const values = form.getValues();\n\n // 2. Persist data + update submitting state\n store.getState().setIsSubmitting(true);\n\n try {\n // 3. Snapshot visible step IDs before writing (to detect tree changes)\n const oldVisibleIds = tree.steps.map((s) => s.definition.id).join(\",\");\n\n // 4. Write validated data into the store\n store.getState().setStepData(currentStep.definition.id, values);\n\n // 5. Re-resolve tree with updated data — step visibility may have changed\n const allData = store.getState().data;\n const updatedTree = resolveTree(schema, allData, externalVars);\n const newVisibleIds = updatedTree.steps.map((s) => s.definition.id).join(\",\");\n\n // 6. If the visible tree changed, truncate stale forward history\n // (e.g. user went back, changed a dep value, tree changed → old path is invalid)\n if (oldVisibleIds !== newVisibleIds) {\n store.getState().truncateHistoryAt(currentStep.definition.id);\n }\n\n // 7. onDataChange callback\n onDataChange?.(allData);\n\n // 8. onStepComplete callback (may be async, e.g. backend-step mode)\n if (onStepComplete) {\n await onStepComplete(currentStep.definition.id, values);\n }\n\n // 9. Navigate using the UPDATED tree so we follow the new step order\n const nextStep = getNextStep(updatedTree.steps, currentStep.definition.id);\n if (nextStep) {\n router.push(nextStep.definition.url);\n } else {\n store.getState().setCompleted(true);\n await onComplete?.(allData);\n }\n } finally {\n store.getState().setIsSubmitting(false);\n }\n }, [\n currentStep,\n form,\n store,\n schema,\n tree.steps,\n externalVars,\n onDataChange,\n onStepComplete,\n onComplete,\n router,\n ]);\n\n return {\n currentStep,\n progress,\n isFirstStep,\n isLastStep,\n form,\n fields: visibleFields,\n handleSubmit,\n goBack,\n isSubmitting,\n errors: form.formState.errors,\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/context.ts","../src/WaypointRunner.tsx","../src/useWaypointStep.ts"],"names":["state","tree","useRouter","usePathname","useStore","useMemo","resolveTree","useEffect"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, useContext } from \"react\";\nimport type { StoreApi } from \"zustand\";\n\nimport type { WaypointSchema, CustomTypeDefinition, ExternalEnum } from \"@waypointjs/core\";\nimport type { WaypointRuntimeStore } from \"@waypointjs/core\";\n\n// ---------------------------------------------------------------------------\n// Context value\n// ---------------------------------------------------------------------------\n\nexport interface WaypointRuntimeContextValue {\n schema: WaypointSchema;\n store: StoreApi<WaypointRuntimeStore>;\n onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;\n onStepComplete?: (\n stepId: string,\n data: Record<string, unknown>\n ) => void | Promise<void>;\n onDataChange?: (data: Record<string, Record<string, unknown>>) => void;\n /** Called when the user skips a step */\n onStepSkipped?: (stepId: string) => void;\n /** App-provided custom field types — available for rendering custom fields */\n customFieldTypes?: CustomTypeDefinition[];\n /** App-provided external enum lists — resolved into ResolvedField.resolvedOptions by the tree resolver */\n externalEnums?: ExternalEnum[];\n}\n\n// ---------------------------------------------------------------------------\n// Context\n// ---------------------------------------------------------------------------\n\nexport const WaypointRuntimeContext =\n createContext<WaypointRuntimeContextValue | null>(null);\n\n/**\n * Returns the current WaypointRunner context.\n * Must be used inside a `<WaypointRunner>` component.\n */\nexport function useWaypointRuntimeContext(): WaypointRuntimeContextValue {\n const ctx = useContext(WaypointRuntimeContext);\n if (!ctx) {\n throw new Error(\n \"useWaypointRuntimeContext must be called inside a <WaypointRunner> component.\"\n );\n }\n return ctx;\n}\n","\"use client\";\n\nimport { useEffect, useMemo, useRef } from \"react\";\nimport { useStore } from \"zustand\";\nimport { useRouter, usePathname } from \"next/navigation\";\n\nimport {\n createRuntimeStore,\n hasPersistedState,\n resolveTree,\n findLastValidStep,\n} from \"@waypointjs/core\";\nimport type { WaypointSchema, WaypointRuntimeStore, CustomTypeDefinition, ExternalEnum } from \"@waypointjs/core\";\n\nimport { WaypointRuntimeContext } from \"./context\";\n\n// ---------------------------------------------------------------------------\n// Props\n// ---------------------------------------------------------------------------\n\nexport interface WaypointRunnerProps {\n schema: WaypointSchema;\n externalVars?: Record<string, unknown>;\n defaultValues?: Record<string, Record<string, unknown>>;\n /** Async function to load previously-saved data (for deep-link resume) */\n fetchData?: () => Promise<Record<string, Record<string, unknown>>>;\n /** Called when the user completes the last step */\n onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;\n /** Called after each step is validated and submitted */\n onStepComplete?: (\n stepId: string,\n data: Record<string, unknown>\n ) => void | Promise<void>;\n /** Called whenever any field value changes */\n onDataChange?: (data: Record<string, Record<string, unknown>>) => void;\n /** Called when the user skips a step */\n onStepSkipped?: (stepId: string) => void;\n /** App-provided custom field types — exposed via context for custom field rendering */\n customFieldTypes?: CustomTypeDefinition[];\n /** App-provided external enum lists — resolved into ResolvedField.resolvedOptions */\n externalEnums?: ExternalEnum[];\n children: React.ReactNode;\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n/**\n * Context provider that initialises the runtime store and wires up callbacks.\n *\n * ### Multi-journey support\n * Each `<WaypointRunner>` creates its own **isolated** Zustand store instance,\n * so multiple runners with different schemas can coexist in the same app\n * without any state interference:\n *\n * ```tsx\n * // Both journeys live side-by-side — each has its own store\n * <WaypointRunner schema={projectSchema}>…</WaypointRunner>\n * <WaypointRunner schema={depositSchema}>…</WaypointRunner>\n * ```\n *\n * ### Pause & Resume\n * When `schema.persistenceMode === \"zustand\"`, each journey's state\n * (`data`, `currentStepId`, `history`) is saved to localStorage under a\n * per-schema key (`waypoint-runtime-<schemaId>`).\n *\n * On remount, `WaypointRunner` detects the saved state and calls `resume()`\n * instead of `init()`, so the user lands back exactly where they left off —\n * navigation state and form data intact.\n *\n * @example\n * <WaypointRunner schema={mySchema} onComplete={handleComplete}>\n * {children}\n * </WaypointRunner>\n */\nexport function WaypointRunner({\n schema,\n externalVars = {},\n defaultValues = {},\n fetchData,\n onComplete,\n onStepComplete,\n onDataChange,\n onStepSkipped,\n customFieldTypes,\n externalEnums,\n children,\n}: WaypointRunnerProps) {\n const router = useRouter();\n const pathname = usePathname();\n\n // Create store once per runner instance.\n // The persist middleware (when active) hydrates synchronously from localStorage,\n // so by the time the first useEffect fires, persisted state is already in the store.\n const storeRef = useRef<ReturnType<typeof createRuntimeStore> | null>(null);\n if (storeRef.current === null) {\n storeRef.current = createRuntimeStore({\n persistenceMode: schema.persistenceMode,\n schemaId: schema.id,\n });\n }\n const store = storeRef.current;\n\n // Subscribe to missing blocking vars to show error UI reactively\n const missingVars = useStore(store, (s: WaypointRuntimeStore) => {\n if (!s.schema) return [];\n return resolveTree(s.schema, s.data, s.externalVars).missingExternalVars;\n });\n\n // ---------------------------------------------------------------------------\n // Init / Resume on mount\n // ---------------------------------------------------------------------------\n useEffect(() => {\n let cancelled = false;\n\n async function initialize() {\n // ── Resume path ────────────────────────────────────────────────────────\n // When persistenceMode is \"zustand\", the persist middleware has already\n // synchronously hydrated the store from localStorage by the time this\n // effect runs. If the saved schemaId matches, we resume instead of\n // resetting so the user picks up exactly where they left off.\n if (\n schema.persistenceMode === \"zustand\" &&\n hasPersistedState(store, schema.id)\n ) {\n // Keep data + currentStepId + history — just update schema & externalVars\n store.getState().resume(schema, externalVars);\n\n // Navigate to the persisted step\n const state = store.getState();\n if (state.currentStepId) {\n const tree = resolveTree(schema, state.data, state.externalVars, externalEnums);\n const step = tree.steps.find(\n (s) => s.definition.id === state.currentStepId\n );\n if (step && step.definition.url !== pathname) {\n router.push(step.definition.url);\n }\n }\n return;\n }\n\n // ── Fresh-start path ───────────────────────────────────────────────────\n let data = { ...defaultValues };\n\n if (fetchData) {\n try {\n const fetched = await fetchData();\n if (!cancelled) {\n data = { ...data, ...fetched };\n }\n } catch (err) {\n console.error(\"Waypoint: fetchData failed\", err);\n }\n }\n\n if (cancelled) return;\n\n store.getState().init(schema, { data, externalVars });\n\n // Deep-link resume: if the user already has data, redirect to the last\n // valid step instead of forcing them back to step 1.\n const state = store.getState();\n const tree = resolveTree(schema, state.data, state.externalVars, externalEnums);\n const lastValid = findLastValidStep(tree.steps, state.data, state.externalVars);\n\n if (lastValid && lastValid.definition.url !== pathname) {\n router.push(lastValid.definition.url);\n }\n }\n\n initialize();\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Sync externalVars into the store when the prop changes after mount\n useEffect(() => {\n for (const [key, value] of Object.entries(externalVars)) {\n store.getState().setExternalVar(key, value);\n }\n }, [externalVars, store]);\n\n const contextValue = useMemo(\n () => ({\n schema,\n store,\n onComplete,\n onStepComplete,\n onDataChange,\n onStepSkipped,\n customFieldTypes,\n externalEnums,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [schema, store, externalEnums]\n );\n\n // Show a blocking error if required external variables are missing after init\n if (missingVars.length > 0) {\n return (\n <div\n role=\"alert\"\n style={{\n color: \"#b91c1c\",\n background: \"#fef2f2\",\n border: \"1px solid #fca5a5\",\n borderRadius: 8,\n padding: \"1rem 1.25rem\",\n fontFamily: \"sans-serif\",\n }}\n >\n <strong>Waypoint Runtime Error</strong>\n <p style={{ margin: \"0.5rem 0 0\" }}>\n Missing required external variables:{\" \"}\n <code>{missingVars.join(\", \")}</code>\n </p>\n </div>\n );\n }\n\n return (\n <WaypointRuntimeContext.Provider value={contextValue}>\n {children}\n </WaypointRuntimeContext.Provider>\n );\n}\n","\"use client\";\n\nimport { useCallback, useEffect, useMemo } from \"react\";\nimport { useRouter, usePathname } from \"next/navigation\";\nimport { useStore } from \"zustand\";\nimport { useForm } from \"react-hook-form\";\nimport { zodResolver } from \"@hookform/resolvers/zod\";\nimport type { FieldErrors, FieldValues, UseFormReturn } from \"react-hook-form\";\n\nimport {\n resolveTree,\n buildZodSchema,\n calculateProgress,\n getNextStep,\n getPreviousStep,\n findStepIndex,\n} from \"@waypointjs/core\";\nimport type { ResolvedField, ResolvedStep, WaypointRuntimeStore } from \"@waypointjs/core\";\n\nimport { useWaypointRuntimeContext } from \"./context\";\n\n// ---------------------------------------------------------------------------\n// Return type\n// ---------------------------------------------------------------------------\n\nexport interface WaypointStepReturn {\n // Step context\n currentStep: ResolvedStep | undefined;\n progress: number;\n isFirstStep: boolean;\n isLastStep: boolean;\n\n // React Hook Form\n form: UseFormReturn<FieldValues>;\n /** Visible fields for the current step */\n fields: ResolvedField[];\n\n // Actions\n /** Validate → persist → onStepComplete → navigate next (or onComplete on last step) */\n handleSubmit: () => Promise<void>;\n goBack: () => void;\n /** Skip this step without validation — only available when step.skippable is true */\n skipStep: () => void;\n\n // State\n isSubmitting: boolean;\n /** Whether the current step can be skipped */\n canSkip: boolean;\n errors: FieldErrors;\n}\n\n// ---------------------------------------------------------------------------\n// Hook\n// ---------------------------------------------------------------------------\n\n/**\n * Per-page hook for multi-step forms powered by WaypointRunner.\n *\n * Derives the current step from the URL, provides react-hook-form wired to\n * the step's Zod schema, and handles navigation automatically.\n *\n * Must be used inside a `<WaypointRunner>` component.\n *\n * @example\n * const { form, fields, handleSubmit, progress } = useWaypointStep();\n */\nexport function useWaypointStep(): WaypointStepReturn {\n const { schema, store, onComplete, onStepComplete, onDataChange, onStepSkipped, externalEnums } =\n useWaypointRuntimeContext();\n const router = useRouter();\n const pathname = usePathname();\n\n // Subscribe to store state\n const { data, externalVars, currentStepId, skippedSteps, isSubmitting } = useStore(\n store,\n (s: WaypointRuntimeStore) => ({\n data: s.data,\n externalVars: s.externalVars,\n currentStepId: s.currentStepId,\n skippedSteps: s.skippedSteps,\n isSubmitting: s.isSubmitting,\n })\n );\n\n // Resolve the full tree\n const tree = useMemo(\n () => resolveTree(schema, data, externalVars, externalEnums, skippedSteps),\n [schema, data, externalVars, externalEnums, skippedSteps]\n );\n\n // Find the step matching the current pathname\n const currentStep = useMemo(() => {\n return tree.steps.find((s) => {\n const stepUrl = s.definition.url;\n // Exact match, or pathname ends with step URL (handles leading slash variants)\n return pathname === stepUrl || pathname.endsWith(stepUrl);\n });\n }, [tree.steps, pathname]);\n\n // Sync currentStepId into the store whenever the page changes\n useEffect(() => {\n if (currentStep && currentStep.definition.id !== currentStepId) {\n store.getState().setCurrentStep(currentStep.definition.id);\n }\n }, [currentStep, currentStepId, store]);\n\n // Visible fields for the current step\n const visibleFields = useMemo(\n () => currentStep?.fields.filter((f) => f.visible) ?? [],\n [currentStep]\n );\n\n // Build the Zod schema from visible fields (pass data for cross-field validation)\n const zodSchema = useMemo(() => buildZodSchema(visibleFields, externalEnums, data), [visibleFields, externalEnums, data]);\n\n // Existing step data used as default values, with dynamic defaults as fallback\n const defaultValues = useMemo(\n () => {\n if (!currentStep) return {};\n const stored = data[currentStep.definition.id] ?? {};\n // Merge dynamic/static defaults for fields that have no stored value\n const merged: Record<string, unknown> = { ...stored };\n for (const field of currentStep.fields) {\n const fid = field.definition.id;\n if (merged[fid] === undefined || merged[fid] === null || merged[fid] === \"\") {\n const dynDefault = field.resolvedDefaultValue;\n const staticDefault = field.definition.defaultValue;\n const resolved = dynDefault ?? staticDefault;\n if (resolved !== undefined) {\n merged[fid] = resolved;\n }\n }\n }\n return merged;\n },\n // Only recompute when the step changes (not on every data write)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [currentStep?.definition.id]\n );\n\n // React Hook Form instance\n const form = useForm<FieldValues>({\n resolver: zodResolver(zodSchema),\n defaultValues,\n });\n\n // Reset form defaults when step changes\n useEffect(() => {\n if (currentStep) {\n form.reset(data[currentStep.definition.id] ?? {});\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [currentStep?.definition.id]);\n\n // Navigation position\n const stepIndex = currentStep\n ? findStepIndex(tree.steps, currentStep.definition.id)\n : -1;\n const isFirstStep = stepIndex === 0;\n const isLastStep = stepIndex === tree.steps.length - 1;\n const progress = currentStep\n ? calculateProgress(tree.steps, currentStep.definition.id)\n : 0;\n\n // Whether the current step can be skipped\n const canSkip = !!currentStep?.definition.skippable;\n\n // goBack\n const goBack = useCallback(() => {\n if (!currentStep) return;\n const prev = getPreviousStep(tree.steps, currentStep.definition.id);\n if (prev) {\n router.push(prev.definition.url);\n }\n }, [currentStep, tree.steps, router]);\n\n // skipStep — bypass validation, mark as skipped, navigate to next\n const skipStep = useCallback(() => {\n if (!currentStep || !currentStep.definition.skippable) return;\n\n const stepId = currentStep.definition.id;\n store.getState().skipStep(stepId);\n\n // Re-resolve tree after marking as skipped (conditions may depend on $step.X.skipped)\n const updatedTree = resolveTree(schema, store.getState().data, externalVars, externalEnums, [\n ...skippedSteps,\n stepId,\n ]);\n const nextStep = getNextStep(updatedTree.steps, stepId);\n\n onStepSkipped?.(stepId);\n\n if (nextStep) {\n router.push(nextStep.definition.url);\n } else {\n store.getState().setCompleted(true);\n onComplete?.(store.getState().data);\n }\n }, [currentStep, store, schema, externalVars, externalEnums, skippedSteps, onStepSkipped, onComplete, router]);\n\n // handleSubmit\n const handleSubmit = useCallback(async () => {\n if (!currentStep) return;\n\n // 1. Validate via RHF + Zod\n const isValid = await form.trigger();\n if (!isValid) return;\n\n const values = form.getValues();\n\n // 2. Persist data + update submitting state\n store.getState().setIsSubmitting(true);\n\n try {\n // 3. Snapshot visible step IDs before writing (to detect tree changes)\n const oldVisibleIds = tree.steps.map((s) => s.definition.id).join(\",\");\n\n // 4. Write validated data into the store\n store.getState().setStepData(currentStep.definition.id, values);\n\n // 4b. If this step was previously skipped, un-skip it (user filled it properly)\n if (skippedSteps.includes(currentStep.definition.id)) {\n store.getState().unskipStep(currentStep.definition.id);\n }\n\n // 5. Re-resolve tree with updated data — step visibility may have changed\n const allData = store.getState().data;\n const updatedSkipped = store.getState().skippedSteps;\n const updatedTree = resolveTree(schema, allData, externalVars, externalEnums, updatedSkipped);\n const newVisibleIds = updatedTree.steps.map((s) => s.definition.id).join(\",\");\n\n // 6. If the visible tree changed, truncate stale forward history\n // (e.g. user went back, changed a dep value, tree changed → old path is invalid)\n if (oldVisibleIds !== newVisibleIds) {\n store.getState().truncateHistoryAt(currentStep.definition.id);\n }\n\n // 7. onDataChange callback\n onDataChange?.(allData);\n\n // 8. onStepComplete callback (may be async, e.g. backend-step mode)\n if (onStepComplete) {\n await onStepComplete(currentStep.definition.id, values);\n }\n\n // 9. Navigate using the UPDATED tree so we follow the new step order\n const nextStep = getNextStep(updatedTree.steps, currentStep.definition.id);\n if (nextStep) {\n router.push(nextStep.definition.url);\n } else {\n store.getState().setCompleted(true);\n await onComplete?.(allData);\n }\n } finally {\n store.getState().setIsSubmitting(false);\n }\n }, [\n currentStep,\n form,\n store,\n schema,\n tree.steps,\n externalVars,\n externalEnums,\n skippedSteps,\n onDataChange,\n onStepComplete,\n onComplete,\n router,\n ]);\n\n return {\n currentStep,\n progress,\n isFirstStep,\n isLastStep,\n form,\n fields: visibleFields,\n handleSubmit,\n goBack,\n skipStep,\n isSubmitting,\n canSkip,\n errors: form.formState.errors,\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waypointjs/next",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Next.js integration for @waypointjs/react",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"navigation",
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"@hookform/resolvers": "^3.9.1",
|
|
28
28
|
"react-hook-form": "^7.53.2",
|
|
29
29
|
"zustand": "^4.5.2",
|
|
30
|
-
"@waypointjs/core": "0.1.
|
|
31
|
-
"@waypointjs/react": "0.1.
|
|
30
|
+
"@waypointjs/core": "0.1.6",
|
|
31
|
+
"@waypointjs/react": "0.1.6"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/react": "^18.3.3",
|