@waypointjs/next 0.1.5 → 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 +51 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +51 -7
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -26,6 +26,7 @@ function WaypointRunner({
|
|
|
26
26
|
onComplete,
|
|
27
27
|
onStepComplete,
|
|
28
28
|
onDataChange,
|
|
29
|
+
onStepSkipped,
|
|
29
30
|
customFieldTypes,
|
|
30
31
|
externalEnums,
|
|
31
32
|
children
|
|
@@ -98,6 +99,7 @@ function WaypointRunner({
|
|
|
98
99
|
onComplete,
|
|
99
100
|
onStepComplete,
|
|
100
101
|
onDataChange,
|
|
102
|
+
onStepSkipped,
|
|
101
103
|
customFieldTypes,
|
|
102
104
|
externalEnums
|
|
103
105
|
}),
|
|
@@ -131,21 +133,22 @@ function WaypointRunner({
|
|
|
131
133
|
return /* @__PURE__ */ jsxRuntime.jsx(WaypointRuntimeContext.Provider, { value: contextValue, children });
|
|
132
134
|
}
|
|
133
135
|
function useWaypointStep() {
|
|
134
|
-
const { schema, store, onComplete, onStepComplete, onDataChange, externalEnums } = useWaypointRuntimeContext();
|
|
136
|
+
const { schema, store, onComplete, onStepComplete, onDataChange, onStepSkipped, externalEnums } = useWaypointRuntimeContext();
|
|
135
137
|
const router = navigation.useRouter();
|
|
136
138
|
const pathname = navigation.usePathname();
|
|
137
|
-
const { data, externalVars, currentStepId, isSubmitting } = zustand.useStore(
|
|
139
|
+
const { data, externalVars, currentStepId, skippedSteps, isSubmitting } = zustand.useStore(
|
|
138
140
|
store,
|
|
139
141
|
(s) => ({
|
|
140
142
|
data: s.data,
|
|
141
143
|
externalVars: s.externalVars,
|
|
142
144
|
currentStepId: s.currentStepId,
|
|
145
|
+
skippedSteps: s.skippedSteps,
|
|
143
146
|
isSubmitting: s.isSubmitting
|
|
144
147
|
})
|
|
145
148
|
);
|
|
146
149
|
const tree = react.useMemo(
|
|
147
|
-
() => core.resolveTree(schema, data, externalVars, externalEnums),
|
|
148
|
-
[schema, data, externalVars, externalEnums]
|
|
150
|
+
() => core.resolveTree(schema, data, externalVars, externalEnums, skippedSteps),
|
|
151
|
+
[schema, data, externalVars, externalEnums, skippedSteps]
|
|
149
152
|
);
|
|
150
153
|
const currentStep = react.useMemo(() => {
|
|
151
154
|
return tree.steps.find((s) => {
|
|
@@ -162,9 +165,25 @@ function useWaypointStep() {
|
|
|
162
165
|
() => currentStep?.fields.filter((f) => f.visible) ?? [],
|
|
163
166
|
[currentStep]
|
|
164
167
|
);
|
|
165
|
-
const zodSchema = react.useMemo(() => core.buildZodSchema(visibleFields), [visibleFields]);
|
|
168
|
+
const zodSchema = react.useMemo(() => core.buildZodSchema(visibleFields, externalEnums, data), [visibleFields, externalEnums, data]);
|
|
166
169
|
const defaultValues = react.useMemo(
|
|
167
|
-
() =>
|
|
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
|
+
},
|
|
168
187
|
// Only recompute when the step changes (not on every data write)
|
|
169
188
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
170
189
|
[currentStep?.definition.id]
|
|
@@ -182,6 +201,7 @@ function useWaypointStep() {
|
|
|
182
201
|
const isFirstStep = stepIndex === 0;
|
|
183
202
|
const isLastStep = stepIndex === tree.steps.length - 1;
|
|
184
203
|
const progress = currentStep ? core.calculateProgress(tree.steps, currentStep.definition.id) : 0;
|
|
204
|
+
const canSkip = !!currentStep?.definition.skippable;
|
|
185
205
|
const goBack = react.useCallback(() => {
|
|
186
206
|
if (!currentStep) return;
|
|
187
207
|
const prev = core.getPreviousStep(tree.steps, currentStep.definition.id);
|
|
@@ -189,6 +209,23 @@ function useWaypointStep() {
|
|
|
189
209
|
router.push(prev.definition.url);
|
|
190
210
|
}
|
|
191
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]);
|
|
192
229
|
const handleSubmit = react.useCallback(async () => {
|
|
193
230
|
if (!currentStep) return;
|
|
194
231
|
const isValid = await form.trigger();
|
|
@@ -198,8 +235,12 @@ function useWaypointStep() {
|
|
|
198
235
|
try {
|
|
199
236
|
const oldVisibleIds = tree.steps.map((s) => s.definition.id).join(",");
|
|
200
237
|
store.getState().setStepData(currentStep.definition.id, values);
|
|
238
|
+
if (skippedSteps.includes(currentStep.definition.id)) {
|
|
239
|
+
store.getState().unskipStep(currentStep.definition.id);
|
|
240
|
+
}
|
|
201
241
|
const allData = store.getState().data;
|
|
202
|
-
const
|
|
242
|
+
const updatedSkipped = store.getState().skippedSteps;
|
|
243
|
+
const updatedTree = core.resolveTree(schema, allData, externalVars, externalEnums, updatedSkipped);
|
|
203
244
|
const newVisibleIds = updatedTree.steps.map((s) => s.definition.id).join(",");
|
|
204
245
|
if (oldVisibleIds !== newVisibleIds) {
|
|
205
246
|
store.getState().truncateHistoryAt(currentStep.definition.id);
|
|
@@ -226,6 +267,7 @@ function useWaypointStep() {
|
|
|
226
267
|
tree.steps,
|
|
227
268
|
externalVars,
|
|
228
269
|
externalEnums,
|
|
270
|
+
skippedSteps,
|
|
229
271
|
onDataChange,
|
|
230
272
|
onStepComplete,
|
|
231
273
|
onComplete,
|
|
@@ -240,7 +282,9 @@ function useWaypointStep() {
|
|
|
240
282
|
fields: visibleFields,
|
|
241
283
|
handleSubmit,
|
|
242
284
|
goBack,
|
|
285
|
+
skipStep,
|
|
243
286
|
isSubmitting,
|
|
287
|
+
canSkip,
|
|
244
288
|
errors: form.formState.errors
|
|
245
289
|
};
|
|
246
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":"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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 /** 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 /** 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 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 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\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, externalEnums } =\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, externalEnums),\n [schema, data, externalVars, externalEnums]\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, externalEnums);\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 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
|
@@ -16,6 +16,8 @@ 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;
|
|
19
21
|
/** App-provided custom field types — exposed via context for custom field rendering */
|
|
20
22
|
customFieldTypes?: CustomTypeDefinition[];
|
|
21
23
|
/** App-provided external enum lists — resolved into ResolvedField.resolvedOptions */
|
|
@@ -50,7 +52,7 @@ interface WaypointRunnerProps {
|
|
|
50
52
|
* {children}
|
|
51
53
|
* </WaypointRunner>
|
|
52
54
|
*/
|
|
53
|
-
declare function WaypointRunner({ schema, externalVars, defaultValues, fetchData, onComplete, onStepComplete, onDataChange, customFieldTypes, externalEnums, 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;
|
|
54
56
|
|
|
55
57
|
interface WaypointStepReturn {
|
|
56
58
|
currentStep: ResolvedStep | undefined;
|
|
@@ -63,7 +65,11 @@ interface WaypointStepReturn {
|
|
|
63
65
|
/** Validate → persist → onStepComplete → navigate next (or onComplete on last step) */
|
|
64
66
|
handleSubmit: () => Promise<void>;
|
|
65
67
|
goBack: () => void;
|
|
68
|
+
/** Skip this step without validation — only available when step.skippable is true */
|
|
69
|
+
skipStep: () => void;
|
|
66
70
|
isSubmitting: boolean;
|
|
71
|
+
/** Whether the current step can be skipped */
|
|
72
|
+
canSkip: boolean;
|
|
67
73
|
errors: FieldErrors;
|
|
68
74
|
}
|
|
69
75
|
/**
|
|
@@ -85,6 +91,8 @@ interface WaypointRuntimeContextValue {
|
|
|
85
91
|
onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;
|
|
86
92
|
onStepComplete?: (stepId: string, data: Record<string, unknown>) => void | Promise<void>;
|
|
87
93
|
onDataChange?: (data: Record<string, Record<string, unknown>>) => void;
|
|
94
|
+
/** Called when the user skips a step */
|
|
95
|
+
onStepSkipped?: (stepId: string) => void;
|
|
88
96
|
/** App-provided custom field types — available for rendering custom fields */
|
|
89
97
|
customFieldTypes?: CustomTypeDefinition[];
|
|
90
98
|
/** App-provided external enum lists — resolved into ResolvedField.resolvedOptions by the tree resolver */
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ 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;
|
|
19
21
|
/** App-provided custom field types — exposed via context for custom field rendering */
|
|
20
22
|
customFieldTypes?: CustomTypeDefinition[];
|
|
21
23
|
/** App-provided external enum lists — resolved into ResolvedField.resolvedOptions */
|
|
@@ -50,7 +52,7 @@ interface WaypointRunnerProps {
|
|
|
50
52
|
* {children}
|
|
51
53
|
* </WaypointRunner>
|
|
52
54
|
*/
|
|
53
|
-
declare function WaypointRunner({ schema, externalVars, defaultValues, fetchData, onComplete, onStepComplete, onDataChange, customFieldTypes, externalEnums, 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;
|
|
54
56
|
|
|
55
57
|
interface WaypointStepReturn {
|
|
56
58
|
currentStep: ResolvedStep | undefined;
|
|
@@ -63,7 +65,11 @@ interface WaypointStepReturn {
|
|
|
63
65
|
/** Validate → persist → onStepComplete → navigate next (or onComplete on last step) */
|
|
64
66
|
handleSubmit: () => Promise<void>;
|
|
65
67
|
goBack: () => void;
|
|
68
|
+
/** Skip this step without validation — only available when step.skippable is true */
|
|
69
|
+
skipStep: () => void;
|
|
66
70
|
isSubmitting: boolean;
|
|
71
|
+
/** Whether the current step can be skipped */
|
|
72
|
+
canSkip: boolean;
|
|
67
73
|
errors: FieldErrors;
|
|
68
74
|
}
|
|
69
75
|
/**
|
|
@@ -85,6 +91,8 @@ interface WaypointRuntimeContextValue {
|
|
|
85
91
|
onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;
|
|
86
92
|
onStepComplete?: (stepId: string, data: Record<string, unknown>) => void | Promise<void>;
|
|
87
93
|
onDataChange?: (data: Record<string, Record<string, unknown>>) => void;
|
|
94
|
+
/** Called when the user skips a step */
|
|
95
|
+
onStepSkipped?: (stepId: string) => void;
|
|
88
96
|
/** App-provided custom field types — available for rendering custom fields */
|
|
89
97
|
customFieldTypes?: CustomTypeDefinition[];
|
|
90
98
|
/** App-provided external enum lists — resolved into ResolvedField.resolvedOptions by the tree resolver */
|
package/dist/index.js
CHANGED
|
@@ -24,6 +24,7 @@ function WaypointRunner({
|
|
|
24
24
|
onComplete,
|
|
25
25
|
onStepComplete,
|
|
26
26
|
onDataChange,
|
|
27
|
+
onStepSkipped,
|
|
27
28
|
customFieldTypes,
|
|
28
29
|
externalEnums,
|
|
29
30
|
children
|
|
@@ -96,6 +97,7 @@ function WaypointRunner({
|
|
|
96
97
|
onComplete,
|
|
97
98
|
onStepComplete,
|
|
98
99
|
onDataChange,
|
|
100
|
+
onStepSkipped,
|
|
99
101
|
customFieldTypes,
|
|
100
102
|
externalEnums
|
|
101
103
|
}),
|
|
@@ -129,21 +131,22 @@ function WaypointRunner({
|
|
|
129
131
|
return /* @__PURE__ */ jsx(WaypointRuntimeContext.Provider, { value: contextValue, children });
|
|
130
132
|
}
|
|
131
133
|
function useWaypointStep() {
|
|
132
|
-
const { schema, store, onComplete, onStepComplete, onDataChange, externalEnums } = useWaypointRuntimeContext();
|
|
134
|
+
const { schema, store, onComplete, onStepComplete, onDataChange, onStepSkipped, externalEnums } = useWaypointRuntimeContext();
|
|
133
135
|
const router = useRouter();
|
|
134
136
|
const pathname = usePathname();
|
|
135
|
-
const { data, externalVars, currentStepId, isSubmitting } = useStore(
|
|
137
|
+
const { data, externalVars, currentStepId, skippedSteps, isSubmitting } = useStore(
|
|
136
138
|
store,
|
|
137
139
|
(s) => ({
|
|
138
140
|
data: s.data,
|
|
139
141
|
externalVars: s.externalVars,
|
|
140
142
|
currentStepId: s.currentStepId,
|
|
143
|
+
skippedSteps: s.skippedSteps,
|
|
141
144
|
isSubmitting: s.isSubmitting
|
|
142
145
|
})
|
|
143
146
|
);
|
|
144
147
|
const tree = useMemo(
|
|
145
|
-
() => resolveTree(schema, data, externalVars, externalEnums),
|
|
146
|
-
[schema, data, externalVars, externalEnums]
|
|
148
|
+
() => resolveTree(schema, data, externalVars, externalEnums, skippedSteps),
|
|
149
|
+
[schema, data, externalVars, externalEnums, skippedSteps]
|
|
147
150
|
);
|
|
148
151
|
const currentStep = useMemo(() => {
|
|
149
152
|
return tree.steps.find((s) => {
|
|
@@ -160,9 +163,25 @@ function useWaypointStep() {
|
|
|
160
163
|
() => currentStep?.fields.filter((f) => f.visible) ?? [],
|
|
161
164
|
[currentStep]
|
|
162
165
|
);
|
|
163
|
-
const zodSchema = useMemo(() => buildZodSchema(visibleFields), [visibleFields]);
|
|
166
|
+
const zodSchema = useMemo(() => buildZodSchema(visibleFields, externalEnums, data), [visibleFields, externalEnums, data]);
|
|
164
167
|
const defaultValues = useMemo(
|
|
165
|
-
() =>
|
|
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
|
+
},
|
|
166
185
|
// Only recompute when the step changes (not on every data write)
|
|
167
186
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
168
187
|
[currentStep?.definition.id]
|
|
@@ -180,6 +199,7 @@ function useWaypointStep() {
|
|
|
180
199
|
const isFirstStep = stepIndex === 0;
|
|
181
200
|
const isLastStep = stepIndex === tree.steps.length - 1;
|
|
182
201
|
const progress = currentStep ? calculateProgress(tree.steps, currentStep.definition.id) : 0;
|
|
202
|
+
const canSkip = !!currentStep?.definition.skippable;
|
|
183
203
|
const goBack = useCallback(() => {
|
|
184
204
|
if (!currentStep) return;
|
|
185
205
|
const prev = getPreviousStep(tree.steps, currentStep.definition.id);
|
|
@@ -187,6 +207,23 @@ function useWaypointStep() {
|
|
|
187
207
|
router.push(prev.definition.url);
|
|
188
208
|
}
|
|
189
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]);
|
|
190
227
|
const handleSubmit = useCallback(async () => {
|
|
191
228
|
if (!currentStep) return;
|
|
192
229
|
const isValid = await form.trigger();
|
|
@@ -196,8 +233,12 @@ function useWaypointStep() {
|
|
|
196
233
|
try {
|
|
197
234
|
const oldVisibleIds = tree.steps.map((s) => s.definition.id).join(",");
|
|
198
235
|
store.getState().setStepData(currentStep.definition.id, values);
|
|
236
|
+
if (skippedSteps.includes(currentStep.definition.id)) {
|
|
237
|
+
store.getState().unskipStep(currentStep.definition.id);
|
|
238
|
+
}
|
|
199
239
|
const allData = store.getState().data;
|
|
200
|
-
const
|
|
240
|
+
const updatedSkipped = store.getState().skippedSteps;
|
|
241
|
+
const updatedTree = resolveTree(schema, allData, externalVars, externalEnums, updatedSkipped);
|
|
201
242
|
const newVisibleIds = updatedTree.steps.map((s) => s.definition.id).join(",");
|
|
202
243
|
if (oldVisibleIds !== newVisibleIds) {
|
|
203
244
|
store.getState().truncateHistoryAt(currentStep.definition.id);
|
|
@@ -224,6 +265,7 @@ function useWaypointStep() {
|
|
|
224
265
|
tree.steps,
|
|
225
266
|
externalVars,
|
|
226
267
|
externalEnums,
|
|
268
|
+
skippedSteps,
|
|
227
269
|
onDataChange,
|
|
228
270
|
onStepComplete,
|
|
229
271
|
onComplete,
|
|
@@ -238,7 +280,9 @@ function useWaypointStep() {
|
|
|
238
280
|
fields: visibleFields,
|
|
239
281
|
handleSubmit,
|
|
240
282
|
goBack,
|
|
283
|
+
skipStep,
|
|
241
284
|
isSubmitting,
|
|
285
|
+
canSkip,
|
|
242
286
|
errors: form.formState.errors
|
|
243
287
|
};
|
|
244
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, 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 /** 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 /** 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 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 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\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, externalEnums } =\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, externalEnums),\n [schema, data, externalVars, externalEnums]\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, externalEnums);\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 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/
|
|
31
|
-
"@waypointjs/
|
|
30
|
+
"@waypointjs/core": "0.1.6",
|
|
31
|
+
"@waypointjs/react": "0.1.6"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/react": "^18.3.3",
|