formstand 0.19.0 → 0.20.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/dist/index.js CHANGED
@@ -1,9 +1,11 @@
1
- import { getAtPath, slotAtPath, setAtPath, isPlainObject, hasOwnSlot, valuesEqual, isFieldDirty, useFormSelector, useFormSelectorShallow, parsePath } from './chunk-N7WPXYCU.js';
1
+ import { getAtPath, slotAtPath, setAtPath, isPlainObject, hasOwnSlot, valuesEqual, useFormSelectorShallow, isFieldDirty, useFormSelector, parsePath } from './chunk-N7WPXYCU.js';
2
2
  export { getAtPath, parsePath, setAtPath, useFormSelector, useFormSelectorShallow } from './chunk-N7WPXYCU.js';
3
+ import { shouldValidateOn } from './chunk-MQ6262S3.js';
4
+ export { shouldValidateOn } from './chunk-MQ6262S3.js';
3
5
  import { createStore } from 'zustand/vanilla';
4
6
  import { devtools } from 'zustand/middleware';
5
7
  import { z } from 'zod';
6
- import { useRef, useEffect, useMemo, useCallback, useActionState, createContext, useState, useContext, useId } from 'react';
8
+ import { useRef, useEffect, useMemo, useState, useCallback, useActionState, createContext, useContext, useId } from 'react';
7
9
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
10
  import { useStore } from 'zustand/react';
9
11
  import { useShallow } from 'zustand/react/shallow';
@@ -1008,23 +1010,6 @@ var createForm = (schema, options) => {
1008
1010
  });
1009
1011
  };
1010
1012
 
1011
- // src/core/mode.ts
1012
- var shouldValidateOn = (trigger, mode, reValidateMode, submitAttempted, touched = false) => {
1013
- const effective = submitAttempted ? reValidateMode : mode;
1014
- switch (effective) {
1015
- case "onChange":
1016
- return trigger === "change";
1017
- case "onBlur":
1018
- return trigger === "blur";
1019
- case "onSubmit":
1020
- return false;
1021
- case "onTouched":
1022
- return trigger === "blur" || touched;
1023
- case "all":
1024
- return true;
1025
- }
1026
- };
1027
-
1028
1013
  // src/core/persist.ts
1029
1014
  var kindOf = (value) => value === null ? "null" : Array.isArray(value) ? "array" : typeof value;
1030
1015
  var conflictsWith = (draft, reference) => {
@@ -1183,6 +1168,233 @@ var useForm = (schema, options) => {
1183
1168
  }, [form, schema, options.initialValues]);
1184
1169
  return form;
1185
1170
  };
1171
+
1172
+ // src/react/inputProps.ts
1173
+ var hasFieldError = (error) => error !== void 0 && error.length > 0;
1174
+ var ariaInvalid = (field) => hasFieldError(field.error) ? true : void 0;
1175
+ var numberToInputText = (value) => value === void 0 || value === null || Number.isNaN(value) ? "" : String(value);
1176
+ var parseNumberText = (text) => {
1177
+ if (text.trim() === "") return { kind: "empty" };
1178
+ const parsed = Number(text);
1179
+ return Number.isFinite(parsed) ? { kind: "number", value: parsed } : { kind: "invalid" };
1180
+ };
1181
+ var textInputProps = (field) => ({
1182
+ name: field.path,
1183
+ value: field.value ?? "",
1184
+ "aria-invalid": ariaInvalid(field),
1185
+ onChange: (e) => {
1186
+ const text = e.target.value;
1187
+ field.setValue(text === "" && field.clearable ? field.emptyValue : text);
1188
+ },
1189
+ onBlur: field.onBlur
1190
+ });
1191
+ var numberInputProps = (field) => ({
1192
+ type: "number",
1193
+ name: field.path,
1194
+ value: numberToInputText(field.value),
1195
+ "aria-invalid": ariaInvalid(field),
1196
+ onChange: (e) => {
1197
+ const parsed = parseNumberText(e.target.value);
1198
+ if (parsed.kind === "invalid") return;
1199
+ field.setValue(
1200
+ parsed.kind === "number" ? parsed.value : field.emptyValue
1201
+ );
1202
+ },
1203
+ onBlur: field.onBlur
1204
+ });
1205
+ var dateToInputText = (value) => {
1206
+ if (value === void 0 || value === null || Number.isNaN(value.getTime())) {
1207
+ return "";
1208
+ }
1209
+ const pad = (part) => String(part).padStart(2, "0");
1210
+ return `${String(value.getFullYear()).padStart(4, "0")}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
1211
+ };
1212
+ var parseDateText = (text) => {
1213
+ if (text.trim() === "") return { kind: "empty" };
1214
+ const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
1215
+ if (match === null) return { kind: "invalid" };
1216
+ const [, year, month, day] = match;
1217
+ const y = Number(year);
1218
+ const date = new Date(y, Number(month) - 1, Number(day));
1219
+ if (y < 100) date.setFullYear(y);
1220
+ return date.getFullYear() === y && date.getMonth() === Number(month) - 1 && date.getDate() === Number(day) ? { kind: "date", value: date } : { kind: "invalid" };
1221
+ };
1222
+ var dateInputProps = (field) => ({
1223
+ type: "date",
1224
+ name: field.path,
1225
+ value: dateToInputText(field.value),
1226
+ "aria-invalid": ariaInvalid(field),
1227
+ onChange: (e) => {
1228
+ const parsed = parseDateText(e.target.value);
1229
+ if (parsed.kind !== "date") {
1230
+ field.setValue(field.emptyValue);
1231
+ return;
1232
+ }
1233
+ const current = field.value;
1234
+ const next = current instanceof Date && !Number.isNaN(current.getTime()) ? new Date(
1235
+ parsed.value.getFullYear(),
1236
+ parsed.value.getMonth(),
1237
+ parsed.value.getDate(),
1238
+ current.getHours(),
1239
+ current.getMinutes(),
1240
+ current.getSeconds(),
1241
+ current.getMilliseconds()
1242
+ ) : parsed.value;
1243
+ field.setValue(next);
1244
+ },
1245
+ onBlur: field.onBlur
1246
+ });
1247
+ var checkboxProps = (field) => ({
1248
+ type: "checkbox",
1249
+ name: field.path,
1250
+ checked: field.value ?? false,
1251
+ "aria-invalid": ariaInvalid(field),
1252
+ onChange: (e) => field.setValue(e.target.checked),
1253
+ onBlur: field.onBlur
1254
+ });
1255
+ var selectProps = (field) => ({
1256
+ name: field.path,
1257
+ value: field.value ?? "",
1258
+ "aria-invalid": ariaInvalid(field),
1259
+ onChange: (e) => {
1260
+ const v = e.target.value;
1261
+ field.setValue(v === "" && field.clearable ? field.emptyValue : v);
1262
+ },
1263
+ onBlur: field.onBlur
1264
+ });
1265
+
1266
+ // src/react/useMaskedInput.ts
1267
+ var useMaskedInput = (field, options) => {
1268
+ const [edit, setEdit] = useState({
1269
+ raw: null,
1270
+ pushed: void 0
1271
+ });
1272
+ const externallyChanged = edit.raw !== null && !Object.is(field.value, edit.pushed);
1273
+ if (externallyChanged) {
1274
+ setEdit({ raw: null, pushed: void 0 });
1275
+ }
1276
+ const raw = externallyChanged ? null : edit.raw;
1277
+ return {
1278
+ name: field.path,
1279
+ value: raw ?? (field.value === void 0 || field.value === null ? "" : options.format(field.value)),
1280
+ "aria-invalid": hasFieldError(field.error) ? true : void 0,
1281
+ onChange: (e) => {
1282
+ const text = e.target.value;
1283
+ const parsed = options.parse(text);
1284
+ switch (parsed.kind) {
1285
+ case "value":
1286
+ setEdit({ raw: text, pushed: parsed.value });
1287
+ field.setValue(parsed.value);
1288
+ return;
1289
+ case "empty": {
1290
+ const empty = field.emptyValue;
1291
+ setEdit({ raw: text, pushed: empty });
1292
+ field.setValue(empty);
1293
+ return;
1294
+ }
1295
+ case "invalid":
1296
+ setEdit({ raw: text, pushed: field.value });
1297
+ return;
1298
+ }
1299
+ },
1300
+ onBlur: () => {
1301
+ setEdit({ raw: null, pushed: void 0 });
1302
+ field.onBlur();
1303
+ }
1304
+ };
1305
+ };
1306
+ var useFormSteps = (form, steps) => {
1307
+ const [step, setStep] = useState(0);
1308
+ const [visited, setVisited] = useState(
1309
+ () => /* @__PURE__ */ new Set([0])
1310
+ );
1311
+ const latest = useRef(steps);
1312
+ useEffect(() => {
1313
+ latest.current = steps;
1314
+ });
1315
+ const validateStep = useCallback(
1316
+ async (index) => {
1317
+ const definition = latest.current[index];
1318
+ if (definition === void 0) return true;
1319
+ const result = await form.validateFieldsAsync(definition.fields);
1320
+ if (result.kind === "invalid") {
1321
+ Object.keys(result.errors).forEach((key) => {
1322
+ form.setTouched(key, true);
1323
+ });
1324
+ return false;
1325
+ }
1326
+ return true;
1327
+ },
1328
+ [form]
1329
+ );
1330
+ const next = useCallback(async () => {
1331
+ const ok = await validateStep(step);
1332
+ if (!ok) return false;
1333
+ const target = Math.min(step + 1, latest.current.length - 1);
1334
+ setStep(target);
1335
+ setVisited((prev) => /* @__PURE__ */ new Set([...prev, target]));
1336
+ return true;
1337
+ }, [validateStep, step]);
1338
+ const back = useCallback(() => {
1339
+ setStep((prev) => Math.max(prev - 1, 0));
1340
+ }, []);
1341
+ const goTo = useCallback(
1342
+ async (target) => {
1343
+ const count = latest.current.length;
1344
+ const clamped = Math.max(0, Math.min(target, count - 1));
1345
+ if (clamped <= step) {
1346
+ setStep(clamped);
1347
+ return clamped;
1348
+ }
1349
+ const walk = async (from) => {
1350
+ if (from >= clamped) return clamped;
1351
+ return await validateStep(from) ? walk(from + 1) : from;
1352
+ };
1353
+ const landed = await walk(step);
1354
+ setStep(landed);
1355
+ setVisited(
1356
+ (prev) => /* @__PURE__ */ new Set([
1357
+ ...prev,
1358
+ ...Array.from({ length: landed - step + 1 }, (_, i) => step + i)
1359
+ ])
1360
+ );
1361
+ return landed;
1362
+ },
1363
+ [validateStep, step]
1364
+ );
1365
+ const errorFlags = useFormSelectorShallow(
1366
+ form,
1367
+ (state) => steps.map(
1368
+ (definition) => definition.fields.some(
1369
+ (path) => Object.keys(state.errors).some((key) => isPathOrChild(key, path))
1370
+ )
1371
+ )
1372
+ );
1373
+ const statuses = useMemo(
1374
+ () => steps.map(
1375
+ (definition, index) => ({
1376
+ name: definition.name,
1377
+ visited: visited.has(index),
1378
+ hasErrors: errorFlags[index] ?? false
1379
+ })
1380
+ ),
1381
+ [steps, visited, errorFlags]
1382
+ );
1383
+ return useMemo(
1384
+ () => ({
1385
+ step,
1386
+ name: steps[step]?.name,
1387
+ count: steps.length,
1388
+ isFirst: step === 0,
1389
+ isLast: step === steps.length - 1,
1390
+ next,
1391
+ back,
1392
+ goTo,
1393
+ steps: statuses
1394
+ }),
1395
+ [step, steps, next, back, goTo, statuses]
1396
+ );
1397
+ };
1186
1398
  var warnUnobserved = (result, options, site) => {
1187
1399
  if (result.kind === "error" && options?.onError === void 0 && process.env.NODE_ENV !== "production") {
1188
1400
  console.warn(
@@ -1676,6 +1888,7 @@ var createFormHooks = (form, name) => {
1676
1888
  paths
1677
1889
  );
1678
1890
  const useBoundSubmitCount = () => useSubmitCount(structural);
1891
+ const useBoundSteps = (steps) => useFormSteps(form, steps);
1679
1892
  const prefix = capitalize(name ?? "");
1680
1893
  return {
1681
1894
  [`use${prefix}Field`]: useBoundField,
@@ -1690,103 +1903,10 @@ var createFormHooks = (form, name) => {
1690
1903
  [`use${prefix}IsSubmitting`]: useBoundIsSubmitting,
1691
1904
  [`use${prefix}SubmitCount`]: useBoundSubmitCount,
1692
1905
  [`use${prefix}Values`]: useBoundValues,
1693
- [`use${prefix}Fields`]: useBoundFields
1906
+ [`use${prefix}Fields`]: useBoundFields,
1907
+ [`use${prefix}Steps`]: useBoundSteps
1694
1908
  };
1695
1909
  };
1696
-
1697
- // src/react/inputProps.ts
1698
- var hasFieldError = (error) => error !== void 0 && error.length > 0;
1699
- var ariaInvalid = (field) => hasFieldError(field.error) ? true : void 0;
1700
- var numberToInputText = (value) => value === void 0 || value === null || Number.isNaN(value) ? "" : String(value);
1701
- var parseNumberText = (text) => {
1702
- if (text.trim() === "") return { kind: "empty" };
1703
- const parsed = Number(text);
1704
- return Number.isFinite(parsed) ? { kind: "number", value: parsed } : { kind: "invalid" };
1705
- };
1706
- var textInputProps = (field) => ({
1707
- name: field.path,
1708
- value: field.value ?? "",
1709
- "aria-invalid": ariaInvalid(field),
1710
- onChange: (e) => {
1711
- const text = e.target.value;
1712
- field.setValue(text === "" && field.clearable ? field.emptyValue : text);
1713
- },
1714
- onBlur: field.onBlur
1715
- });
1716
- var numberInputProps = (field) => ({
1717
- type: "number",
1718
- name: field.path,
1719
- value: numberToInputText(field.value),
1720
- "aria-invalid": ariaInvalid(field),
1721
- onChange: (e) => {
1722
- const parsed = parseNumberText(e.target.value);
1723
- if (parsed.kind === "invalid") return;
1724
- field.setValue(
1725
- parsed.kind === "number" ? parsed.value : field.emptyValue
1726
- );
1727
- },
1728
- onBlur: field.onBlur
1729
- });
1730
- var dateToInputText = (value) => {
1731
- if (value === void 0 || value === null || Number.isNaN(value.getTime())) {
1732
- return "";
1733
- }
1734
- const pad = (part) => String(part).padStart(2, "0");
1735
- return `${String(value.getFullYear()).padStart(4, "0")}-${pad(value.getMonth() + 1)}-${pad(value.getDate())}`;
1736
- };
1737
- var parseDateText = (text) => {
1738
- if (text.trim() === "") return { kind: "empty" };
1739
- const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(text);
1740
- if (match === null) return { kind: "invalid" };
1741
- const [, year, month, day] = match;
1742
- const y = Number(year);
1743
- const date = new Date(y, Number(month) - 1, Number(day));
1744
- if (y < 100) date.setFullYear(y);
1745
- return date.getFullYear() === y && date.getMonth() === Number(month) - 1 && date.getDate() === Number(day) ? { kind: "date", value: date } : { kind: "invalid" };
1746
- };
1747
- var dateInputProps = (field) => ({
1748
- type: "date",
1749
- name: field.path,
1750
- value: dateToInputText(field.value),
1751
- "aria-invalid": ariaInvalid(field),
1752
- onChange: (e) => {
1753
- const parsed = parseDateText(e.target.value);
1754
- if (parsed.kind !== "date") {
1755
- field.setValue(field.emptyValue);
1756
- return;
1757
- }
1758
- const current = field.value;
1759
- const next = current instanceof Date && !Number.isNaN(current.getTime()) ? new Date(
1760
- parsed.value.getFullYear(),
1761
- parsed.value.getMonth(),
1762
- parsed.value.getDate(),
1763
- current.getHours(),
1764
- current.getMinutes(),
1765
- current.getSeconds(),
1766
- current.getMilliseconds()
1767
- ) : parsed.value;
1768
- field.setValue(next);
1769
- },
1770
- onBlur: field.onBlur
1771
- });
1772
- var checkboxProps = (field) => ({
1773
- type: "checkbox",
1774
- name: field.path,
1775
- checked: field.value ?? false,
1776
- "aria-invalid": ariaInvalid(field),
1777
- onChange: (e) => field.setValue(e.target.checked),
1778
- onBlur: field.onBlur
1779
- });
1780
- var selectProps = (field) => ({
1781
- name: field.path,
1782
- value: field.value ?? "",
1783
- "aria-invalid": ariaInvalid(field),
1784
- onChange: (e) => {
1785
- const v = e.target.value;
1786
- field.setValue(v === "" && field.clearable ? field.emptyValue : v);
1787
- },
1788
- onBlur: field.onBlur
1789
- });
1790
1910
  var useFieldA11y = (error) => {
1791
1911
  const id = useId();
1792
1912
  const errorId = `${id}-error`;
@@ -2079,6 +2199,6 @@ var focusField = (path, root) => {
2079
2199
  return path === "" ? (root !== void 0 || document.querySelectorAll("form").length <= 1) && focusFirstOf(namedControls(scope).filter(isFocusCandidate)) : focusFirstOf(pathCandidates(scope, [path]).filter(isFocusCandidate));
2080
2200
  };
2081
2201
 
2082
- export { CheckboxField, DateField, NumberField, SelectField, TextField, checkboxProps, createForm, createFormContext, createFormHooks, dateInputProps, dateToInputText, emptyValueForSchema, flattenIssues, focusField, focusFirstError, hasFieldError, isAsyncRequiredError, isClearableSchema, numberInputProps, numberToInputText, parseDateText, parseNumberText, persistForm, selectProps, shouldValidateOn, textInputProps, useField, useFieldArray, useFields, useForm, useFormAction, useFormActionState, useFormError, useFormValues, useIsDirty, useIsSubmitting, useIsValid, useIsValidating, useNumberInput, usePersistForm, useSubmitCount, useVariantField, validateAsync, validateSync };
2202
+ export { CheckboxField, DateField, NumberField, SelectField, TextField, checkboxProps, createForm, createFormContext, createFormHooks, dateInputProps, dateToInputText, emptyValueForSchema, flattenIssues, focusField, focusFirstError, hasFieldError, isAsyncRequiredError, isClearableSchema, numberInputProps, numberToInputText, parseDateText, parseNumberText, persistForm, selectProps, textInputProps, useField, useFieldArray, useFields, useForm, useFormAction, useFormActionState, useFormError, useFormSteps, useFormValues, useIsDirty, useIsSubmitting, useIsValid, useIsValidating, useMaskedInput, useNumberInput, usePersistForm, useSubmitCount, useVariantField, validateAsync, validateSync };
2083
2203
  //# sourceMappingURL=index.js.map
2084
2204
  //# sourceMappingURL=index.js.map