formstand 0.13.0 → 0.14.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/CHANGELOG.md +40 -0
- package/README.md +86 -386
- package/dist/chunk-N7WPXYCU.js +144 -0
- package/dist/chunk-N7WPXYCU.js.map +1 -0
- package/dist/createForm-C9hZffCQ.d.cts +190 -0
- package/dist/createForm-C9hZffCQ.d.ts +190 -0
- package/dist/devtools/index.cjs +313 -0
- package/dist/devtools/index.cjs.map +1 -0
- package/dist/devtools/index.d.cts +27 -0
- package/dist/devtools/index.d.ts +27 -0
- package/dist/devtools/index.js +276 -0
- package/dist/devtools/index.js.map +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -188
- package/dist/index.d.ts +4 -188
- package/dist/index.js +3 -141
- package/dist/index.js.map +1 -1
- package/package.json +12 -2
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { getAtPath, useFormSelectorShallow } from '../chunk-N7WPXYCU.js';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
4
|
+
|
|
5
|
+
// src/devtools/paths.ts
|
|
6
|
+
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && // A Date is a leaf, not a container to recurse into: its own keys are
|
|
7
|
+
// methods, and useField binds the Date itself.
|
|
8
|
+
!(value instanceof Date);
|
|
9
|
+
var leafPaths = (value, base = "") => {
|
|
10
|
+
const join = (key) => base === "" ? String(key) : `${base}.${String(key)}`;
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
return value.length === 0 ? [base] : value.flatMap((item, index) => leafPaths(item, join(index)));
|
|
13
|
+
}
|
|
14
|
+
if (isPlainObject(value)) {
|
|
15
|
+
const keys = Object.keys(value);
|
|
16
|
+
return keys.length === 0 ? [base] : keys.flatMap((key) => leafPaths(value[key], join(key)));
|
|
17
|
+
}
|
|
18
|
+
return [base];
|
|
19
|
+
};
|
|
20
|
+
var unmatchedErrorKeys = (errorKeys, leaves) => {
|
|
21
|
+
const covered = new Set(leaves);
|
|
22
|
+
return errorKeys.filter((key) => !covered.has(key));
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/devtools/styles.ts
|
|
26
|
+
var ANCHOR = {
|
|
27
|
+
"bottom-right": { bottom: 12, right: 12 },
|
|
28
|
+
"bottom-left": { bottom: 12, left: 12 },
|
|
29
|
+
"top-right": { top: 12, right: 12 },
|
|
30
|
+
"top-left": { top: 12, left: 12 }
|
|
31
|
+
};
|
|
32
|
+
var MONO = 'ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace';
|
|
33
|
+
var anchorFor = (position) => ANCHOR[position];
|
|
34
|
+
var styles = {
|
|
35
|
+
root: {
|
|
36
|
+
position: "fixed",
|
|
37
|
+
zIndex: 2147483e3,
|
|
38
|
+
fontFamily: MONO,
|
|
39
|
+
fontSize: 11,
|
|
40
|
+
lineHeight: 1.45,
|
|
41
|
+
color: "#e6e6e6",
|
|
42
|
+
colorScheme: "dark"
|
|
43
|
+
},
|
|
44
|
+
toggle: {
|
|
45
|
+
background: "#1b1b1f",
|
|
46
|
+
border: "1px solid #3a3a42",
|
|
47
|
+
color: "#e6e6e6",
|
|
48
|
+
borderRadius: 6,
|
|
49
|
+
padding: "6px 10px",
|
|
50
|
+
font: "inherit",
|
|
51
|
+
cursor: "pointer"
|
|
52
|
+
},
|
|
53
|
+
panel: {
|
|
54
|
+
width: "min(440px, calc(100vw - 24px))",
|
|
55
|
+
maxHeight: "min(60vh, 520px)",
|
|
56
|
+
overflow: "auto",
|
|
57
|
+
background: "#141417",
|
|
58
|
+
border: "1px solid #3a3a42",
|
|
59
|
+
borderRadius: 8,
|
|
60
|
+
boxShadow: "0 12px 32px rgba(0,0,0,0.45)"
|
|
61
|
+
},
|
|
62
|
+
header: {
|
|
63
|
+
display: "flex",
|
|
64
|
+
alignItems: "center",
|
|
65
|
+
justifyContent: "space-between",
|
|
66
|
+
gap: 8,
|
|
67
|
+
padding: "8px 10px",
|
|
68
|
+
borderBottom: "1px solid #2a2a31",
|
|
69
|
+
position: "sticky",
|
|
70
|
+
top: 0,
|
|
71
|
+
background: "#141417"
|
|
72
|
+
},
|
|
73
|
+
section: { padding: "8px 10px", borderBottom: "1px solid #23232a" },
|
|
74
|
+
sectionTitle: {
|
|
75
|
+
color: "#9aa0aa",
|
|
76
|
+
textTransform: "uppercase",
|
|
77
|
+
letterSpacing: "0.06em",
|
|
78
|
+
fontSize: 10,
|
|
79
|
+
margin: "0 0 6px"
|
|
80
|
+
},
|
|
81
|
+
table: {
|
|
82
|
+
width: "100%",
|
|
83
|
+
borderCollapse: "collapse",
|
|
84
|
+
tableLayout: "fixed"
|
|
85
|
+
},
|
|
86
|
+
th: {
|
|
87
|
+
textAlign: "left",
|
|
88
|
+
color: "#9aa0aa",
|
|
89
|
+
fontWeight: 400,
|
|
90
|
+
padding: "2px 4px",
|
|
91
|
+
borderBottom: "1px solid #2a2a31"
|
|
92
|
+
},
|
|
93
|
+
td: {
|
|
94
|
+
padding: "2px 4px",
|
|
95
|
+
verticalAlign: "top",
|
|
96
|
+
wordBreak: "break-word"
|
|
97
|
+
},
|
|
98
|
+
path: { color: "#c8b6ff" },
|
|
99
|
+
error: { color: "#ff8f8f" },
|
|
100
|
+
muted: { color: "#7d838d" },
|
|
101
|
+
flag: { color: "#7ddca4" },
|
|
102
|
+
pre: {
|
|
103
|
+
margin: 0,
|
|
104
|
+
whiteSpace: "pre-wrap",
|
|
105
|
+
wordBreak: "break-word"
|
|
106
|
+
},
|
|
107
|
+
button: {
|
|
108
|
+
background: "#22222a",
|
|
109
|
+
border: "1px solid #3a3a42",
|
|
110
|
+
color: "#e6e6e6",
|
|
111
|
+
borderRadius: 5,
|
|
112
|
+
padding: "3px 8px",
|
|
113
|
+
font: "inherit",
|
|
114
|
+
cursor: "pointer"
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
var isDevBuild = () => typeof process === "undefined" || process.env?.["NODE_ENV"] !== "production";
|
|
118
|
+
var preview = (value) => {
|
|
119
|
+
if (value === void 0) return "undefined";
|
|
120
|
+
if (value instanceof Date) return value.toISOString();
|
|
121
|
+
const json = JSON.stringify(value);
|
|
122
|
+
return json === void 0 ? String(value) : json.length > 60 ? `${json.slice(0, 57)}\u2026` : json;
|
|
123
|
+
};
|
|
124
|
+
var flags = (row) => [
|
|
125
|
+
row.touched ? "touched" : "",
|
|
126
|
+
row.dirty ? "dirty" : "",
|
|
127
|
+
row.isValidating ? "validating" : ""
|
|
128
|
+
].filter((flag) => flag !== "").join(" ");
|
|
129
|
+
var useDevtoolsState = (form) => useFormSelectorShallow(form, (state) => ({
|
|
130
|
+
values: state.values,
|
|
131
|
+
errors: state.errors,
|
|
132
|
+
schemaErrors: state.schemaErrors,
|
|
133
|
+
serverErrors: state.serverErrors,
|
|
134
|
+
touched: state.touched,
|
|
135
|
+
isValidating: state.isValidating,
|
|
136
|
+
isSubmitting: state.isSubmitting,
|
|
137
|
+
isValidatingForm: state.isValidatingForm,
|
|
138
|
+
submitCount: state.submitCount,
|
|
139
|
+
mode: state.mode
|
|
140
|
+
}));
|
|
141
|
+
var Section = ({
|
|
142
|
+
title,
|
|
143
|
+
children
|
|
144
|
+
}) => /* @__PURE__ */ jsxs("div", { style: styles.section, children: [
|
|
145
|
+
/* @__PURE__ */ jsx("p", { style: styles.sectionTitle, children: title }),
|
|
146
|
+
children
|
|
147
|
+
] });
|
|
148
|
+
var DevtoolsPanel = ({
|
|
149
|
+
form,
|
|
150
|
+
position = "bottom-right",
|
|
151
|
+
defaultOpen = false,
|
|
152
|
+
label = "formstand"
|
|
153
|
+
}) => {
|
|
154
|
+
const state = useDevtoolsState(form);
|
|
155
|
+
const [open, setOpen] = useState(defaultOpen);
|
|
156
|
+
const [held, setHeld] = useState(null);
|
|
157
|
+
const rows = leafPaths(state.values).map((path) => {
|
|
158
|
+
const field = form.getFieldState(path);
|
|
159
|
+
return {
|
|
160
|
+
path,
|
|
161
|
+
value: field.value,
|
|
162
|
+
error: field.error,
|
|
163
|
+
touched: field.touched,
|
|
164
|
+
dirty: field.dirty,
|
|
165
|
+
isValidating: field.isValidating
|
|
166
|
+
};
|
|
167
|
+
});
|
|
168
|
+
const orphanErrors = unmatchedErrorKeys(
|
|
169
|
+
Object.keys(state.errors),
|
|
170
|
+
rows.map((row) => row.path)
|
|
171
|
+
);
|
|
172
|
+
const diff = form.diff();
|
|
173
|
+
return /* @__PURE__ */ jsx("div", { style: { ...styles.root, ...anchorFor(position) }, children: open ? /* @__PURE__ */ jsxs("div", { style: styles.panel, children: [
|
|
174
|
+
/* @__PURE__ */ jsxs("div", { style: styles.header, children: [
|
|
175
|
+
/* @__PURE__ */ jsx("strong", { children: label }),
|
|
176
|
+
/* @__PURE__ */ jsxs("span", { style: styles.muted, children: [
|
|
177
|
+
state.mode,
|
|
178
|
+
state.isSubmitting ? " \xB7 submitting" : "",
|
|
179
|
+
state.isValidatingForm ? " \xB7 validating" : "",
|
|
180
|
+
state.submitCount > 0 ? ` \xB7 submits ${state.submitCount}` : ""
|
|
181
|
+
] }),
|
|
182
|
+
/* @__PURE__ */ jsx(
|
|
183
|
+
"button",
|
|
184
|
+
{
|
|
185
|
+
type: "button",
|
|
186
|
+
style: styles.button,
|
|
187
|
+
onClick: () => setOpen(false),
|
|
188
|
+
children: "close"
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
] }),
|
|
192
|
+
/* @__PURE__ */ jsx(Section, { title: `fields (${rows.length})`, children: /* @__PURE__ */ jsxs("table", { style: styles.table, children: [
|
|
193
|
+
/* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
|
|
194
|
+
/* @__PURE__ */ jsx("th", { style: { ...styles.th, width: "38%" }, children: "path" }),
|
|
195
|
+
/* @__PURE__ */ jsx("th", { style: styles.th, children: "value" }),
|
|
196
|
+
/* @__PURE__ */ jsx("th", { style: { ...styles.th, width: "26%" }, children: "state" })
|
|
197
|
+
] }) }),
|
|
198
|
+
/* @__PURE__ */ jsx("tbody", { children: rows.map((row) => /* @__PURE__ */ jsxs("tr", { children: [
|
|
199
|
+
/* @__PURE__ */ jsx("td", { style: { ...styles.td, ...styles.path }, children: row.path }),
|
|
200
|
+
/* @__PURE__ */ jsxs("td", { style: styles.td, children: [
|
|
201
|
+
preview(row.value),
|
|
202
|
+
row.error === void 0 ? null : /* @__PURE__ */ jsx("div", { style: styles.error, children: row.error.join(", ") })
|
|
203
|
+
] }),
|
|
204
|
+
/* @__PURE__ */ jsx("td", { style: { ...styles.td, ...styles.flag }, children: flags(row) })
|
|
205
|
+
] }, row.path)) })
|
|
206
|
+
] }) }),
|
|
207
|
+
orphanErrors.length === 0 ? null : (
|
|
208
|
+
// Errors with no field of their own: the root "" key, array-level
|
|
209
|
+
// messages, server verdicts on paths that hold no value yet.
|
|
210
|
+
/* @__PURE__ */ jsx(Section, { title: "errors without a field", children: orphanErrors.map((key) => /* @__PURE__ */ jsxs("div", { children: [
|
|
211
|
+
/* @__PURE__ */ jsx("span", { style: styles.path, children: key === "" ? "(root)" : key }),
|
|
212
|
+
" ",
|
|
213
|
+
/* @__PURE__ */ jsx("span", { style: styles.error, children: (state.errors[key] ?? []).join(", ") })
|
|
214
|
+
] }, key)) })
|
|
215
|
+
),
|
|
216
|
+
/* @__PURE__ */ jsxs(Section, { title: "error channels", children: [
|
|
217
|
+
/* @__PURE__ */ jsxs("div", { style: styles.muted, children: [
|
|
218
|
+
"schema ",
|
|
219
|
+
Object.keys(state.schemaErrors).length,
|
|
220
|
+
" \xB7 server",
|
|
221
|
+
" ",
|
|
222
|
+
Object.keys(state.serverErrors).length
|
|
223
|
+
] }),
|
|
224
|
+
Object.entries(state.serverErrors).map(([key, messages]) => /* @__PURE__ */ jsxs("div", { children: [
|
|
225
|
+
/* @__PURE__ */ jsx("span", { style: styles.path, children: key === "" ? "(root)" : key }),
|
|
226
|
+
" ",
|
|
227
|
+
/* @__PURE__ */ jsx("span", { style: styles.error, children: messages.join(", ") }),
|
|
228
|
+
" ",
|
|
229
|
+
/* @__PURE__ */ jsx("span", { style: styles.muted, children: getAtPath(state.schemaErrors, key) === void 0 ? "(showing)" : "(behind a schema error)" })
|
|
230
|
+
] }, key))
|
|
231
|
+
] }),
|
|
232
|
+
/* @__PURE__ */ jsx(Section, { title: "diff vs initial", children: /* @__PURE__ */ jsx("pre", { style: styles.pre, children: Object.keys(diff).length === 0 ? "clean" : JSON.stringify(diff, null, 2) }) }),
|
|
233
|
+
/* @__PURE__ */ jsxs(Section, { title: "time travel", children: [
|
|
234
|
+
/* @__PURE__ */ jsx(
|
|
235
|
+
"button",
|
|
236
|
+
{
|
|
237
|
+
type: "button",
|
|
238
|
+
style: styles.button,
|
|
239
|
+
onClick: () => setHeld(form.snapshot()),
|
|
240
|
+
children: "snapshot"
|
|
241
|
+
}
|
|
242
|
+
),
|
|
243
|
+
" ",
|
|
244
|
+
/* @__PURE__ */ jsx(
|
|
245
|
+
"button",
|
|
246
|
+
{
|
|
247
|
+
type: "button",
|
|
248
|
+
style: styles.button,
|
|
249
|
+
disabled: held === null,
|
|
250
|
+
onClick: () => {
|
|
251
|
+
if (held !== null) form.restore(held);
|
|
252
|
+
},
|
|
253
|
+
children: "restore"
|
|
254
|
+
}
|
|
255
|
+
),
|
|
256
|
+
" ",
|
|
257
|
+
/* @__PURE__ */ jsx("span", { style: styles.muted, children: held === null ? "nothing held" : "snapshot held" })
|
|
258
|
+
] })
|
|
259
|
+
] }) : /* @__PURE__ */ jsxs(
|
|
260
|
+
"button",
|
|
261
|
+
{
|
|
262
|
+
type: "button",
|
|
263
|
+
style: styles.toggle,
|
|
264
|
+
onClick: () => setOpen(true),
|
|
265
|
+
children: [
|
|
266
|
+
label,
|
|
267
|
+
Object.keys(state.errors).length > 0 ? ` \xB7 ${String(Object.keys(state.errors).length)} err` : ""
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
) });
|
|
271
|
+
};
|
|
272
|
+
var FormstandDevtools = (props) => isDevBuild() ? /* @__PURE__ */ jsx(DevtoolsPanel, { ...props }) : null;
|
|
273
|
+
|
|
274
|
+
export { FormstandDevtools, leafPaths, unmatchedErrorKeys };
|
|
275
|
+
//# sourceMappingURL=index.js.map
|
|
276
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/devtools/paths.ts","../../src/devtools/styles.ts","../../src/devtools/FormstandDevtools.tsx"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// Walking a values object into the list of paths the panel shows.\n\nconst isPlainObject = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" &&\n value !== null &&\n !Array.isArray(value) &&\n // A Date is a leaf, not a container to recurse into: its own keys are\n // methods, and useField binds the Date itself.\n !(value instanceof Date);\n\n// The addressable leaf paths of a values object, in declaration order.\n// Arrays contribute one path per ROW rather than one for the array, because\n// that is what a field binds to; the array's own path still shows up in the\n// panel via the error map, which is where `z.array().min(1)` lands.\n//\n// An empty object or array is itself a leaf here. It has no children to\n// describe, and dropping it would hide a field that exists in the schema.\nexport const leafPaths = (value: unknown, base = \"\"): readonly string[] => {\n const join = (key: string | number): string =>\n base === \"\" ? String(key) : `${base}.${String(key)}`;\n\n if (Array.isArray(value)) {\n return value.length === 0\n ? [base]\n : value.flatMap((item, index) => leafPaths(item, join(index)));\n }\n if (isPlainObject(value)) {\n const keys = Object.keys(value);\n return keys.length === 0\n ? [base]\n : keys.flatMap((key) => leafPaths(value[key], join(key)));\n }\n return [base];\n};\n\n// Error keys that no leaf path covers: the root \"\" key (a schema-wide\n// refine), array-level messages keyed at the container, and server verdicts\n// written at a path that holds no value yet. Without this the panel would\n// silently drop exactly the errors that are hardest to debug.\nexport const unmatchedErrorKeys = (\n errorKeys: readonly string[],\n leaves: readonly string[],\n): readonly string[] => {\n const covered = new Set(leaves);\n return errorKeys.filter((key) => !covered.has(key));\n};\n","import type { CSSProperties } from \"react\";\n\n// Inline styles on purpose: a devtools panel that needs the consumer to\n// import a stylesheet is a devtools panel that renders unstyled the first\n// time someone tries it. Inline also means nothing here can leak into or be\n// overridden by the host app's CSS, which matters when the whole job is\n// showing you the truth about your form.\n\nexport type DevtoolsPosition =\n | \"bottom-right\"\n | \"bottom-left\"\n | \"top-right\"\n | \"top-left\";\n\nconst ANCHOR: Readonly<Record<DevtoolsPosition, CSSProperties>> = {\n \"bottom-right\": { bottom: 12, right: 12 },\n \"bottom-left\": { bottom: 12, left: 12 },\n \"top-right\": { top: 12, right: 12 },\n \"top-left\": { top: 12, left: 12 },\n};\n\nconst MONO =\n 'ui-monospace, SFMono-Regular, Menlo, Consolas, \"Liberation Mono\", monospace';\n\nexport const anchorFor = (position: DevtoolsPosition): CSSProperties =>\n ANCHOR[position];\n\nexport const styles = {\n root: {\n position: \"fixed\",\n zIndex: 2147483000,\n fontFamily: MONO,\n fontSize: 11,\n lineHeight: 1.45,\n color: \"#e6e6e6\",\n colorScheme: \"dark\",\n } satisfies CSSProperties,\n toggle: {\n background: \"#1b1b1f\",\n border: \"1px solid #3a3a42\",\n color: \"#e6e6e6\",\n borderRadius: 6,\n padding: \"6px 10px\",\n font: \"inherit\",\n cursor: \"pointer\",\n } satisfies CSSProperties,\n panel: {\n width: \"min(440px, calc(100vw - 24px))\",\n maxHeight: \"min(60vh, 520px)\",\n overflow: \"auto\",\n background: \"#141417\",\n border: \"1px solid #3a3a42\",\n borderRadius: 8,\n boxShadow: \"0 12px 32px rgba(0,0,0,0.45)\",\n } satisfies CSSProperties,\n header: {\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"space-between\",\n gap: 8,\n padding: \"8px 10px\",\n borderBottom: \"1px solid #2a2a31\",\n position: \"sticky\",\n top: 0,\n background: \"#141417\",\n } satisfies CSSProperties,\n section: { padding: \"8px 10px\", borderBottom: \"1px solid #23232a\" },\n sectionTitle: {\n color: \"#9aa0aa\",\n textTransform: \"uppercase\",\n letterSpacing: \"0.06em\",\n fontSize: 10,\n margin: \"0 0 6px\",\n } satisfies CSSProperties,\n table: {\n width: \"100%\",\n borderCollapse: \"collapse\",\n tableLayout: \"fixed\",\n } satisfies CSSProperties,\n th: {\n textAlign: \"left\",\n color: \"#9aa0aa\",\n fontWeight: 400,\n padding: \"2px 4px\",\n borderBottom: \"1px solid #2a2a31\",\n } satisfies CSSProperties,\n td: {\n padding: \"2px 4px\",\n verticalAlign: \"top\",\n wordBreak: \"break-word\",\n } satisfies CSSProperties,\n path: { color: \"#c8b6ff\" } satisfies CSSProperties,\n error: { color: \"#ff8f8f\" } satisfies CSSProperties,\n muted: { color: \"#7d838d\" } satisfies CSSProperties,\n flag: { color: \"#7ddca4\" } satisfies CSSProperties,\n pre: {\n margin: 0,\n whiteSpace: \"pre-wrap\",\n wordBreak: \"break-word\",\n } satisfies CSSProperties,\n button: {\n background: \"#22222a\",\n border: \"1px solid #3a3a42\",\n color: \"#e6e6e6\",\n borderRadius: 5,\n padding: \"3px 8px\",\n font: \"inherit\",\n cursor: \"pointer\",\n } satisfies CSSProperties,\n} as const;\n","import { type ReactNode, useState } from \"react\";\nimport type { z } from \"zod\";\nimport type { Form } from \"../core/createForm\";\nimport type { FieldPath } from \"../core/fieldPath\";\nimport { getAtPath } from \"../core/path\";\nimport type { FormState } from \"../core/types\";\nimport { useFormSelectorShallow } from \"../react/useFormSelector\";\nimport { leafPaths, unmatchedErrorKeys } from \"./paths\";\nimport { type DevtoolsPosition, anchorFor, styles } from \"./styles\";\n\nexport type FormstandDevtoolsProps<TSchema extends z.ZodType> = Readonly<{\n form: Form<TSchema>;\n position?: DevtoolsPosition;\n defaultOpen?: boolean;\n /** Shown on the toggle, for telling two mounted panels apart. */\n label?: string;\n}>;\n\ntype FieldRow = Readonly<{\n path: string;\n value: unknown;\n error: readonly string[] | undefined;\n touched: boolean;\n dirty: boolean;\n isValidating: boolean;\n}>;\n\n// Non-production only, matching createForm's own devtools gate: the panel is\n// a debugging affordance, and shipping it to end users would leak the whole\n// form state into the page. Bundlers that define NODE_ENV drop the panel\n// entirely; the `typeof process` guard keeps it from throwing in runtimes\n// that define no process at all.\nconst isDevBuild = (): boolean =>\n typeof process === \"undefined\" ||\n process.env?.[\"NODE_ENV\"] !== \"production\";\n\nconst preview = (value: unknown): string => {\n if (value === undefined) return \"undefined\";\n if (value instanceof Date) return value.toISOString();\n const json = JSON.stringify(value);\n // Long strings and big row objects would push the value column past the\n // panel; the full value is one expand away in the Values section.\n return json === undefined\n ? String(value)\n : json.length > 60\n ? `${json.slice(0, 57)}…`\n : json;\n};\n\nconst flags = (row: FieldRow): string =>\n [\n row.touched ? \"touched\" : \"\",\n row.dirty ? \"dirty\" : \"\",\n row.isValidating ? \"validating\" : \"\",\n ]\n .filter((flag) => flag !== \"\")\n .join(\" \");\n\n// Reads every slice the panel renders in ONE shallow-compared subscription.\n// Each member is a stored reference the form replaces on write, so shallow\n// equality is stable — deriving anything fresh (an array from dirtyFields())\n// inside the selector would hand the store a new reference every pass and\n// loop it (React #185).\nconst useDevtoolsState = <TSchema extends z.ZodType>(form: Form<TSchema>) =>\n useFormSelectorShallow(form, (state) => ({\n values: state.values,\n errors: state.errors,\n schemaErrors: state.schemaErrors,\n serverErrors: state.serverErrors,\n touched: state.touched,\n isValidating: state.isValidating,\n isSubmitting: state.isSubmitting,\n isValidatingForm: state.isValidatingForm,\n submitCount: state.submitCount,\n mode: state.mode,\n }));\n\nconst Section = ({\n title,\n children,\n}: Readonly<{ title: string; children: ReactNode }>) => (\n <div style={styles.section}>\n <p style={styles.sectionTitle}>{title}</p>\n {children}\n </div>\n);\n\nconst DevtoolsPanel = <TSchema extends z.ZodType>({\n form,\n position = \"bottom-right\",\n defaultOpen = false,\n label = \"formstand\",\n}: FormstandDevtoolsProps<TSchema>) => {\n const state = useDevtoolsState(form);\n const [open, setOpen] = useState(defaultOpen);\n const [held, setHeld] = useState<FormState<z.input<TSchema>> | null>(null);\n\n // Computed every render rather than memoized. getFieldState and diff() read\n // through `form`, not through anything in a dependency array, so a memo\n // would need deps the linter can't see and would go stale the moment one\n // was missed. This component only re-renders when the subscription above\n // reports a real change, so recomputing is both cheap and always right.\n const rows: readonly FieldRow[] = leafPaths(state.values).map((path) => {\n // Runtime-built strings need the cast the typed surface documents.\n // getFieldState rather than re-deriving dirty here, so the panel reports\n // the library's own comparison rules (Dates by timestamp, deep for\n // containers) instead of offering a second opinion.\n const field = form.getFieldState(path as FieldPath<z.input<TSchema>>);\n return {\n path,\n value: field.value,\n error: field.error,\n touched: field.touched,\n dirty: field.dirty,\n isValidating: field.isValidating,\n };\n });\n\n const orphanErrors = unmatchedErrorKeys(\n Object.keys(state.errors),\n rows.map((row) => row.path),\n );\n\n const diff = form.diff();\n\n return (\n <div style={{ ...styles.root, ...anchorFor(position) }}>\n {open ? (\n <div style={styles.panel}>\n <div style={styles.header}>\n <strong>{label}</strong>\n <span style={styles.muted}>\n {state.mode}\n {state.isSubmitting ? \" · submitting\" : \"\"}\n {state.isValidatingForm ? \" · validating\" : \"\"}\n {state.submitCount > 0 ? ` · submits ${state.submitCount}` : \"\"}\n </span>\n <button\n type=\"button\"\n style={styles.button}\n onClick={() => setOpen(false)}\n >\n close\n </button>\n </div>\n\n <Section title={`fields (${rows.length})`}>\n <table style={styles.table}>\n <thead>\n <tr>\n <th style={{ ...styles.th, width: \"38%\" }}>path</th>\n <th style={styles.th}>value</th>\n <th style={{ ...styles.th, width: \"26%\" }}>state</th>\n </tr>\n </thead>\n <tbody>\n {rows.map((row) => (\n <tr key={row.path}>\n <td style={{ ...styles.td, ...styles.path }}>{row.path}</td>\n <td style={styles.td}>\n {preview(row.value)}\n {row.error === undefined ? null : (\n <div style={styles.error}>{row.error.join(\", \")}</div>\n )}\n </td>\n <td style={{ ...styles.td, ...styles.flag }}>\n {flags(row)}\n </td>\n </tr>\n ))}\n </tbody>\n </table>\n </Section>\n\n {orphanErrors.length === 0 ? null : (\n // Errors with no field of their own: the root \"\" key, array-level\n // messages, server verdicts on paths that hold no value yet.\n <Section title=\"errors without a field\">\n {orphanErrors.map((key) => (\n <div key={key}>\n <span style={styles.path}>{key === \"\" ? \"(root)\" : key}</span>{\" \"}\n <span style={styles.error}>\n {(state.errors[key] ?? []).join(\", \")}\n </span>\n </div>\n ))}\n </Section>\n )}\n\n <Section title=\"error channels\">\n <div style={styles.muted}>\n schema {Object.keys(state.schemaErrors).length} · server{\" \"}\n {Object.keys(state.serverErrors).length}\n </div>\n {Object.entries(state.serverErrors).map(([key, messages]) => (\n <div key={key}>\n <span style={styles.path}>{key === \"\" ? \"(root)\" : key}</span>{\" \"}\n <span style={styles.error}>{messages.join(\", \")}</span>{\" \"}\n <span style={styles.muted}>\n {getAtPath(state.schemaErrors, key) === undefined\n ? \"(showing)\"\n : \"(behind a schema error)\"}\n </span>\n </div>\n ))}\n </Section>\n\n <Section title=\"diff vs initial\">\n <pre style={styles.pre}>\n {Object.keys(diff).length === 0\n ? \"clean\"\n : JSON.stringify(diff, null, 2)}\n </pre>\n </Section>\n\n <Section title=\"time travel\">\n <button\n type=\"button\"\n style={styles.button}\n onClick={() => setHeld(form.snapshot())}\n >\n snapshot\n </button>{\" \"}\n <button\n type=\"button\"\n style={styles.button}\n disabled={held === null}\n onClick={() => {\n if (held !== null) form.restore(held);\n }}\n >\n restore\n </button>{\" \"}\n <span style={styles.muted}>\n {held === null ? \"nothing held\" : \"snapshot held\"}\n </span>\n </Section>\n </div>\n ) : (\n <button\n type=\"button\"\n style={styles.toggle}\n onClick={() => setOpen(true)}\n >\n {label}\n {Object.keys(state.errors).length > 0\n ? ` · ${String(Object.keys(state.errors).length)} err`\n : \"\"}\n </button>\n )}\n </div>\n );\n};\n\n/**\n * A form-aware debugging panel: every field's value, error, and flags, the\n * two error channels shown separately, the live diff against initial values,\n * and snapshot/restore time travel.\n *\n * Renders nothing in production builds.\n */\nexport const FormstandDevtools = <TSchema extends z.ZodType>(\n props: FormstandDevtoolsProps<TSchema>,\n) => (isDevBuild() ? <DevtoolsPanel {...props} /> : null);\n"]}
|