@waypointjs/next 0.1.2 → 0.1.4
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 +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -241,6 +241,7 @@ function useWaypointStep() {
|
|
|
241
241
|
}
|
|
242
242
|
|
|
243
243
|
exports.WaypointRunner = WaypointRunner;
|
|
244
|
+
exports.WaypointRuntimeContext = WaypointRuntimeContext;
|
|
244
245
|
exports.useWaypointRuntimeContext = useWaypointRuntimeContext;
|
|
245
246
|
exports.useWaypointStep = useWaypointStep;
|
|
246
247
|
//# sourceMappingURL=index.cjs.map
|
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 } 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 } 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"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { WaypointSchema, ResolvedStep, ResolvedField, WaypointRuntimeStore } from '@waypointjs/core';
|
|
3
3
|
import { UseFormReturn, FieldValues, FieldErrors } from 'react-hook-form';
|
|
4
|
+
import * as react from 'react';
|
|
4
5
|
import { StoreApi } from 'zustand';
|
|
5
6
|
|
|
6
7
|
interface WaypointRunnerProps {
|
|
@@ -81,10 +82,11 @@ interface WaypointRuntimeContextValue {
|
|
|
81
82
|
onStepComplete?: (stepId: string, data: Record<string, unknown>) => void | Promise<void>;
|
|
82
83
|
onDataChange?: (data: Record<string, Record<string, unknown>>) => void;
|
|
83
84
|
}
|
|
85
|
+
declare const WaypointRuntimeContext: react.Context<WaypointRuntimeContextValue | null>;
|
|
84
86
|
/**
|
|
85
87
|
* Returns the current WaypointRunner context.
|
|
86
88
|
* Must be used inside a `<WaypointRunner>` component.
|
|
87
89
|
*/
|
|
88
90
|
declare function useWaypointRuntimeContext(): WaypointRuntimeContextValue;
|
|
89
91
|
|
|
90
|
-
export { WaypointRunner, type WaypointRunnerProps, type WaypointRuntimeContextValue, type WaypointStepReturn, useWaypointRuntimeContext, useWaypointStep };
|
|
92
|
+
export { WaypointRunner, type WaypointRunnerProps, WaypointRuntimeContext, type WaypointRuntimeContextValue, type WaypointStepReturn, useWaypointRuntimeContext, useWaypointStep };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
2
|
import { WaypointSchema, ResolvedStep, ResolvedField, WaypointRuntimeStore } from '@waypointjs/core';
|
|
3
3
|
import { UseFormReturn, FieldValues, FieldErrors } from 'react-hook-form';
|
|
4
|
+
import * as react from 'react';
|
|
4
5
|
import { StoreApi } from 'zustand';
|
|
5
6
|
|
|
6
7
|
interface WaypointRunnerProps {
|
|
@@ -81,10 +82,11 @@ interface WaypointRuntimeContextValue {
|
|
|
81
82
|
onStepComplete?: (stepId: string, data: Record<string, unknown>) => void | Promise<void>;
|
|
82
83
|
onDataChange?: (data: Record<string, Record<string, unknown>>) => void;
|
|
83
84
|
}
|
|
85
|
+
declare const WaypointRuntimeContext: react.Context<WaypointRuntimeContextValue | null>;
|
|
84
86
|
/**
|
|
85
87
|
* Returns the current WaypointRunner context.
|
|
86
88
|
* Must be used inside a `<WaypointRunner>` component.
|
|
87
89
|
*/
|
|
88
90
|
declare function useWaypointRuntimeContext(): WaypointRuntimeContextValue;
|
|
89
91
|
|
|
90
|
-
export { WaypointRunner, type WaypointRunnerProps, type WaypointRuntimeContextValue, type WaypointStepReturn, useWaypointRuntimeContext, useWaypointStep };
|
|
92
|
+
export { WaypointRunner, type WaypointRunnerProps, WaypointRuntimeContext, type WaypointRuntimeContextValue, type WaypointStepReturn, useWaypointRuntimeContext, useWaypointStep };
|
package/dist/index.js
CHANGED
|
@@ -238,6 +238,6 @@ function useWaypointStep() {
|
|
|
238
238
|
};
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
-
export { WaypointRunner, useWaypointRuntimeContext, useWaypointStep };
|
|
241
|
+
export { WaypointRunner, WaypointRuntimeContext, useWaypointRuntimeContext, useWaypointStep };
|
|
242
242
|
//# sourceMappingURL=index.js.map
|
|
243
243
|
//# sourceMappingURL=index.js.map
|
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 } 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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@waypointjs/next",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
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.4",
|
|
31
|
+
"@waypointjs/react": "0.1.4"
|
|
32
32
|
},
|
|
33
33
|
"devDependencies": {
|
|
34
34
|
"@types/react": "^18.3.3",
|