@waypointjs/next 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/index.cjs +247 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +90 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.js +243 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mauconduit Thomas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var zustand = require('zustand');
|
|
5
|
+
var navigation = require('next/navigation');
|
|
6
|
+
var core = require('@waypointjs/core');
|
|
7
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
8
|
+
var reactHookForm = require('react-hook-form');
|
|
9
|
+
var zod = require('@hookform/resolvers/zod');
|
|
10
|
+
|
|
11
|
+
var WaypointRuntimeContext = react.createContext(null);
|
|
12
|
+
function useWaypointRuntimeContext() {
|
|
13
|
+
const ctx = react.useContext(WaypointRuntimeContext);
|
|
14
|
+
if (!ctx) {
|
|
15
|
+
throw new Error(
|
|
16
|
+
"useWaypointRuntimeContext must be called inside a <WaypointRunner> component."
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
return ctx;
|
|
20
|
+
}
|
|
21
|
+
function WaypointRunner({
|
|
22
|
+
schema,
|
|
23
|
+
externalVars = {},
|
|
24
|
+
defaultValues = {},
|
|
25
|
+
fetchData,
|
|
26
|
+
onComplete,
|
|
27
|
+
onStepComplete,
|
|
28
|
+
onDataChange,
|
|
29
|
+
children
|
|
30
|
+
}) {
|
|
31
|
+
const router = navigation.useRouter();
|
|
32
|
+
const pathname = navigation.usePathname();
|
|
33
|
+
const storeRef = react.useRef(null);
|
|
34
|
+
if (storeRef.current === null) {
|
|
35
|
+
storeRef.current = core.createRuntimeStore({
|
|
36
|
+
persistenceMode: schema.persistenceMode,
|
|
37
|
+
schemaId: schema.id
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
const store = storeRef.current;
|
|
41
|
+
const missingVars = zustand.useStore(store, (s) => {
|
|
42
|
+
if (!s.schema) return [];
|
|
43
|
+
return core.resolveTree(s.schema, s.data, s.externalVars).missingExternalVars;
|
|
44
|
+
});
|
|
45
|
+
react.useEffect(() => {
|
|
46
|
+
let cancelled = false;
|
|
47
|
+
async function initialize() {
|
|
48
|
+
if (schema.persistenceMode === "zustand" && core.hasPersistedState(store, schema.id)) {
|
|
49
|
+
store.getState().resume(schema, externalVars);
|
|
50
|
+
const state2 = store.getState();
|
|
51
|
+
if (state2.currentStepId) {
|
|
52
|
+
const tree2 = core.resolveTree(schema, state2.data, state2.externalVars);
|
|
53
|
+
const step = tree2.steps.find(
|
|
54
|
+
(s) => s.definition.id === state2.currentStepId
|
|
55
|
+
);
|
|
56
|
+
if (step && step.definition.url !== pathname) {
|
|
57
|
+
router.push(step.definition.url);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
let data = { ...defaultValues };
|
|
63
|
+
if (fetchData) {
|
|
64
|
+
try {
|
|
65
|
+
const fetched = await fetchData();
|
|
66
|
+
if (!cancelled) {
|
|
67
|
+
data = { ...data, ...fetched };
|
|
68
|
+
}
|
|
69
|
+
} catch (err) {
|
|
70
|
+
console.error("Waypoint: fetchData failed", err);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (cancelled) return;
|
|
74
|
+
store.getState().init(schema, { data, externalVars });
|
|
75
|
+
const state = store.getState();
|
|
76
|
+
const tree = core.resolveTree(schema, state.data, state.externalVars);
|
|
77
|
+
const lastValid = core.findLastValidStep(tree.steps, state.data, state.externalVars);
|
|
78
|
+
if (lastValid && lastValid.definition.url !== pathname) {
|
|
79
|
+
router.push(lastValid.definition.url);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
initialize();
|
|
83
|
+
return () => {
|
|
84
|
+
cancelled = true;
|
|
85
|
+
};
|
|
86
|
+
}, []);
|
|
87
|
+
react.useEffect(() => {
|
|
88
|
+
for (const [key, value] of Object.entries(externalVars)) {
|
|
89
|
+
store.getState().setExternalVar(key, value);
|
|
90
|
+
}
|
|
91
|
+
}, [externalVars, store]);
|
|
92
|
+
const contextValue = react.useMemo(
|
|
93
|
+
() => ({
|
|
94
|
+
schema,
|
|
95
|
+
store,
|
|
96
|
+
onComplete,
|
|
97
|
+
onStepComplete,
|
|
98
|
+
onDataChange
|
|
99
|
+
}),
|
|
100
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
101
|
+
[schema, store]
|
|
102
|
+
);
|
|
103
|
+
if (missingVars.length > 0) {
|
|
104
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
105
|
+
"div",
|
|
106
|
+
{
|
|
107
|
+
role: "alert",
|
|
108
|
+
style: {
|
|
109
|
+
color: "#b91c1c",
|
|
110
|
+
background: "#fef2f2",
|
|
111
|
+
border: "1px solid #fca5a5",
|
|
112
|
+
borderRadius: 8,
|
|
113
|
+
padding: "1rem 1.25rem",
|
|
114
|
+
fontFamily: "sans-serif"
|
|
115
|
+
},
|
|
116
|
+
children: [
|
|
117
|
+
/* @__PURE__ */ jsxRuntime.jsx("strong", { children: "Waypoint Runtime Error" }),
|
|
118
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { style: { margin: "0.5rem 0 0" }, children: [
|
|
119
|
+
"Missing required external variables:",
|
|
120
|
+
" ",
|
|
121
|
+
/* @__PURE__ */ jsxRuntime.jsx("code", { children: missingVars.join(", ") })
|
|
122
|
+
] })
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
return /* @__PURE__ */ jsxRuntime.jsx(WaypointRuntimeContext.Provider, { value: contextValue, children });
|
|
128
|
+
}
|
|
129
|
+
function useWaypointStep() {
|
|
130
|
+
const { schema, store, onComplete, onStepComplete, onDataChange } = useWaypointRuntimeContext();
|
|
131
|
+
const router = navigation.useRouter();
|
|
132
|
+
const pathname = navigation.usePathname();
|
|
133
|
+
const { data, externalVars, currentStepId, isSubmitting } = zustand.useStore(
|
|
134
|
+
store,
|
|
135
|
+
(s) => ({
|
|
136
|
+
data: s.data,
|
|
137
|
+
externalVars: s.externalVars,
|
|
138
|
+
currentStepId: s.currentStepId,
|
|
139
|
+
isSubmitting: s.isSubmitting
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
const tree = react.useMemo(
|
|
143
|
+
() => core.resolveTree(schema, data, externalVars),
|
|
144
|
+
[schema, data, externalVars]
|
|
145
|
+
);
|
|
146
|
+
const currentStep = react.useMemo(() => {
|
|
147
|
+
return tree.steps.find((s) => {
|
|
148
|
+
const stepUrl = s.definition.url;
|
|
149
|
+
return pathname === stepUrl || pathname.endsWith(stepUrl);
|
|
150
|
+
});
|
|
151
|
+
}, [tree.steps, pathname]);
|
|
152
|
+
react.useEffect(() => {
|
|
153
|
+
if (currentStep && currentStep.definition.id !== currentStepId) {
|
|
154
|
+
store.getState().setCurrentStep(currentStep.definition.id);
|
|
155
|
+
}
|
|
156
|
+
}, [currentStep, currentStepId, store]);
|
|
157
|
+
const visibleFields = react.useMemo(
|
|
158
|
+
() => currentStep?.fields.filter((f) => f.visible) ?? [],
|
|
159
|
+
[currentStep]
|
|
160
|
+
);
|
|
161
|
+
const zodSchema = react.useMemo(() => core.buildZodSchema(visibleFields), [visibleFields]);
|
|
162
|
+
const defaultValues = react.useMemo(
|
|
163
|
+
() => currentStep ? data[currentStep.definition.id] ?? {} : {},
|
|
164
|
+
// Only recompute when the step changes (not on every data write)
|
|
165
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
166
|
+
[currentStep?.definition.id]
|
|
167
|
+
);
|
|
168
|
+
const form = reactHookForm.useForm({
|
|
169
|
+
resolver: zod.zodResolver(zodSchema),
|
|
170
|
+
defaultValues
|
|
171
|
+
});
|
|
172
|
+
react.useEffect(() => {
|
|
173
|
+
if (currentStep) {
|
|
174
|
+
form.reset(data[currentStep.definition.id] ?? {});
|
|
175
|
+
}
|
|
176
|
+
}, [currentStep?.definition.id]);
|
|
177
|
+
const stepIndex = currentStep ? core.findStepIndex(tree.steps, currentStep.definition.id) : -1;
|
|
178
|
+
const isFirstStep = stepIndex === 0;
|
|
179
|
+
const isLastStep = stepIndex === tree.steps.length - 1;
|
|
180
|
+
const progress = currentStep ? core.calculateProgress(tree.steps, currentStep.definition.id) : 0;
|
|
181
|
+
const goBack = react.useCallback(() => {
|
|
182
|
+
if (!currentStep) return;
|
|
183
|
+
const prev = core.getPreviousStep(tree.steps, currentStep.definition.id);
|
|
184
|
+
if (prev) {
|
|
185
|
+
router.push(prev.definition.url);
|
|
186
|
+
}
|
|
187
|
+
}, [currentStep, tree.steps, router]);
|
|
188
|
+
const handleSubmit = react.useCallback(async () => {
|
|
189
|
+
if (!currentStep) return;
|
|
190
|
+
const isValid = await form.trigger();
|
|
191
|
+
if (!isValid) return;
|
|
192
|
+
const values = form.getValues();
|
|
193
|
+
store.getState().setIsSubmitting(true);
|
|
194
|
+
try {
|
|
195
|
+
const oldVisibleIds = tree.steps.map((s) => s.definition.id).join(",");
|
|
196
|
+
store.getState().setStepData(currentStep.definition.id, values);
|
|
197
|
+
const allData = store.getState().data;
|
|
198
|
+
const updatedTree = core.resolveTree(schema, allData, externalVars);
|
|
199
|
+
const newVisibleIds = updatedTree.steps.map((s) => s.definition.id).join(",");
|
|
200
|
+
if (oldVisibleIds !== newVisibleIds) {
|
|
201
|
+
store.getState().truncateHistoryAt(currentStep.definition.id);
|
|
202
|
+
}
|
|
203
|
+
onDataChange?.(allData);
|
|
204
|
+
if (onStepComplete) {
|
|
205
|
+
await onStepComplete(currentStep.definition.id, values);
|
|
206
|
+
}
|
|
207
|
+
const nextStep = core.getNextStep(updatedTree.steps, currentStep.definition.id);
|
|
208
|
+
if (nextStep) {
|
|
209
|
+
router.push(nextStep.definition.url);
|
|
210
|
+
} else {
|
|
211
|
+
store.getState().setCompleted(true);
|
|
212
|
+
await onComplete?.(allData);
|
|
213
|
+
}
|
|
214
|
+
} finally {
|
|
215
|
+
store.getState().setIsSubmitting(false);
|
|
216
|
+
}
|
|
217
|
+
}, [
|
|
218
|
+
currentStep,
|
|
219
|
+
form,
|
|
220
|
+
store,
|
|
221
|
+
schema,
|
|
222
|
+
tree.steps,
|
|
223
|
+
externalVars,
|
|
224
|
+
onDataChange,
|
|
225
|
+
onStepComplete,
|
|
226
|
+
onComplete,
|
|
227
|
+
router
|
|
228
|
+
]);
|
|
229
|
+
return {
|
|
230
|
+
currentStep,
|
|
231
|
+
progress,
|
|
232
|
+
isFirstStep,
|
|
233
|
+
isLastStep,
|
|
234
|
+
form,
|
|
235
|
+
fields: visibleFields,
|
|
236
|
+
handleSubmit,
|
|
237
|
+
goBack,
|
|
238
|
+
isSubmitting,
|
|
239
|
+
errors: form.formState.errors
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
exports.WaypointRunner = WaypointRunner;
|
|
244
|
+
exports.useWaypointRuntimeContext = useWaypointRuntimeContext;
|
|
245
|
+
exports.useWaypointStep = useWaypointStep;
|
|
246
|
+
//# sourceMappingURL=index.cjs.map
|
|
247
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { WaypointSchema, ResolvedStep, ResolvedField, WaypointRuntimeStore } from '@waypointjs/core';
|
|
3
|
+
import { UseFormReturn, FieldValues, FieldErrors } from 'react-hook-form';
|
|
4
|
+
import { StoreApi } from 'zustand';
|
|
5
|
+
|
|
6
|
+
interface WaypointRunnerProps {
|
|
7
|
+
schema: WaypointSchema;
|
|
8
|
+
externalVars?: Record<string, unknown>;
|
|
9
|
+
defaultValues?: Record<string, Record<string, unknown>>;
|
|
10
|
+
/** Async function to load previously-saved data (for deep-link resume) */
|
|
11
|
+
fetchData?: () => Promise<Record<string, Record<string, unknown>>>;
|
|
12
|
+
/** Called when the user completes the last step */
|
|
13
|
+
onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;
|
|
14
|
+
/** Called after each step is validated and submitted */
|
|
15
|
+
onStepComplete?: (stepId: string, data: Record<string, unknown>) => void | Promise<void>;
|
|
16
|
+
/** Called whenever any field value changes */
|
|
17
|
+
onDataChange?: (data: Record<string, Record<string, unknown>>) => void;
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Context provider that initialises the runtime store and wires up callbacks.
|
|
22
|
+
*
|
|
23
|
+
* ### Multi-journey support
|
|
24
|
+
* Each `<WaypointRunner>` creates its own **isolated** Zustand store instance,
|
|
25
|
+
* so multiple runners with different schemas can coexist in the same app
|
|
26
|
+
* without any state interference:
|
|
27
|
+
*
|
|
28
|
+
* ```tsx
|
|
29
|
+
* // Both journeys live side-by-side — each has its own store
|
|
30
|
+
* <WaypointRunner schema={projectSchema}>…</WaypointRunner>
|
|
31
|
+
* <WaypointRunner schema={depositSchema}>…</WaypointRunner>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* ### Pause & Resume
|
|
35
|
+
* When `schema.persistenceMode === "zustand"`, each journey's state
|
|
36
|
+
* (`data`, `currentStepId`, `history`) is saved to localStorage under a
|
|
37
|
+
* per-schema key (`waypoint-runtime-<schemaId>`).
|
|
38
|
+
*
|
|
39
|
+
* On remount, `WaypointRunner` detects the saved state and calls `resume()`
|
|
40
|
+
* instead of `init()`, so the user lands back exactly where they left off —
|
|
41
|
+
* navigation state and form data intact.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* <WaypointRunner schema={mySchema} onComplete={handleComplete}>
|
|
45
|
+
* {children}
|
|
46
|
+
* </WaypointRunner>
|
|
47
|
+
*/
|
|
48
|
+
declare function WaypointRunner({ schema, externalVars, defaultValues, fetchData, onComplete, onStepComplete, onDataChange, children, }: WaypointRunnerProps): react_jsx_runtime.JSX.Element;
|
|
49
|
+
|
|
50
|
+
interface WaypointStepReturn {
|
|
51
|
+
currentStep: ResolvedStep | undefined;
|
|
52
|
+
progress: number;
|
|
53
|
+
isFirstStep: boolean;
|
|
54
|
+
isLastStep: boolean;
|
|
55
|
+
form: UseFormReturn<FieldValues>;
|
|
56
|
+
/** Visible fields for the current step */
|
|
57
|
+
fields: ResolvedField[];
|
|
58
|
+
/** Validate → persist → onStepComplete → navigate next (or onComplete on last step) */
|
|
59
|
+
handleSubmit: () => Promise<void>;
|
|
60
|
+
goBack: () => void;
|
|
61
|
+
isSubmitting: boolean;
|
|
62
|
+
errors: FieldErrors;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Per-page hook for multi-step forms powered by WaypointRunner.
|
|
66
|
+
*
|
|
67
|
+
* Derives the current step from the URL, provides react-hook-form wired to
|
|
68
|
+
* the step's Zod schema, and handles navigation automatically.
|
|
69
|
+
*
|
|
70
|
+
* Must be used inside a `<WaypointRunner>` component.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* const { form, fields, handleSubmit, progress } = useWaypointStep();
|
|
74
|
+
*/
|
|
75
|
+
declare function useWaypointStep(): WaypointStepReturn;
|
|
76
|
+
|
|
77
|
+
interface WaypointRuntimeContextValue {
|
|
78
|
+
schema: WaypointSchema;
|
|
79
|
+
store: StoreApi<WaypointRuntimeStore>;
|
|
80
|
+
onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;
|
|
81
|
+
onStepComplete?: (stepId: string, data: Record<string, unknown>) => void | Promise<void>;
|
|
82
|
+
onDataChange?: (data: Record<string, Record<string, unknown>>) => void;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Returns the current WaypointRunner context.
|
|
86
|
+
* Must be used inside a `<WaypointRunner>` component.
|
|
87
|
+
*/
|
|
88
|
+
declare function useWaypointRuntimeContext(): WaypointRuntimeContextValue;
|
|
89
|
+
|
|
90
|
+
export { WaypointRunner, type WaypointRunnerProps, type WaypointRuntimeContextValue, type WaypointStepReturn, useWaypointRuntimeContext, useWaypointStep };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import { WaypointSchema, ResolvedStep, ResolvedField, WaypointRuntimeStore } from '@waypointjs/core';
|
|
3
|
+
import { UseFormReturn, FieldValues, FieldErrors } from 'react-hook-form';
|
|
4
|
+
import { StoreApi } from 'zustand';
|
|
5
|
+
|
|
6
|
+
interface WaypointRunnerProps {
|
|
7
|
+
schema: WaypointSchema;
|
|
8
|
+
externalVars?: Record<string, unknown>;
|
|
9
|
+
defaultValues?: Record<string, Record<string, unknown>>;
|
|
10
|
+
/** Async function to load previously-saved data (for deep-link resume) */
|
|
11
|
+
fetchData?: () => Promise<Record<string, Record<string, unknown>>>;
|
|
12
|
+
/** Called when the user completes the last step */
|
|
13
|
+
onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;
|
|
14
|
+
/** Called after each step is validated and submitted */
|
|
15
|
+
onStepComplete?: (stepId: string, data: Record<string, unknown>) => void | Promise<void>;
|
|
16
|
+
/** Called whenever any field value changes */
|
|
17
|
+
onDataChange?: (data: Record<string, Record<string, unknown>>) => void;
|
|
18
|
+
children: React.ReactNode;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Context provider that initialises the runtime store and wires up callbacks.
|
|
22
|
+
*
|
|
23
|
+
* ### Multi-journey support
|
|
24
|
+
* Each `<WaypointRunner>` creates its own **isolated** Zustand store instance,
|
|
25
|
+
* so multiple runners with different schemas can coexist in the same app
|
|
26
|
+
* without any state interference:
|
|
27
|
+
*
|
|
28
|
+
* ```tsx
|
|
29
|
+
* // Both journeys live side-by-side — each has its own store
|
|
30
|
+
* <WaypointRunner schema={projectSchema}>…</WaypointRunner>
|
|
31
|
+
* <WaypointRunner schema={depositSchema}>…</WaypointRunner>
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* ### Pause & Resume
|
|
35
|
+
* When `schema.persistenceMode === "zustand"`, each journey's state
|
|
36
|
+
* (`data`, `currentStepId`, `history`) is saved to localStorage under a
|
|
37
|
+
* per-schema key (`waypoint-runtime-<schemaId>`).
|
|
38
|
+
*
|
|
39
|
+
* On remount, `WaypointRunner` detects the saved state and calls `resume()`
|
|
40
|
+
* instead of `init()`, so the user lands back exactly where they left off —
|
|
41
|
+
* navigation state and form data intact.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* <WaypointRunner schema={mySchema} onComplete={handleComplete}>
|
|
45
|
+
* {children}
|
|
46
|
+
* </WaypointRunner>
|
|
47
|
+
*/
|
|
48
|
+
declare function WaypointRunner({ schema, externalVars, defaultValues, fetchData, onComplete, onStepComplete, onDataChange, children, }: WaypointRunnerProps): react_jsx_runtime.JSX.Element;
|
|
49
|
+
|
|
50
|
+
interface WaypointStepReturn {
|
|
51
|
+
currentStep: ResolvedStep | undefined;
|
|
52
|
+
progress: number;
|
|
53
|
+
isFirstStep: boolean;
|
|
54
|
+
isLastStep: boolean;
|
|
55
|
+
form: UseFormReturn<FieldValues>;
|
|
56
|
+
/** Visible fields for the current step */
|
|
57
|
+
fields: ResolvedField[];
|
|
58
|
+
/** Validate → persist → onStepComplete → navigate next (or onComplete on last step) */
|
|
59
|
+
handleSubmit: () => Promise<void>;
|
|
60
|
+
goBack: () => void;
|
|
61
|
+
isSubmitting: boolean;
|
|
62
|
+
errors: FieldErrors;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Per-page hook for multi-step forms powered by WaypointRunner.
|
|
66
|
+
*
|
|
67
|
+
* Derives the current step from the URL, provides react-hook-form wired to
|
|
68
|
+
* the step's Zod schema, and handles navigation automatically.
|
|
69
|
+
*
|
|
70
|
+
* Must be used inside a `<WaypointRunner>` component.
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* const { form, fields, handleSubmit, progress } = useWaypointStep();
|
|
74
|
+
*/
|
|
75
|
+
declare function useWaypointStep(): WaypointStepReturn;
|
|
76
|
+
|
|
77
|
+
interface WaypointRuntimeContextValue {
|
|
78
|
+
schema: WaypointSchema;
|
|
79
|
+
store: StoreApi<WaypointRuntimeStore>;
|
|
80
|
+
onComplete?: (data: Record<string, Record<string, unknown>>) => void | Promise<void>;
|
|
81
|
+
onStepComplete?: (stepId: string, data: Record<string, unknown>) => void | Promise<void>;
|
|
82
|
+
onDataChange?: (data: Record<string, Record<string, unknown>>) => void;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Returns the current WaypointRunner context.
|
|
86
|
+
* Must be used inside a `<WaypointRunner>` component.
|
|
87
|
+
*/
|
|
88
|
+
declare function useWaypointRuntimeContext(): WaypointRuntimeContextValue;
|
|
89
|
+
|
|
90
|
+
export { WaypointRunner, type WaypointRunnerProps, type WaypointRuntimeContextValue, type WaypointStepReturn, useWaypointRuntimeContext, useWaypointStep };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { createContext, useContext, useRef, useEffect, useMemo, useCallback } from 'react';
|
|
2
|
+
import { useStore } from 'zustand';
|
|
3
|
+
import { useRouter, usePathname } from 'next/navigation';
|
|
4
|
+
import { createRuntimeStore, resolveTree, buildZodSchema, findStepIndex, calculateProgress, getPreviousStep, getNextStep, hasPersistedState, findLastValidStep } from '@waypointjs/core';
|
|
5
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
6
|
+
import { useForm } from 'react-hook-form';
|
|
7
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
8
|
+
|
|
9
|
+
var WaypointRuntimeContext = createContext(null);
|
|
10
|
+
function useWaypointRuntimeContext() {
|
|
11
|
+
const ctx = useContext(WaypointRuntimeContext);
|
|
12
|
+
if (!ctx) {
|
|
13
|
+
throw new Error(
|
|
14
|
+
"useWaypointRuntimeContext must be called inside a <WaypointRunner> component."
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
return ctx;
|
|
18
|
+
}
|
|
19
|
+
function WaypointRunner({
|
|
20
|
+
schema,
|
|
21
|
+
externalVars = {},
|
|
22
|
+
defaultValues = {},
|
|
23
|
+
fetchData,
|
|
24
|
+
onComplete,
|
|
25
|
+
onStepComplete,
|
|
26
|
+
onDataChange,
|
|
27
|
+
children
|
|
28
|
+
}) {
|
|
29
|
+
const router = useRouter();
|
|
30
|
+
const pathname = usePathname();
|
|
31
|
+
const storeRef = useRef(null);
|
|
32
|
+
if (storeRef.current === null) {
|
|
33
|
+
storeRef.current = createRuntimeStore({
|
|
34
|
+
persistenceMode: schema.persistenceMode,
|
|
35
|
+
schemaId: schema.id
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
const store = storeRef.current;
|
|
39
|
+
const missingVars = useStore(store, (s) => {
|
|
40
|
+
if (!s.schema) return [];
|
|
41
|
+
return resolveTree(s.schema, s.data, s.externalVars).missingExternalVars;
|
|
42
|
+
});
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
let cancelled = false;
|
|
45
|
+
async function initialize() {
|
|
46
|
+
if (schema.persistenceMode === "zustand" && hasPersistedState(store, schema.id)) {
|
|
47
|
+
store.getState().resume(schema, externalVars);
|
|
48
|
+
const state2 = store.getState();
|
|
49
|
+
if (state2.currentStepId) {
|
|
50
|
+
const tree2 = resolveTree(schema, state2.data, state2.externalVars);
|
|
51
|
+
const step = tree2.steps.find(
|
|
52
|
+
(s) => s.definition.id === state2.currentStepId
|
|
53
|
+
);
|
|
54
|
+
if (step && step.definition.url !== pathname) {
|
|
55
|
+
router.push(step.definition.url);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
let data = { ...defaultValues };
|
|
61
|
+
if (fetchData) {
|
|
62
|
+
try {
|
|
63
|
+
const fetched = await fetchData();
|
|
64
|
+
if (!cancelled) {
|
|
65
|
+
data = { ...data, ...fetched };
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
console.error("Waypoint: fetchData failed", err);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (cancelled) return;
|
|
72
|
+
store.getState().init(schema, { data, externalVars });
|
|
73
|
+
const state = store.getState();
|
|
74
|
+
const tree = resolveTree(schema, state.data, state.externalVars);
|
|
75
|
+
const lastValid = findLastValidStep(tree.steps, state.data, state.externalVars);
|
|
76
|
+
if (lastValid && lastValid.definition.url !== pathname) {
|
|
77
|
+
router.push(lastValid.definition.url);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
initialize();
|
|
81
|
+
return () => {
|
|
82
|
+
cancelled = true;
|
|
83
|
+
};
|
|
84
|
+
}, []);
|
|
85
|
+
useEffect(() => {
|
|
86
|
+
for (const [key, value] of Object.entries(externalVars)) {
|
|
87
|
+
store.getState().setExternalVar(key, value);
|
|
88
|
+
}
|
|
89
|
+
}, [externalVars, store]);
|
|
90
|
+
const contextValue = useMemo(
|
|
91
|
+
() => ({
|
|
92
|
+
schema,
|
|
93
|
+
store,
|
|
94
|
+
onComplete,
|
|
95
|
+
onStepComplete,
|
|
96
|
+
onDataChange
|
|
97
|
+
}),
|
|
98
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
99
|
+
[schema, store]
|
|
100
|
+
);
|
|
101
|
+
if (missingVars.length > 0) {
|
|
102
|
+
return /* @__PURE__ */ jsxs(
|
|
103
|
+
"div",
|
|
104
|
+
{
|
|
105
|
+
role: "alert",
|
|
106
|
+
style: {
|
|
107
|
+
color: "#b91c1c",
|
|
108
|
+
background: "#fef2f2",
|
|
109
|
+
border: "1px solid #fca5a5",
|
|
110
|
+
borderRadius: 8,
|
|
111
|
+
padding: "1rem 1.25rem",
|
|
112
|
+
fontFamily: "sans-serif"
|
|
113
|
+
},
|
|
114
|
+
children: [
|
|
115
|
+
/* @__PURE__ */ jsx("strong", { children: "Waypoint Runtime Error" }),
|
|
116
|
+
/* @__PURE__ */ jsxs("p", { style: { margin: "0.5rem 0 0" }, children: [
|
|
117
|
+
"Missing required external variables:",
|
|
118
|
+
" ",
|
|
119
|
+
/* @__PURE__ */ jsx("code", { children: missingVars.join(", ") })
|
|
120
|
+
] })
|
|
121
|
+
]
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
return /* @__PURE__ */ jsx(WaypointRuntimeContext.Provider, { value: contextValue, children });
|
|
126
|
+
}
|
|
127
|
+
function useWaypointStep() {
|
|
128
|
+
const { schema, store, onComplete, onStepComplete, onDataChange } = useWaypointRuntimeContext();
|
|
129
|
+
const router = useRouter();
|
|
130
|
+
const pathname = usePathname();
|
|
131
|
+
const { data, externalVars, currentStepId, isSubmitting } = useStore(
|
|
132
|
+
store,
|
|
133
|
+
(s) => ({
|
|
134
|
+
data: s.data,
|
|
135
|
+
externalVars: s.externalVars,
|
|
136
|
+
currentStepId: s.currentStepId,
|
|
137
|
+
isSubmitting: s.isSubmitting
|
|
138
|
+
})
|
|
139
|
+
);
|
|
140
|
+
const tree = useMemo(
|
|
141
|
+
() => resolveTree(schema, data, externalVars),
|
|
142
|
+
[schema, data, externalVars]
|
|
143
|
+
);
|
|
144
|
+
const currentStep = useMemo(() => {
|
|
145
|
+
return tree.steps.find((s) => {
|
|
146
|
+
const stepUrl = s.definition.url;
|
|
147
|
+
return pathname === stepUrl || pathname.endsWith(stepUrl);
|
|
148
|
+
});
|
|
149
|
+
}, [tree.steps, pathname]);
|
|
150
|
+
useEffect(() => {
|
|
151
|
+
if (currentStep && currentStep.definition.id !== currentStepId) {
|
|
152
|
+
store.getState().setCurrentStep(currentStep.definition.id);
|
|
153
|
+
}
|
|
154
|
+
}, [currentStep, currentStepId, store]);
|
|
155
|
+
const visibleFields = useMemo(
|
|
156
|
+
() => currentStep?.fields.filter((f) => f.visible) ?? [],
|
|
157
|
+
[currentStep]
|
|
158
|
+
);
|
|
159
|
+
const zodSchema = useMemo(() => buildZodSchema(visibleFields), [visibleFields]);
|
|
160
|
+
const defaultValues = useMemo(
|
|
161
|
+
() => currentStep ? data[currentStep.definition.id] ?? {} : {},
|
|
162
|
+
// Only recompute when the step changes (not on every data write)
|
|
163
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
164
|
+
[currentStep?.definition.id]
|
|
165
|
+
);
|
|
166
|
+
const form = useForm({
|
|
167
|
+
resolver: zodResolver(zodSchema),
|
|
168
|
+
defaultValues
|
|
169
|
+
});
|
|
170
|
+
useEffect(() => {
|
|
171
|
+
if (currentStep) {
|
|
172
|
+
form.reset(data[currentStep.definition.id] ?? {});
|
|
173
|
+
}
|
|
174
|
+
}, [currentStep?.definition.id]);
|
|
175
|
+
const stepIndex = currentStep ? findStepIndex(tree.steps, currentStep.definition.id) : -1;
|
|
176
|
+
const isFirstStep = stepIndex === 0;
|
|
177
|
+
const isLastStep = stepIndex === tree.steps.length - 1;
|
|
178
|
+
const progress = currentStep ? calculateProgress(tree.steps, currentStep.definition.id) : 0;
|
|
179
|
+
const goBack = useCallback(() => {
|
|
180
|
+
if (!currentStep) return;
|
|
181
|
+
const prev = getPreviousStep(tree.steps, currentStep.definition.id);
|
|
182
|
+
if (prev) {
|
|
183
|
+
router.push(prev.definition.url);
|
|
184
|
+
}
|
|
185
|
+
}, [currentStep, tree.steps, router]);
|
|
186
|
+
const handleSubmit = useCallback(async () => {
|
|
187
|
+
if (!currentStep) return;
|
|
188
|
+
const isValid = await form.trigger();
|
|
189
|
+
if (!isValid) return;
|
|
190
|
+
const values = form.getValues();
|
|
191
|
+
store.getState().setIsSubmitting(true);
|
|
192
|
+
try {
|
|
193
|
+
const oldVisibleIds = tree.steps.map((s) => s.definition.id).join(",");
|
|
194
|
+
store.getState().setStepData(currentStep.definition.id, values);
|
|
195
|
+
const allData = store.getState().data;
|
|
196
|
+
const updatedTree = resolveTree(schema, allData, externalVars);
|
|
197
|
+
const newVisibleIds = updatedTree.steps.map((s) => s.definition.id).join(",");
|
|
198
|
+
if (oldVisibleIds !== newVisibleIds) {
|
|
199
|
+
store.getState().truncateHistoryAt(currentStep.definition.id);
|
|
200
|
+
}
|
|
201
|
+
onDataChange?.(allData);
|
|
202
|
+
if (onStepComplete) {
|
|
203
|
+
await onStepComplete(currentStep.definition.id, values);
|
|
204
|
+
}
|
|
205
|
+
const nextStep = getNextStep(updatedTree.steps, currentStep.definition.id);
|
|
206
|
+
if (nextStep) {
|
|
207
|
+
router.push(nextStep.definition.url);
|
|
208
|
+
} else {
|
|
209
|
+
store.getState().setCompleted(true);
|
|
210
|
+
await onComplete?.(allData);
|
|
211
|
+
}
|
|
212
|
+
} finally {
|
|
213
|
+
store.getState().setIsSubmitting(false);
|
|
214
|
+
}
|
|
215
|
+
}, [
|
|
216
|
+
currentStep,
|
|
217
|
+
form,
|
|
218
|
+
store,
|
|
219
|
+
schema,
|
|
220
|
+
tree.steps,
|
|
221
|
+
externalVars,
|
|
222
|
+
onDataChange,
|
|
223
|
+
onStepComplete,
|
|
224
|
+
onComplete,
|
|
225
|
+
router
|
|
226
|
+
]);
|
|
227
|
+
return {
|
|
228
|
+
currentStep,
|
|
229
|
+
progress,
|
|
230
|
+
isFirstStep,
|
|
231
|
+
isLastStep,
|
|
232
|
+
form,
|
|
233
|
+
fields: visibleFields,
|
|
234
|
+
handleSubmit,
|
|
235
|
+
goBack,
|
|
236
|
+
isSubmitting,
|
|
237
|
+
errors: form.formState.errors
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export { WaypointRunner, useWaypointRuntimeContext, useWaypointStep };
|
|
242
|
+
//# sourceMappingURL=index.js.map
|
|
243
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +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"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@waypointjs/next",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Next.js integration for @waypointjs/react",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"navigation",
|
|
7
|
+
"journey",
|
|
8
|
+
"wizard",
|
|
9
|
+
"next.js"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"type": "module",
|
|
13
|
+
"main": "./dist/index.cjs",
|
|
14
|
+
"module": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"require": "./dist/index.cjs"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"@hookform/resolvers": "^3.9.1",
|
|
28
|
+
"react-hook-form": "^7.53.2",
|
|
29
|
+
"zustand": "^4.5.2",
|
|
30
|
+
"@waypointjs/react": "0.1.0",
|
|
31
|
+
"@waypointjs/core": "0.1.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/react": "^18.3.3",
|
|
35
|
+
"next": "^14.2.3",
|
|
36
|
+
"react": "^18.3.1",
|
|
37
|
+
"tsup": "^8.1.0",
|
|
38
|
+
"typescript": "^5.4.5"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"next": ">=14",
|
|
42
|
+
"react": ">=18"
|
|
43
|
+
},
|
|
44
|
+
"scripts": {
|
|
45
|
+
"build": "tsup",
|
|
46
|
+
"dev": "tsup --watch",
|
|
47
|
+
"lint": "tsc --noEmit"
|
|
48
|
+
}
|
|
49
|
+
}
|