@krizaka/ui 3.0.0 → 3.2.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/README.md +1 -1
- package/dist/achievement.js +1 -1
- package/dist/action-bar.js +1 -1
- package/dist/app-shell.js +1 -1
- package/dist/argument-card.js +2 -2
- package/dist/card-grid.js +1 -1
- package/dist/card.js +1 -1
- package/dist/choice-grid.js +1 -1
- package/dist/{chunk-OGJI5X4W.js → chunk-2PUDI7JS.js} +2 -2
- package/dist/chunk-4LOMIQVX.js +21 -0
- package/dist/chunk-4LOMIQVX.js.map +1 -0
- package/dist/{chunk-K3SWZXYT.js → chunk-DGQMDJXK.js} +2 -2
- package/dist/chunk-G3ETON2A.js +22 -0
- package/dist/chunk-G3ETON2A.js.map +1 -0
- package/dist/chunk-I5RVM5CW.js +27 -0
- package/dist/chunk-I5RVM5CW.js.map +1 -0
- package/dist/chunk-IZ7MV6HM.js +26 -0
- package/dist/chunk-IZ7MV6HM.js.map +1 -0
- package/dist/chunk-ZNMFG62E.js +20 -0
- package/dist/chunk-ZNMFG62E.js.map +1 -0
- package/dist/cn.d.ts +9 -1
- package/dist/cn.js +5 -3
- package/dist/code-block.js +1 -1
- package/dist/command.js +1 -1
- package/dist/confirm-button.js +1 -1
- package/dist/continue-prompt.js +1 -1
- package/dist/cta-panel.js +1 -1
- package/dist/data-table.js +2 -2
- package/dist/date-picker.d.ts +650 -61
- package/dist/date-picker.js +918 -122
- package/dist/date-picker.js.map +1 -1
- package/dist/decision-list.js +1 -1
- package/dist/dropdown-menu.js +1 -1
- package/dist/error-page.js +1 -1
- package/dist/field.js +1 -1
- package/dist/filter-bar.js +2 -2
- package/dist/form/server.d.ts +96 -0
- package/dist/form/server.js +82 -0
- package/dist/form/server.js.map +1 -0
- package/dist/form.d.ts +503 -0
- package/dist/form.js +760 -0
- package/dist/form.js.map +1 -0
- package/dist/gallery-page.js +1 -1
- package/dist/glow-card.js +1 -1
- package/dist/goal-gauge.js +1 -1
- package/dist/how-it-works-page.js +1 -1
- package/dist/landscape.js +1 -1
- package/dist/link-card.js +1 -1
- package/dist/longform-page.js +1 -1
- package/dist/marquee.js +1 -1
- package/dist/mascot-quote.js +1 -1
- package/dist/motion.css +18 -1
- package/dist/native/form.d.ts +376 -0
- package/dist/native/form.js +650 -0
- package/dist/native/form.js.map +1 -0
- package/dist/native.d.ts +351 -3
- package/dist/native.js +1205 -399
- package/dist/native.js.map +1 -1
- package/dist/nav-bar.js +1 -1
- package/dist/page-hero.js +2 -2
- package/dist/page.js +2 -2
- package/dist/popover.js +1 -1
- package/dist/pricing-page.js +1 -1
- package/dist/product-page.js +1 -1
- package/dist/profile-card.js +1 -1
- package/dist/promise-card.js +1 -1
- package/dist/scroll-stage.js +1 -1
- package/dist/stage-banner.js +1 -1
- package/dist/terminal-replay.js +1 -1
- package/dist/toast.js +1 -1
- package/dist/toc.js +1 -1
- package/dist/tooltip.js +1 -1
- package/package.json +50 -7
- package/registry/achievement.json +1 -1
- package/registry/action-bar.json +1 -1
- package/registry/alert.json +1 -1
- package/registry/app-shell.json +1 -1
- package/registry/argument-card.json +1 -1
- package/registry/audio-player.json +1 -1
- package/registry/auth-layout.json +1 -1
- package/registry/avatar.json +1 -1
- package/registry/badge.json +1 -1
- package/registry/button.json +1 -1
- package/registry/card-grid.json +1 -1
- package/registry/card.json +1 -1
- package/registry/catalogue-page.json +1 -1
- package/registry/changelog-page.json +1 -1
- package/registry/checkbox.json +89 -4
- package/registry/chip-link.json +1 -1
- package/registry/chip.json +1 -1
- package/registry/choice-grid.json +1 -1
- package/registry/cn.json +1 -1
- package/registry/code-block.json +1 -1
- package/registry/combobox.json +1 -1
- package/registry/command.json +1 -1
- package/registry/component-page.json +1 -1
- package/registry/confirm-button.json +1 -1
- package/registry/contact-page.json +1 -1
- package/registry/continue-prompt.json +1 -1
- package/registry/countdown.json +1 -1
- package/registry/cta-panel.json +1 -1
- package/registry/dashboard-layout.json +1 -1
- package/registry/data-table.json +1 -1
- package/registry/date-picker.json +1811 -236
- package/registry/decision-list.json +1 -1
- package/registry/detail-layout.json +1 -1
- package/registry/dialog.json +1 -1
- package/registry/docs-article-page.json +1 -1
- package/registry/docs-hub-page.json +1 -1
- package/registry/docs-layout.json +1 -1
- package/registry/dropdown-menu.json +1 -1
- package/registry/empty-state.json +1 -1
- package/registry/error-page.json +1 -1
- package/registry/examples/checkbox/native/terms.d.ts +4 -0
- package/registry/examples/checkbox/native/terms.dark.png +0 -0
- package/registry/examples/checkbox/native/terms.js +26 -0
- package/registry/examples/checkbox/native/terms.light.png +0 -0
- package/registry/examples/checkbox/native/terms.tsx +28 -0
- package/registry/examples/date-picker/birth-date-wheel.d.ts +4 -0
- package/registry/examples/date-picker/birth-date-wheel.js +13 -0
- package/registry/examples/date-picker/birth-date-wheel.tsx +11 -0
- package/registry/examples/date-picker/birth-date.d.ts +4 -0
- package/registry/examples/date-picker/birth-date.js +25 -0
- package/registry/examples/date-picker/birth-date.tsx +21 -0
- package/registry/examples/date-picker/calendar.js +1 -1
- package/registry/examples/date-picker/calendar.tsx +2 -6
- package/registry/examples/date-picker/day-strip.d.ts +4 -0
- package/registry/examples/date-picker/day-strip.js +28 -0
- package/registry/examples/date-picker/day-strip.tsx +27 -0
- package/registry/examples/date-picker/month-year.d.ts +4 -0
- package/registry/examples/date-picker/month-year.js +13 -0
- package/registry/examples/date-picker/month-year.tsx +10 -0
- package/registry/examples/date-picker/native/birth-date-wheel.d.ts +4 -0
- package/registry/examples/date-picker/native/birth-date-wheel.dark.png +0 -0
- package/registry/examples/date-picker/native/birth-date-wheel.js +13 -0
- package/registry/examples/date-picker/native/birth-date-wheel.light.png +0 -0
- package/registry/examples/date-picker/native/birth-date-wheel.tsx +9 -0
- package/registry/examples/date-picker/native/birth-date.d.ts +4 -0
- package/registry/examples/date-picker/native/birth-date.dark.png +0 -0
- package/registry/examples/date-picker/native/birth-date.js +30 -0
- package/registry/examples/date-picker/native/birth-date.light.png +0 -0
- package/registry/examples/date-picker/native/birth-date.tsx +27 -0
- package/registry/examples/date-picker/native/calendar.d.ts +4 -0
- package/registry/examples/date-picker/native/calendar.dark.png +0 -0
- package/registry/examples/date-picker/native/calendar.js +13 -0
- package/registry/examples/date-picker/native/calendar.light.png +0 -0
- package/registry/examples/date-picker/native/calendar.tsx +8 -0
- package/registry/examples/date-picker/native/day-strip.d.ts +4 -0
- package/registry/examples/date-picker/native/day-strip.dark.png +0 -0
- package/registry/examples/date-picker/native/day-strip.js +29 -0
- package/registry/examples/date-picker/native/day-strip.light.png +0 -0
- package/registry/examples/date-picker/native/day-strip.tsx +26 -0
- package/registry/examples/date-picker/native/month-year.d.ts +4 -0
- package/registry/examples/date-picker/native/month-year.dark.png +0 -0
- package/registry/examples/date-picker/native/month-year.js +13 -0
- package/registry/examples/date-picker/native/month-year.light.png +0 -0
- package/registry/examples/date-picker/native/month-year.tsx +8 -0
- package/registry/examples/date-picker/native/time-slots.d.ts +4 -0
- package/registry/examples/date-picker/native/time-slots.dark.png +0 -0
- package/registry/examples/date-picker/native/time-slots.js +15 -0
- package/registry/examples/date-picker/native/time-slots.light.png +0 -0
- package/registry/examples/date-picker/native/time-slots.tsx +15 -0
- package/registry/examples/date-picker/native/time-wheel.d.ts +4 -0
- package/registry/examples/date-picker/native/time-wheel.dark.png +0 -0
- package/registry/examples/date-picker/native/time-wheel.js +13 -0
- package/registry/examples/date-picker/native/time-wheel.light.png +0 -0
- package/registry/examples/date-picker/native/time-wheel.tsx +8 -0
- package/registry/examples/date-picker/time-slots.d.ts +4 -0
- package/registry/examples/date-picker/time-slots.js +26 -0
- package/registry/examples/date-picker/time-slots.tsx +25 -0
- package/registry/examples/date-picker/time-wheel.d.ts +4 -0
- package/registry/examples/date-picker/time-wheel.js +13 -0
- package/registry/examples/date-picker/time-wheel.tsx +10 -0
- package/registry/examples/date-picker/wheel.d.ts +4 -0
- package/registry/examples/date-picker/wheel.js +14 -0
- package/registry/examples/date-picker/wheel.tsx +16 -0
- package/registry/examples/field/native/default.d.ts +4 -0
- package/registry/examples/field/native/default.dark.png +0 -0
- package/registry/examples/field/native/default.js +29 -0
- package/registry/examples/field/native/default.light.png +0 -0
- package/registry/examples/field/native/default.tsx +23 -0
- package/registry/examples/form/amount.d.ts +4 -0
- package/registry/examples/form/amount.js +30 -0
- package/registry/examples/form/amount.tsx +36 -0
- package/registry/examples/form/audience.d.ts +4 -0
- package/registry/examples/form/audience.js +29 -0
- package/registry/examples/form/audience.tsx +31 -0
- package/registry/examples/form/native/amount.d.ts +4 -0
- package/registry/examples/form/native/amount.dark.png +0 -0
- package/registry/examples/form/native/amount.js +26 -0
- package/registry/examples/form/native/amount.light.png +0 -0
- package/registry/examples/form/native/amount.tsx +30 -0
- package/registry/examples/form/native/audience.d.ts +4 -0
- package/registry/examples/form/native/audience.dark.png +0 -0
- package/registry/examples/form/native/audience.js +24 -0
- package/registry/examples/form/native/audience.light.png +0 -0
- package/registry/examples/form/native/audience.tsx +24 -0
- package/registry/examples/form/native/profile.d.ts +4 -0
- package/registry/examples/form/native/profile.dark.png +0 -0
- package/registry/examples/form/native/profile.js +33 -0
- package/registry/examples/form/native/profile.light.png +0 -0
- package/registry/examples/form/native/profile.tsx +36 -0
- package/registry/examples/form/native/settings.d.ts +4 -0
- package/registry/examples/form/native/settings.dark.png +0 -0
- package/registry/examples/form/native/settings.js +25 -0
- package/registry/examples/form/native/settings.light.png +0 -0
- package/registry/examples/form/native/settings.tsx +23 -0
- package/registry/examples/form/native/sign-up.d.ts +4 -0
- package/registry/examples/form/native/sign-up.dark.png +0 -0
- package/registry/examples/form/native/sign-up.js +38 -0
- package/registry/examples/form/native/sign-up.light.png +0 -0
- package/registry/examples/form/native/sign-up.tsx +34 -0
- package/registry/examples/form/native/upload.d.ts +4 -0
- package/registry/examples/form/native/upload.dark.png +0 -0
- package/registry/examples/form/native/upload.js +23 -0
- package/registry/examples/form/native/upload.light.png +0 -0
- package/registry/examples/form/native/upload.tsx +22 -0
- package/registry/examples/form/profile.d.ts +4 -0
- package/registry/examples/form/profile.js +35 -0
- package/registry/examples/form/profile.tsx +40 -0
- package/registry/examples/form/report.d.ts +4 -0
- package/registry/examples/form/report.js +30 -0
- package/registry/examples/form/report.tsx +30 -0
- package/registry/examples/form/settings.d.ts +4 -0
- package/registry/examples/form/settings.js +30 -0
- package/registry/examples/form/settings.tsx +31 -0
- package/registry/examples/form/sign-up.d.ts +4 -0
- package/registry/examples/form/sign-up.js +38 -0
- package/registry/examples/form/sign-up.tsx +37 -0
- package/registry/examples/form/upload.d.ts +4 -0
- package/registry/examples/form/upload.js +32 -0
- package/registry/examples/form/upload.tsx +32 -0
- package/registry/field.json +104 -4
- package/registry/file-upload.json +1 -1
- package/registry/filter-bar.json +1 -1
- package/registry/form.json +2940 -0
- package/registry/gallery-page.json +1 -1
- package/registry/glow-card.json +1 -1
- package/registry/goal-gauge.json +1 -1
- package/registry/how-it-works-page.json +1 -1
- package/registry/image-cropper.json +1 -1
- package/registry/index.json +43 -10
- package/registry/iso.json +1 -1
- package/registry/kbd.json +1 -1
- package/registry/landing-page.json +1 -1
- package/registry/landscape.json +1 -1
- package/registry/legal-page.json +1 -1
- package/registry/link-card.json +1 -1
- package/registry/loader.json +1 -1
- package/registry/longform-page.json +1 -1
- package/registry/marketing-layout.json +1 -1
- package/registry/marquee.json +1 -1
- package/registry/mascot-quote.json +1 -1
- package/registry/mascot-state.json +2 -2
- package/registry/mascots.json +2 -2
- package/registry/media-capture.json +1 -1
- package/registry/meter.json +1 -1
- package/registry/nav-bar.json +1 -1
- package/registry/number-ticker.json +1 -1
- package/registry/page-header.json +1 -1
- package/registry/page-hero.json +1 -1
- package/registry/page.json +1 -1
- package/registry/pagination.json +1 -1
- package/registry/pointer-light.json +1 -1
- package/registry/popover.json +1 -1
- package/registry/pricing-page.json +1 -1
- package/registry/product-page.json +1 -1
- package/registry/profile-card.json +1 -1
- package/registry/progress.json +1 -1
- package/registry/promise-card.json +1 -1
- package/registry/radio-group.json +1 -1
- package/registry/scroll-stage.json +1 -1
- package/registry/search-field.json +1 -1
- package/registry/section-backdrop.json +1 -1
- package/registry/separator.json +1 -1
- package/registry/settings-layout.json +1 -1
- package/registry/skeleton.json +1 -1
- package/registry/slider.json +1 -1
- package/registry/slot.json +1 -1
- package/registry/spinner.json +1 -1
- package/registry/split-view.json +1 -1
- package/registry/stage-banner.json +1 -1
- package/registry/stat.json +1 -1
- package/registry/status-page.json +1 -1
- package/registry/stepper.json +1 -1
- package/registry/switch.json +1 -1
- package/registry/tabs.json +1 -1
- package/registry/tag-input.json +1 -1
- package/registry/terminal-replay.json +1 -1
- package/registry/theme.json +8 -1
- package/registry/toast.json +1 -1
- package/registry/toc.json +1 -1
- package/registry/tooltip.json +1 -1
- package/registry/video-player.json +1 -1
- package/registry/video-trimmer.json +1 -1
- package/registry/virtual.json +1 -1
- package/skills/date-picker/SKILL.md +43 -0
- package/skills/form/SKILL.md +44 -0
- package/dist/chunk-2USC2YT6.js +0 -17
- package/dist/chunk-2USC2YT6.js.map +0 -1
- package/dist/chunk-5AXLA4LK.js +0 -13
- package/dist/chunk-5AXLA4LK.js.map +0 -1
- package/dist/chunk-FJDSLNXK.js +0 -11
- package/dist/chunk-FJDSLNXK.js.map +0 -1
- package/dist/chunk-KNBYZGSQ.js +0 -11
- package/dist/chunk-KNBYZGSQ.js.map +0 -1
- package/dist/chunk-VCAAHCHL.js +0 -17
- package/dist/chunk-VCAAHCHL.js.map +0 -1
- /package/dist/{chunk-OGJI5X4W.js.map → chunk-2PUDI7JS.js.map} +0 -0
- /package/dist/{chunk-K3SWZXYT.js.map → chunk-DGQMDJXK.js.map} +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// src/form/errors.ts
|
|
2
|
+
var KEY = /^[\w-]+(\.[\w-]+)+$/;
|
|
3
|
+
function pathName(path) {
|
|
4
|
+
let name = "";
|
|
5
|
+
for (const part of path ?? []) {
|
|
6
|
+
const key = typeof part === "object" && part !== null ? part.key : part;
|
|
7
|
+
if (typeof key === "number") name += `[${key}]`;
|
|
8
|
+
else name += name ? `.${String(key)}` : String(key);
|
|
9
|
+
}
|
|
10
|
+
return name;
|
|
11
|
+
}
|
|
12
|
+
var num = (value) => value === void 0 ? void 0 : Number(value);
|
|
13
|
+
function issueError(issue, prefix = "form.errors") {
|
|
14
|
+
if (KEY.test(issue.message)) return { key: issue.message };
|
|
15
|
+
const key = (code, values) => ({ key: `${prefix}.${code}`, ...values ? { values } : {} });
|
|
16
|
+
const collection = issue.origin === "array" || issue.origin === "set";
|
|
17
|
+
switch (issue.code) {
|
|
18
|
+
case "invalid_type":
|
|
19
|
+
return issue.input === void 0 || issue.input === null || issue.input === "" ? key("required") : issue.expected === "number" ? key("invalidNumber") : issue.expected === "date" ? key("invalidDate") : key("invalid");
|
|
20
|
+
case "too_small": {
|
|
21
|
+
const minimum = num(issue.minimum);
|
|
22
|
+
if (issue.origin === "string") return minimum <= 1 ? key("required") : key("tooShort", { minimum });
|
|
23
|
+
if (collection) return minimum <= 1 ? key("required") : key("tooFew", { minimum });
|
|
24
|
+
return key("tooSmall", { minimum });
|
|
25
|
+
}
|
|
26
|
+
case "too_big": {
|
|
27
|
+
const maximum = num(issue.maximum);
|
|
28
|
+
return issue.origin === "string" ? key("tooLong", { maximum }) : collection ? key("tooMany", { maximum }) : key("tooBig", { maximum });
|
|
29
|
+
}
|
|
30
|
+
case "invalid_format":
|
|
31
|
+
if (issue.format === "email") return key("invalidEmail");
|
|
32
|
+
if (issue.format === "url") return key("invalidUrl");
|
|
33
|
+
if (issue.format === "date" || issue.format === "datetime") return key("invalidDate");
|
|
34
|
+
return key("invalidFormat");
|
|
35
|
+
case "invalid_value":
|
|
36
|
+
return issue.input === false || issue.input === void 0 ? key("required") : key("invalidChoice");
|
|
37
|
+
case "invalid_union":
|
|
38
|
+
case "invalid_key":
|
|
39
|
+
case "invalid_element":
|
|
40
|
+
return key("invalidChoice");
|
|
41
|
+
default:
|
|
42
|
+
return key("invalid");
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function issuesToFields(issues, prefix) {
|
|
46
|
+
const fields = {};
|
|
47
|
+
for (const issue of issues) {
|
|
48
|
+
const name = pathName(issue.path);
|
|
49
|
+
if (!(name in fields)) fields[name] = { ...issueError(issue, prefix), source: "schema" };
|
|
50
|
+
}
|
|
51
|
+
return fields;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/form/server.ts
|
|
55
|
+
async function validateForm(schema, input, options = {}) {
|
|
56
|
+
const result = await schema["~standard"].validate(input);
|
|
57
|
+
if (!result.issues?.length) return { ok: true, value: result.value };
|
|
58
|
+
const fields = issuesToFields(result.issues, options.prefix);
|
|
59
|
+
const general = fields[""];
|
|
60
|
+
delete fields[""];
|
|
61
|
+
return { ok: false, problem: { status: 422, code: general ? general.key : "validation-failed", fields } };
|
|
62
|
+
}
|
|
63
|
+
function formProblem(error) {
|
|
64
|
+
if (error && typeof error === "object") {
|
|
65
|
+
const e = error;
|
|
66
|
+
if (typeof e.status === "number" || typeof e.code === "string") {
|
|
67
|
+
return {
|
|
68
|
+
...typeof e.status === "number" ? { status: e.status } : {},
|
|
69
|
+
...typeof e.code === "string" ? { code: e.code } : {},
|
|
70
|
+
...typeof e.detail === "string" ? { detail: e.detail } : {},
|
|
71
|
+
...e.fields && typeof e.fields === "object" ? { fields: JSON.parse(JSON.stringify(e.fields)) } : {},
|
|
72
|
+
...typeof e.retryAt === "number" ? { retryAt: e.retryAt } : {}
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { status: 500, code: "server" };
|
|
77
|
+
}
|
|
78
|
+
export {
|
|
79
|
+
formProblem,
|
|
80
|
+
validateForm
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/form/errors.ts","../../src/form/server.ts"],"sourcesContent":["// Platform-neutral and server-safe: no React, no DOM, no React Native. The errors of a Krizaka form — a schema's issues,\n// a server's Problem Details, an availability check — as one shape, `FormFieldError`, whose words come from a key of the\n// app's catalogue (`form.errors.tooShort`), never from the validation engine's English.\n\n/** A value a message placeholder takes (`{minimum}`, `{suggestion}`). */\nexport type FormMessageValue = string | number;\n\n/** An error of a field or of the whole form: the key of its words in the app's catalogue, and what fills them in. */\nexport interface FormFieldError {\n /** The i18n key: `form.errors.required`, or the key a schema names (`z.string().min(3, \"signUp.username.short\")`). */\n key: string;\n /** The placeholders of the message (`{ minimum: 3 }`). */\n values?: Readonly<Record<string, FormMessageValue>>;\n /** The server's own words, in English: shown only when the catalogue has no message for the key. */\n message?: string;\n /** A value to offer instead (a free username): a button under the error sets it. Its label is passed translated. */\n suggestion?: { readonly label: string; readonly value: unknown };\n /** Shown at once, while the person types (an availability check), not only once they leave the field. */\n immediate?: boolean;\n /** Where it comes from: the schema (default), a field's check, or the server's answer (kept until the field changes). */\n source?: \"schema\" | \"check\" | \"server\";\n}\n\n/** The English defaults of the generic errors, by code — the words a form shows when the app's catalogue has none. */\nexport const formMessages: Readonly<Record<string, string>> = {\n required: \"This field is required.\",\n tooShort: \"Use at least {minimum} characters.\",\n tooLong: \"Use at most {maximum} characters.\",\n tooSmall: \"Enter {minimum} or more.\",\n tooBig: \"Enter {maximum} or less.\",\n tooFew: \"Choose at least {minimum}.\",\n tooMany: \"Choose at most {maximum}.\",\n invalidEmail: \"Enter an e-mail address, like name@example.com.\",\n invalidUrl: \"Enter a web address, like https://example.com.\",\n invalidDate: \"Enter a real date.\",\n invalidNumber: \"Enter a number.\",\n invalidFormat: \"Check the format of this field.\",\n invalidChoice: \"Choose one of the options.\",\n invalid: \"Check this field.\",\n // The whole form (a server's answer without fields): the codes of @krizaka/query's KrizakaApiError.\n network: \"No connection. Check your network and try again.\",\n \"rate-limited\": \"Too many tries. Wait a moment and try again.\",\n unauthorized: \"Your session has ended. Sign in again.\",\n forbidden: \"You are not allowed to do this.\",\n conflict: \"This was changed meanwhile. Reload and try again.\",\n \"validation-failed\": \"Some fields need your attention.\",\n unavailable: \"The service is unavailable for a moment. Try again shortly.\",\n server: \"Something went wrong on our side. Try again.\",\n};\n\n/** How a form finds its words: the app's translate function, and the prefixes of its keys. */\nexport interface FormI18n {\n /** The app's translate function (`(key, values) => t(key, values)`): a string, or `undefined` when the key is missing. */\n t?: (key: string, values?: Readonly<Record<string, FormMessageValue>>) => string | undefined;\n /** The prefix of the generic field errors. Default `form.errors` (→ `form.errors.tooShort`). */\n prefix?: string;\n /** The prefix of the server's codes for the whole form, as `KrizakaApiError.messageKey()`. Default `apiErrors`. */\n apiPrefix?: string;\n /** The locale of numbers, money and dates. Default `en`. */\n locale?: string;\n}\n\nconst KEY = /^[\\w-]+(\\.[\\w-]+)+$/;\n\nconst fill = (text: string, values?: Readonly<Record<string, FormMessageValue>>) =>\n values ? text.replace(/\\{(\\w+)\\}/g, (all, name: string) => (values[name] === undefined ? all : String(values[name]))) : text;\n\n/** The words of an error: the app's catalogue, else the English default of its code, else the server's words, else the key. */\nexport function formMessage(error: FormFieldError | string, i18n: FormI18n = {}): string {\n const e = typeof error === \"string\" ? { key: error } : error;\n const translated = i18n.t?.(e.key, e.values);\n if (translated && translated !== e.key) return translated;\n const code = e.key.slice(e.key.lastIndexOf(\".\") + 1);\n const fallback = formMessages[code];\n // The server's words beat a generic default (a refusal without a code), never a default for its own code.\n if (e.message && (code === \"invalid\" || !fallback)) return e.message;\n if (fallback && (e.key.startsWith(`${i18n.prefix ?? \"form.errors\"}.`) || e.key.startsWith(`${i18n.apiPrefix ?? \"apiErrors\"}.`))) return fill(fallback, e.values);\n return e.message ?? (KEY.test(e.key) ? fill(fallback ?? formMessages.invalid!, e.values) : e.key);\n}\n\n/** One issue of a Standard Schema (zod 4, valibot, arktype): zod adds its `code` and limits, read when present. */\nexport interface SchemaIssue {\n readonly message: string;\n readonly path?: ReadonlyArray<PropertyKey | { readonly key: PropertyKey }>;\n readonly code?: string;\n readonly minimum?: number | bigint;\n readonly maximum?: number | bigint;\n readonly origin?: string;\n readonly format?: string;\n readonly input?: unknown;\n readonly expected?: string;\n}\n\n/** A field's name from an issue's path, as the form writes it: `links[0].url`. */\nexport function pathName(path: SchemaIssue[\"path\"]): string {\n let name = \"\";\n for (const part of path ?? []) {\n const key = typeof part === \"object\" && part !== null ? part.key : part;\n if (typeof key === \"number\") name += `[${key}]`;\n else name += name ? `.${String(key)}` : String(key);\n }\n return name;\n}\n\nconst num = (value: number | bigint | undefined) => (value === undefined ? undefined : Number(value));\n\n/** The key and values of an issue: the schema's own key when it names one, else the generic code of what failed. */\nexport function issueError(issue: SchemaIssue, prefix = \"form.errors\"): FormFieldError {\n if (KEY.test(issue.message)) return { key: issue.message };\n const key = (code: string, values?: Record<string, FormMessageValue>) => ({ key: `${prefix}.${code}`, ...(values ? { values } : {}) });\n const collection = issue.origin === \"array\" || issue.origin === \"set\";\n switch (issue.code) {\n case \"invalid_type\":\n return issue.input === undefined || issue.input === null || issue.input === \"\" ? key(\"required\") : issue.expected === \"number\" ? key(\"invalidNumber\") : issue.expected === \"date\" ? key(\"invalidDate\") : key(\"invalid\");\n case \"too_small\": {\n const minimum = num(issue.minimum)!;\n if (issue.origin === \"string\") return minimum <= 1 ? key(\"required\") : key(\"tooShort\", { minimum });\n if (collection) return minimum <= 1 ? key(\"required\") : key(\"tooFew\", { minimum });\n return key(\"tooSmall\", { minimum });\n }\n case \"too_big\": {\n const maximum = num(issue.maximum)!;\n return issue.origin === \"string\" ? key(\"tooLong\", { maximum }) : collection ? key(\"tooMany\", { maximum }) : key(\"tooBig\", { maximum });\n }\n case \"invalid_format\":\n if (issue.format === \"email\") return key(\"invalidEmail\");\n if (issue.format === \"url\") return key(\"invalidUrl\");\n if (issue.format === \"date\" || issue.format === \"datetime\") return key(\"invalidDate\");\n return key(\"invalidFormat\");\n case \"invalid_value\":\n // `z.literal(true)`: a box that must be ticked.\n return issue.input === false || issue.input === undefined ? key(\"required\") : key(\"invalidChoice\");\n case \"invalid_union\":\n case \"invalid_key\":\n case \"invalid_element\":\n return key(\"invalidChoice\");\n default:\n return key(\"invalid\");\n }\n}\n\n/** The first error of each field of a schema's issues (an issue without a path is the whole form's: `\"\"`). */\nexport function issuesToFields(issues: readonly SchemaIssue[], prefix?: string): Record<string, FormFieldError> {\n const fields: Record<string, FormFieldError> = {};\n for (const issue of issues) {\n const name = pathName(issue.path);\n if (!(name in fields)) fields[name] = { ...issueError(issue, prefix), source: \"schema\" };\n }\n return fields;\n}\n\n/**\n * A refusal the form shows, serialisable (a Server Action returns it): the shape of `KrizakaApiError` of @krizaka/query —\n * a `KrizakaApiError` *is* a `FormProblem`. `fields` go under their fields, the rest above the submit button.\n */\nexport interface FormProblem {\n /** The HTTP status (0 when no response arrived). */\n status?: number;\n /** The stable code of the refusal (`rate-limited`, `conflict`, `ACCOUNT_EXISTS`): the key of its words. */\n code?: string;\n /** The server's sentence, in English: shown only when the catalogue has no message for the code. */\n detail?: string;\n /** The refused fields, by name: a server's `{ code, message }`, or an error the form already knows (`key`). */\n fields?: Readonly<Record<string, { code?: string; message: string } | FormFieldError | string>>;\n /** Epoch ms before which the same call is refused (a 429). */\n retryAt?: number;\n}\n\n/** True for what a form can read as a problem: a `KrizakaApiError`, a `FormProblem`, an `{ ok: false, problem }` result. */\nexport function isFormProblem(value: unknown): value is FormProblem {\n return typeof value === \"object\" && value !== null && (\"fields\" in value || \"code\" in value || \"status\" in value);\n}\n\n/** A problem as field errors (by name) and the whole form's error (or `null` when every refusal has its field). */\nexport function problemToErrors(\n problem: FormProblem,\n i18n: FormI18n = {},\n known?: (name: string) => boolean,\n codeFields: Readonly<Record<string, string>> = {},\n): { fields: Record<string, FormFieldError>; form: FormFieldError | null } {\n const prefix = i18n.prefix ?? \"form.errors\";\n const fields: Record<string, FormFieldError> = {};\n let unplaced = false;\n // A refusal named by a code that belongs to a field (`UNDERAGE` → `dateOfBirth`): under that field, in its words.\n const owner = problem.code ? codeFields[problem.code] : undefined;\n if (owner && (!known || known(owner)) && !problem.fields?.[owner]) {\n fields[owner] = { key: `${prefix}.${problem.code}`, ...(problem.detail ? { message: problem.detail } : {}), source: \"server\" };\n if (!problem.fields || Object.keys(problem.fields).length === 0) return { fields, form: null };\n }\n for (const [name, refusal] of Object.entries(problem.fields ?? {})) {\n if (known && !known(name)) {\n unplaced = true;\n continue;\n }\n const error: FormFieldError =\n typeof refusal === \"string\"\n ? { key: refusal }\n : \"key\" in refusal\n ? refusal\n : { key: refusal.code ? (KEY.test(refusal.code) ? refusal.code : `${prefix}.${refusal.code}`) : `${prefix}.invalid`, message: refusal.message };\n fields[name] = { ...error, source: \"server\" };\n }\n const placed = Object.keys(fields).length > 0;\n if (placed && !unplaced) return { fields, form: null };\n const code = problem.code ?? (problem.status === 0 ? \"network\" : problem.status && problem.status >= 500 ? \"server\" : \"invalid\");\n const retryIn = problem.retryAt ? Math.max(1, Math.ceil((problem.retryAt - Date.now()) / 1000)) : undefined;\n return {\n fields,\n form: { key: KEY.test(code) ? code : `${i18n.apiPrefix ?? \"apiErrors\"}.${code}`, ...(problem.detail ? { message: problem.detail } : {}), ...(retryIn ? { values: { seconds: retryIn } } : {}), source: \"server\" },\n };\n}\n","/**\n * @krizaka/ui/form/server — the server side of a Krizaka form, for a Server Action or a route handler (no \"use client\",\n * no React): `validateForm` parses with the form's own schema and answers the errors with the same keys the form shows,\n * `formProblem` turns a thrown error (a `KrizakaApiError` of @krizaka/query) into the serialisable refusal a form reads.\n */\nimport { type FormFieldError, type FormProblem, issuesToFields, type SchemaIssue } from \"./errors\";\n\nexport type { FormFieldError, FormProblem } from \"./errors\";\n\n/** What a Server Action answers a form: done (with what it made), or a refusal (`fields` under their fields). */\nexport type FormActionResult<T = unknown> = { ok: true; data?: T } | { ok: false; problem: FormProblem };\n\ntype Schema<O> = { readonly \"~standard\": { validate: (value: unknown) => { value?: O; issues?: readonly SchemaIssue[] } | Promise<{ value?: O; issues?: readonly SchemaIssue[] }> } };\n\n/**\n * Parses what a form sent with its schema (never trust the client): the value, or a `validation-failed` problem whose\n * fields carry the keys the form would show (`form.errors.tooShort`, the schema's own keys).\n */\nexport async function validateForm<O>(schema: Schema<O>, input: unknown, options: { prefix?: string } = {}): Promise<{ ok: true; value: O } | { ok: false; problem: FormProblem }> {\n const result = await schema[\"~standard\"].validate(input);\n if (!result.issues?.length) return { ok: true, value: result.value as O };\n const fields: Record<string, FormFieldError> = issuesToFields(result.issues, options.prefix);\n const general = fields[\"\"];\n delete fields[\"\"];\n return { ok: false, problem: { status: 422, code: general ? general.key : \"validation-failed\", fields } };\n}\n\n/** A thrown error as a refusal a form reads — a `KrizakaApiError` keeps its status, code, fields and retry time. */\nexport function formProblem(error: unknown): FormProblem {\n if (error && typeof error === \"object\") {\n const e = error as { status?: unknown; code?: unknown; detail?: unknown; fields?: unknown; retryAt?: unknown };\n if (typeof e.status === \"number\" || typeof e.code === \"string\") {\n return {\n ...(typeof e.status === \"number\" ? { status: e.status } : {}),\n ...(typeof e.code === \"string\" ? { code: e.code } : {}),\n ...(typeof e.detail === \"string\" ? { detail: e.detail } : {}),\n ...(e.fields && typeof e.fields === \"object\" ? { fields: JSON.parse(JSON.stringify(e.fields)) as FormProblem[\"fields\"] } : {}),\n ...(typeof e.retryAt === \"number\" ? { retryAt: e.retryAt } : {}),\n };\n }\n }\n return { status: 500, code: \"server\" };\n}\n"],"mappings":";AA8DA,IAAM,MAAM;AAgCL,SAAS,SAAS,MAAmC;AAC1D,MAAI,OAAO;AACX,aAAW,QAAQ,QAAQ,CAAC,GAAG;AAC7B,UAAM,MAAM,OAAO,SAAS,YAAY,SAAS,OAAO,KAAK,MAAM;AACnE,QAAI,OAAO,QAAQ,SAAU,SAAQ,IAAI,GAAG;AAAA,QACvC,SAAQ,OAAO,IAAI,OAAO,GAAG,CAAC,KAAK,OAAO,GAAG;AAAA,EACpD;AACA,SAAO;AACT;AAEA,IAAM,MAAM,CAAC,UAAwC,UAAU,SAAY,SAAY,OAAO,KAAK;AAG5F,SAAS,WAAW,OAAoB,SAAS,eAA+B;AACrF,MAAI,IAAI,KAAK,MAAM,OAAO,EAAG,QAAO,EAAE,KAAK,MAAM,QAAQ;AACzD,QAAM,MAAM,CAAC,MAAc,YAA+C,EAAE,KAAK,GAAG,MAAM,IAAI,IAAI,IAAI,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC,EAAG;AACpI,QAAM,aAAa,MAAM,WAAW,WAAW,MAAM,WAAW;AAChE,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO,MAAM,UAAU,UAAa,MAAM,UAAU,QAAQ,MAAM,UAAU,KAAK,IAAI,UAAU,IAAI,MAAM,aAAa,WAAW,IAAI,eAAe,IAAI,MAAM,aAAa,SAAS,IAAI,aAAa,IAAI,IAAI,SAAS;AAAA,IACxN,KAAK,aAAa;AAChB,YAAM,UAAU,IAAI,MAAM,OAAO;AACjC,UAAI,MAAM,WAAW,SAAU,QAAO,WAAW,IAAI,IAAI,UAAU,IAAI,IAAI,YAAY,EAAE,QAAQ,CAAC;AAClG,UAAI,WAAY,QAAO,WAAW,IAAI,IAAI,UAAU,IAAI,IAAI,UAAU,EAAE,QAAQ,CAAC;AACjF,aAAO,IAAI,YAAY,EAAE,QAAQ,CAAC;AAAA,IACpC;AAAA,IACA,KAAK,WAAW;AACd,YAAM,UAAU,IAAI,MAAM,OAAO;AACjC,aAAO,MAAM,WAAW,WAAW,IAAI,WAAW,EAAE,QAAQ,CAAC,IAAI,aAAa,IAAI,WAAW,EAAE,QAAQ,CAAC,IAAI,IAAI,UAAU,EAAE,QAAQ,CAAC;AAAA,IACvI;AAAA,IACA,KAAK;AACH,UAAI,MAAM,WAAW,QAAS,QAAO,IAAI,cAAc;AACvD,UAAI,MAAM,WAAW,MAAO,QAAO,IAAI,YAAY;AACnD,UAAI,MAAM,WAAW,UAAU,MAAM,WAAW,WAAY,QAAO,IAAI,aAAa;AACpF,aAAO,IAAI,eAAe;AAAA,IAC5B,KAAK;AAEH,aAAO,MAAM,UAAU,SAAS,MAAM,UAAU,SAAY,IAAI,UAAU,IAAI,IAAI,eAAe;AAAA,IACnG,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AACH,aAAO,IAAI,eAAe;AAAA,IAC5B;AACE,aAAO,IAAI,SAAS;AAAA,EACxB;AACF;AAGO,SAAS,eAAe,QAAgC,QAAiD;AAC9G,QAAM,SAAyC,CAAC;AAChD,aAAW,SAAS,QAAQ;AAC1B,UAAM,OAAO,SAAS,MAAM,IAAI;AAChC,QAAI,EAAE,QAAQ,QAAS,QAAO,IAAI,IAAI,EAAE,GAAG,WAAW,OAAO,MAAM,GAAG,QAAQ,SAAS;AAAA,EACzF;AACA,SAAO;AACT;;;ACnIA,eAAsB,aAAgB,QAAmB,OAAgB,UAA+B,CAAC,GAA0E;AACjL,QAAM,SAAS,MAAM,OAAO,WAAW,EAAE,SAAS,KAAK;AACvD,MAAI,CAAC,OAAO,QAAQ,OAAQ,QAAO,EAAE,IAAI,MAAM,OAAO,OAAO,MAAW;AACxE,QAAM,SAAyC,eAAe,OAAO,QAAQ,QAAQ,MAAM;AAC3F,QAAM,UAAU,OAAO,EAAE;AACzB,SAAO,OAAO,EAAE;AAChB,SAAO,EAAE,IAAI,OAAO,SAAS,EAAE,QAAQ,KAAK,MAAM,UAAU,QAAQ,MAAM,qBAAqB,OAAO,EAAE;AAC1G;AAGO,SAAS,YAAY,OAA6B;AACvD,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,IAAI;AACV,QAAI,OAAO,EAAE,WAAW,YAAY,OAAO,EAAE,SAAS,UAAU;AAC9D,aAAO;AAAA,QACL,GAAI,OAAO,EAAE,WAAW,WAAW,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,QAC3D,GAAI,OAAO,EAAE,SAAS,WAAW,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;AAAA,QACrD,GAAI,OAAO,EAAE,WAAW,WAAW,EAAE,QAAQ,EAAE,OAAO,IAAI,CAAC;AAAA,QAC3D,GAAI,EAAE,UAAU,OAAO,EAAE,WAAW,WAAW,EAAE,QAAQ,KAAK,MAAM,KAAK,UAAU,EAAE,MAAM,CAAC,EAA2B,IAAI,CAAC;AAAA,QAC5H,GAAI,OAAO,EAAE,YAAY,WAAW,EAAE,SAAS,EAAE,QAAQ,IAAI,CAAC;AAAA,MAChE;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,QAAQ,KAAK,MAAM,SAAS;AACvC;","names":[]}
|
package/dist/form.d.ts
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
import { DeepKeys, AnyFormApi, AnyFieldApi, StandardSchemaV1 } from '@tanstack/react-form';
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import { ButtonProps } from '@krizaka/ui/button';
|
|
4
|
+
import { ComboboxOption, ComboboxLabels } from '@krizaka/ui/combobox';
|
|
5
|
+
import { DatePickerLabels } from '@krizaka/ui/date-picker';
|
|
6
|
+
import { Dropzone } from '@krizaka/ui/file-upload';
|
|
7
|
+
import { TagInputProps } from '@krizaka/ui/tag-input';
|
|
8
|
+
|
|
9
|
+
/** A part of a typed date. */
|
|
10
|
+
type DatePart = "day" | "month" | "year";
|
|
11
|
+
/** The order of day, month and year in a locale (`en-US` → month, day, year; `fr` → day, month, year). */
|
|
12
|
+
declare function datePartsOrder(locale: string): DatePart[];
|
|
13
|
+
/** The ISO-ordered value of what was typed: `""` when nothing is, `YYYY-MM-DD` otherwise (not necessarily a real date). */
|
|
14
|
+
declare function composeDate(parts: Readonly<Record<DatePart, string>>): string;
|
|
15
|
+
/** True for a real calendar date written `YYYY-MM-DD` (not `2001-02-30`). */
|
|
16
|
+
declare function isRealDate(value: string): boolean;
|
|
17
|
+
/** The latest date of birth of someone `age` years old today (`YYYY-MM-DD`): an age gate's `max`, `value <= latestBirthDate(18)`. */
|
|
18
|
+
declare function latestBirthDate(age: number, today?: Date): string;
|
|
19
|
+
|
|
20
|
+
/** A value a message placeholder takes (`{minimum}`, `{suggestion}`). */
|
|
21
|
+
type FormMessageValue = string | number;
|
|
22
|
+
/** An error of a field or of the whole form: the key of its words in the app's catalogue, and what fills them in. */
|
|
23
|
+
interface FormFieldError {
|
|
24
|
+
/** The i18n key: `form.errors.required`, or the key a schema names (`z.string().min(3, "signUp.username.short")`). */
|
|
25
|
+
key: string;
|
|
26
|
+
/** The placeholders of the message (`{ minimum: 3 }`). */
|
|
27
|
+
values?: Readonly<Record<string, FormMessageValue>>;
|
|
28
|
+
/** The server's own words, in English: shown only when the catalogue has no message for the key. */
|
|
29
|
+
message?: string;
|
|
30
|
+
/** A value to offer instead (a free username): a button under the error sets it. Its label is passed translated. */
|
|
31
|
+
suggestion?: {
|
|
32
|
+
readonly label: string;
|
|
33
|
+
readonly value: unknown;
|
|
34
|
+
};
|
|
35
|
+
/** Shown at once, while the person types (an availability check), not only once they leave the field. */
|
|
36
|
+
immediate?: boolean;
|
|
37
|
+
/** Where it comes from: the schema (default), a field's check, or the server's answer (kept until the field changes). */
|
|
38
|
+
source?: "schema" | "check" | "server";
|
|
39
|
+
}
|
|
40
|
+
/** The English defaults of the generic errors, by code — the words a form shows when the app's catalogue has none. */
|
|
41
|
+
declare const formMessages: Readonly<Record<string, string>>;
|
|
42
|
+
/** How a form finds its words: the app's translate function, and the prefixes of its keys. */
|
|
43
|
+
interface FormI18n {
|
|
44
|
+
/** The app's translate function (`(key, values) => t(key, values)`): a string, or `undefined` when the key is missing. */
|
|
45
|
+
t?: (key: string, values?: Readonly<Record<string, FormMessageValue>>) => string | undefined;
|
|
46
|
+
/** The prefix of the generic field errors. Default `form.errors` (→ `form.errors.tooShort`). */
|
|
47
|
+
prefix?: string;
|
|
48
|
+
/** The prefix of the server's codes for the whole form, as `KrizakaApiError.messageKey()`. Default `apiErrors`. */
|
|
49
|
+
apiPrefix?: string;
|
|
50
|
+
/** The locale of numbers, money and dates. Default `en`. */
|
|
51
|
+
locale?: string;
|
|
52
|
+
}
|
|
53
|
+
/** The words of an error: the app's catalogue, else the English default of its code, else the server's words, else the key. */
|
|
54
|
+
declare function formMessage(error: FormFieldError | string, i18n?: FormI18n): string;
|
|
55
|
+
/** One issue of a Standard Schema (zod 4, valibot, arktype): zod adds its `code` and limits, read when present. */
|
|
56
|
+
interface SchemaIssue {
|
|
57
|
+
readonly message: string;
|
|
58
|
+
readonly path?: ReadonlyArray<PropertyKey | {
|
|
59
|
+
readonly key: PropertyKey;
|
|
60
|
+
}>;
|
|
61
|
+
readonly code?: string;
|
|
62
|
+
readonly minimum?: number | bigint;
|
|
63
|
+
readonly maximum?: number | bigint;
|
|
64
|
+
readonly origin?: string;
|
|
65
|
+
readonly format?: string;
|
|
66
|
+
readonly input?: unknown;
|
|
67
|
+
readonly expected?: string;
|
|
68
|
+
}
|
|
69
|
+
/** The key and values of an issue: the schema's own key when it names one, else the generic code of what failed. */
|
|
70
|
+
declare function issueError(issue: SchemaIssue, prefix?: string): FormFieldError;
|
|
71
|
+
/** The first error of each field of a schema's issues (an issue without a path is the whole form's: `""`). */
|
|
72
|
+
declare function issuesToFields(issues: readonly SchemaIssue[], prefix?: string): Record<string, FormFieldError>;
|
|
73
|
+
/**
|
|
74
|
+
* A refusal the form shows, serialisable (a Server Action returns it): the shape of `KrizakaApiError` of @krizaka/query —
|
|
75
|
+
* a `KrizakaApiError` *is* a `FormProblem`. `fields` go under their fields, the rest above the submit button.
|
|
76
|
+
*/
|
|
77
|
+
interface FormProblem {
|
|
78
|
+
/** The HTTP status (0 when no response arrived). */
|
|
79
|
+
status?: number;
|
|
80
|
+
/** The stable code of the refusal (`rate-limited`, `conflict`, `ACCOUNT_EXISTS`): the key of its words. */
|
|
81
|
+
code?: string;
|
|
82
|
+
/** The server's sentence, in English: shown only when the catalogue has no message for the code. */
|
|
83
|
+
detail?: string;
|
|
84
|
+
/** The refused fields, by name: a server's `{ code, message }`, or an error the form already knows (`key`). */
|
|
85
|
+
fields?: Readonly<Record<string, {
|
|
86
|
+
code?: string;
|
|
87
|
+
message: string;
|
|
88
|
+
} | FormFieldError | string>>;
|
|
89
|
+
/** Epoch ms before which the same call is refused (a 429). */
|
|
90
|
+
retryAt?: number;
|
|
91
|
+
}
|
|
92
|
+
/** A problem as field errors (by name) and the whole form's error (or `null` when every refusal has its field). */
|
|
93
|
+
declare function problemToErrors(problem: FormProblem, i18n?: FormI18n, known?: (name: string) => boolean, codeFields?: Readonly<Record<string, string>>): {
|
|
94
|
+
fields: Record<string, FormFieldError>;
|
|
95
|
+
form: FormFieldError | null;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
/** A schema the form validates with: any Standard Schema — zod 4 (`z.object`), valibot, arktype. */
|
|
99
|
+
type FormSchema<Input = unknown, Output = Input> = StandardSchemaV1<Input, Output>;
|
|
100
|
+
/** The values a form holds: the schema's input. */
|
|
101
|
+
type FormValues<S> = S extends {
|
|
102
|
+
readonly "~standard": {
|
|
103
|
+
readonly types?: {
|
|
104
|
+
readonly input: infer I;
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
} ? I : never;
|
|
108
|
+
/** The values a form sends: the schema's output (zod transforms applied). */
|
|
109
|
+
type FormOutput<S> = S extends {
|
|
110
|
+
readonly "~standard": {
|
|
111
|
+
readonly types?: {
|
|
112
|
+
readonly output: infer O;
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
} ? O : never;
|
|
116
|
+
/** A field's name: `email`, `address.city`, `links[0].url`. */
|
|
117
|
+
type FieldName<V> = DeepKeys<V> & string;
|
|
118
|
+
/** What a Server Action (or any submit) answers: done, or a refusal the form shows. */
|
|
119
|
+
type FormActionResult<T = unknown> = {
|
|
120
|
+
ok: true;
|
|
121
|
+
data?: T;
|
|
122
|
+
} | {
|
|
123
|
+
ok: false;
|
|
124
|
+
problem: FormProblem;
|
|
125
|
+
};
|
|
126
|
+
/** A value checked on the server as it is typed: an error (or its key), or nothing when the value is fine. */
|
|
127
|
+
type FieldCheck<T = unknown> = (value: T, signal: AbortSignal) => Promise<FormFieldError | string | null | undefined> | FormFieldError | string | null | undefined;
|
|
128
|
+
interface KrizakaFormDefinition<S extends FormSchema> {
|
|
129
|
+
/** The schema of the values (zod 4): the same one the server parses with. */
|
|
130
|
+
schema: S;
|
|
131
|
+
/** The values at first: every field of the schema. */
|
|
132
|
+
defaultValues: FormValues<S>;
|
|
133
|
+
/** The words: the app's translate function, the key prefixes, the locale. */
|
|
134
|
+
i18n?: FormI18n;
|
|
135
|
+
}
|
|
136
|
+
interface UseKrizakaFormOptions<S extends FormSchema, R = unknown> {
|
|
137
|
+
/** Sends the values (the schema's output): a mutation of @krizaka/query, a Server Action. Throw a `KrizakaApiError` or
|
|
138
|
+
* return `{ ok: false, problem }` to refuse: its `fields` go under their fields, the rest above the button. */
|
|
139
|
+
onSubmit?: (values: FormOutput<S>) => R | Promise<R>;
|
|
140
|
+
/** Called after a successful submit with what `onSubmit` returned (`data` of a `{ ok: true, data }`). */
|
|
141
|
+
onSuccess?: (result: R extends FormActionResult<infer T> ? T | undefined : R) => void;
|
|
142
|
+
/** Values that replace the definition's (a profile loaded from the server). */
|
|
143
|
+
defaultValues?: FormValues<S>;
|
|
144
|
+
/** Overrides the definition's words (the translate function of a hook). */
|
|
145
|
+
i18n?: FormI18n;
|
|
146
|
+
/** The server's codes that belong to a field (`{ UNDERAGE: "dateOfBirth", USERNAME_RESERVED: "username" }`): a refusal
|
|
147
|
+
* with that code shows under the field, as `form.errors.<CODE>`, instead of above the button. */
|
|
148
|
+
codeFields?: Readonly<Record<string, string>>;
|
|
149
|
+
}
|
|
150
|
+
/** The state of a form, for its fields and its button. */
|
|
151
|
+
interface FormContextValue {
|
|
152
|
+
/** TanStack's form, through the facade: never import @tanstack/* in a product. */
|
|
153
|
+
readonly api: AnyFormApi;
|
|
154
|
+
readonly i18n: FormI18n;
|
|
155
|
+
/** The whole form's error (a refusal without a field), or null. */
|
|
156
|
+
readonly error: FormFieldError | null;
|
|
157
|
+
/** Sends the form: validates, focuses (web) or announces (native) the first error, else calls `onSubmit`. */
|
|
158
|
+
readonly submit: () => Promise<boolean>;
|
|
159
|
+
}
|
|
160
|
+
/** The form around a field: inside `form.Form`. */
|
|
161
|
+
declare function useFormContext(): FormContextValue;
|
|
162
|
+
interface FieldOptions<T> {
|
|
163
|
+
/** Checks the value on the server as it is typed (a username's availability): debounced, aborted when it changes. */
|
|
164
|
+
validateAsync?: FieldCheck<T>;
|
|
165
|
+
/** The wait, in ms, between the last change and `validateAsync`. Default 350. */
|
|
166
|
+
asyncDebounceMs?: number;
|
|
167
|
+
}
|
|
168
|
+
/** A field as its component draws it: the value, its setter, its error in words, its state. */
|
|
169
|
+
interface KrizakaField<T = unknown> {
|
|
170
|
+
/** The id of the control (`htmlFor` of its label). */
|
|
171
|
+
readonly id: string;
|
|
172
|
+
readonly name: string;
|
|
173
|
+
readonly value: T;
|
|
174
|
+
/** Sets the value (clears a refusal of the server on it). */
|
|
175
|
+
setValue(value: T): void;
|
|
176
|
+
/** Call when the control loses focus: from then on, its errors show. */
|
|
177
|
+
readonly onBlur: () => void;
|
|
178
|
+
/** The error to show, in words — only once it should show. */
|
|
179
|
+
readonly error: string | undefined;
|
|
180
|
+
/** The error itself, with its suggestion. */
|
|
181
|
+
readonly errorDetail: FormFieldError | undefined;
|
|
182
|
+
/** A check (`validateAsync`) is running. */
|
|
183
|
+
readonly validating: boolean;
|
|
184
|
+
/** The check passed for the current value. */
|
|
185
|
+
readonly available: boolean;
|
|
186
|
+
/** The ids of the hint and the error (`aria-describedby`). */
|
|
187
|
+
readonly hintId: string;
|
|
188
|
+
readonly errorId: string;
|
|
189
|
+
/** The form's locale (numbers, money, dates). */
|
|
190
|
+
readonly locale: string;
|
|
191
|
+
/** TanStack's field, through the facade. */
|
|
192
|
+
readonly api: AnyFieldApi;
|
|
193
|
+
}
|
|
194
|
+
/** Binds a field of the form around it (inside `form.Form`): what the platform's field components are made of. */
|
|
195
|
+
declare function useKrizakaField<T = unknown>(name: string, options?: FieldOptions<T>): KrizakaField<T>;
|
|
196
|
+
/** The array of a field array, with its operations (`form.FieldArray`). */
|
|
197
|
+
interface KrizakaFieldArray<T = unknown> {
|
|
198
|
+
readonly name: string;
|
|
199
|
+
readonly items: readonly T[];
|
|
200
|
+
/** Adds an item at the end. */
|
|
201
|
+
readonly push: (item: T) => void;
|
|
202
|
+
/** Removes the item at `index`. */
|
|
203
|
+
readonly remove: (index: number) => void;
|
|
204
|
+
/** Moves an item. */
|
|
205
|
+
readonly move: (from: number, to: number) => void;
|
|
206
|
+
/** The name of a sub-field of an item: `name(0, "url")` → `links[0].url`. */
|
|
207
|
+
readonly fieldName: (index: number, key?: string) => string;
|
|
208
|
+
/** The error of the array itself (too few, too many), in words, once it should show. */
|
|
209
|
+
readonly error: string | undefined;
|
|
210
|
+
}
|
|
211
|
+
/** Binds an array of the form (a list of links, of tiers): its items and their operations. */
|
|
212
|
+
declare function useKrizakaFieldArray<T = unknown>(name: string): KrizakaFieldArray<T>;
|
|
213
|
+
/** The value of a field, kept up to date (`form.Watch`): show a part of the form only when a box is ticked. */
|
|
214
|
+
declare function useFormValue<T = unknown>(name: string): T;
|
|
215
|
+
/** The state of the submit, for a button: sending, and whether the last try succeeded. */
|
|
216
|
+
declare function useFormStatus(): {
|
|
217
|
+
submitting: boolean;
|
|
218
|
+
succeeded: boolean;
|
|
219
|
+
attempts: number;
|
|
220
|
+
};
|
|
221
|
+
/** The platform's components, typed by the form's field names: `name` completes and is checked against the schema. */
|
|
222
|
+
type BoundFields<C, N extends string> = {
|
|
223
|
+
[K in keyof C]: C[K] extends (props: infer P) => infer R ? (P extends {
|
|
224
|
+
name: string;
|
|
225
|
+
} ? (props: Omit<P, "name"> & {
|
|
226
|
+
name: N;
|
|
227
|
+
}) => R : C[K]) : C[K];
|
|
228
|
+
};
|
|
229
|
+
/** A form in a component: its fields (typed by the schema), its submit and its state. */
|
|
230
|
+
type KrizakaFormInstance<S extends FormSchema, C, RootProps> = BoundFields<C, FieldName<FormValues<S>>> & {
|
|
231
|
+
/** The form element (web) or view (native): renders its fields and provides them the form. */
|
|
232
|
+
Form: (props: RootProps) => React.ReactElement;
|
|
233
|
+
/** Sends the form (what `Submit` does): `true` when it went through. */
|
|
234
|
+
submit: () => Promise<boolean>;
|
|
235
|
+
/** Back to the default values (or the given ones), errors cleared. */
|
|
236
|
+
reset: (values?: FormValues<S>) => void;
|
|
237
|
+
/** Sets a field's value from outside (a suggestion, a preset). */
|
|
238
|
+
setValue: (name: FieldName<FormValues<S>>, value: unknown) => void;
|
|
239
|
+
/** The values now. */
|
|
240
|
+
getValues: () => FormValues<S>;
|
|
241
|
+
/** The whole form's error (a refusal without a field), or null. */
|
|
242
|
+
error: FormFieldError | null;
|
|
243
|
+
/** TanStack's form, the escape hatch — never import @tanstack/* in a product. */
|
|
244
|
+
api: AnyFormApi;
|
|
245
|
+
};
|
|
246
|
+
/** A form defined once (schema + defaults), used in components: `signUp.useForm({ onSubmit })`. */
|
|
247
|
+
interface KrizakaFormFactory<S extends FormSchema, C, RootProps> {
|
|
248
|
+
readonly schema: S;
|
|
249
|
+
readonly defaultValues: FormValues<S>;
|
|
250
|
+
/** The form in a component: `onSubmit` sends the values (a mutation), a thrown `KrizakaApiError` lands on the fields. */
|
|
251
|
+
useForm<R = unknown>(options?: UseKrizakaFormOptions<S, R>): KrizakaFormInstance<S, C, RootProps>;
|
|
252
|
+
/** The form sent by a Server Action: it receives the values and answers `{ ok: true, data }` or `{ ok: false, problem }`. */
|
|
253
|
+
useFormWithAction<T = unknown>(action: (values: FormOutput<S>) => Promise<FormActionResult<T>>, options?: Omit<UseKrizakaFormOptions<S, FormActionResult<T>>, "onSubmit">): KrizakaFormInstance<S, C, RootProps>;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** The currency's sign in a locale (`USD`, `en-US` → `$`; `EUR`, `fr` → `€`). */
|
|
257
|
+
declare function currencySymbol(currency: string, locale: string): string;
|
|
258
|
+
/**
|
|
259
|
+
* An amount as typed (`12.5`, `12,50`, `1,234.56`, `1 234,56`) → integer minor units (`1250`). `undefined` when empty,
|
|
260
|
+
* `null` when it is not an amount (letters, more decimals than the currency has).
|
|
261
|
+
*/
|
|
262
|
+
declare function parseMoney(text: string, currency: string, locale?: string): number | null | undefined;
|
|
263
|
+
/** Minor units as the field writes them while editing (`1250`, `USD`, `fr` → `12,50`): no symbol, no grouping. */
|
|
264
|
+
declare function moneyInputText(minorUnits: number | null | undefined, currency: string, locale?: string): string;
|
|
265
|
+
|
|
266
|
+
type Base = {
|
|
267
|
+
/** The field's name in the schema: `email`, `address.city`, `links[0].url` (typed by the form). */
|
|
268
|
+
name: string;
|
|
269
|
+
/** The visible label, passed translated (also the control's accessible name). */
|
|
270
|
+
label: string;
|
|
271
|
+
/** A help text under the control, read with it; the error takes its place when there is one. */
|
|
272
|
+
hint?: string;
|
|
273
|
+
/** Not available. */
|
|
274
|
+
disabled?: boolean;
|
|
275
|
+
/** Classes merged on the field's root. */
|
|
276
|
+
className?: string;
|
|
277
|
+
};
|
|
278
|
+
type FormProps = Omit<React.ComponentProps<"form">, "onSubmit" | "noValidate"> & {
|
|
279
|
+
/** The fields, the error and the button. */
|
|
280
|
+
children?: React.ReactNode;
|
|
281
|
+
};
|
|
282
|
+
type SubmitProps = Omit<ButtonProps, "type" | "loading"> & {
|
|
283
|
+
/** The button's words, passed translated ("Create my account"). */
|
|
284
|
+
children: React.ReactNode;
|
|
285
|
+
};
|
|
286
|
+
/** The submit button: busy while the form is sent (not pressable twice), never disabled for an invalid form. */
|
|
287
|
+
declare function Submit({ variant, children, ...props }: SubmitProps): React.JSX.Element;
|
|
288
|
+
type FormErrorProps = {
|
|
289
|
+
/** A headline above the message ("We could not create your account"), passed translated. */
|
|
290
|
+
title?: string;
|
|
291
|
+
/** Classes merged on the alert. */
|
|
292
|
+
className?: string;
|
|
293
|
+
};
|
|
294
|
+
/** The whole form's error — a refusal of the server that belongs to no field (a conflict, too many tries, no network). */
|
|
295
|
+
declare function FormError({ title, className }: FormErrorProps): React.JSX.Element | null;
|
|
296
|
+
type TextFieldProps = Base & {
|
|
297
|
+
/** `text` (default), `email`, `password`, `url`, `tel`, `search`: the right keyboard on a phone. */
|
|
298
|
+
type?: "text" | "email" | "password" | "url" | "tel" | "search";
|
|
299
|
+
/** The browser's autofill (`email`, `username`, `new-password`, `name`). */
|
|
300
|
+
autoComplete?: string;
|
|
301
|
+
/** An example of a value, passed translated — never the label. */
|
|
302
|
+
placeholder?: string;
|
|
303
|
+
/** The keyboard on a phone (`numeric`, `decimal`). */
|
|
304
|
+
inputMode?: React.HTMLAttributes<HTMLInputElement>["inputMode"];
|
|
305
|
+
/** The most characters. */
|
|
306
|
+
maxLength?: number;
|
|
307
|
+
/** Cleans what is typed before it is stored (`(v) => v.toLowerCase().replace(/\s+/g, "_")`). */
|
|
308
|
+
transform?: (raw: string) => string;
|
|
309
|
+
/** Checks the value on the server as it is typed (availability): an error key, or nothing when it is fine. */
|
|
310
|
+
validateAsync?: FieldCheck<string>;
|
|
311
|
+
/** The wait, in ms, between the last key and `validateAsync`. Default 350. */
|
|
312
|
+
asyncDebounceMs?: number;
|
|
313
|
+
/** The words once `validateAsync` accepted the value ("@eric is free"), passed translated. */
|
|
314
|
+
availableHint?: string;
|
|
315
|
+
/** The name of the wait while it checks ("Checking"), passed translated. */
|
|
316
|
+
checkingLabel?: string;
|
|
317
|
+
/** Something before the text (an `@`, an icon). */
|
|
318
|
+
start?: React.ReactNode;
|
|
319
|
+
};
|
|
320
|
+
/** A text field of the form: label, input, hint or error, an optional check on the server. */
|
|
321
|
+
declare function TextField({ name, label, hint, disabled, className, type, transform, validateAsync, asyncDebounceMs, availableHint, checkingLabel, start, ...input }: TextFieldProps): React.JSX.Element;
|
|
322
|
+
type TextAreaProps = Base & {
|
|
323
|
+
/** An example of a value, passed translated. */
|
|
324
|
+
placeholder?: string;
|
|
325
|
+
/** The visible lines. Default 4. */
|
|
326
|
+
rows?: number;
|
|
327
|
+
/** The most characters. */
|
|
328
|
+
maxLength?: number;
|
|
329
|
+
};
|
|
330
|
+
/** A longer text of the form (a bio, a description, a report). */
|
|
331
|
+
declare function TextArea({ name, label, hint, disabled, className, rows, ...input }: TextAreaProps): React.JSX.Element;
|
|
332
|
+
type CheckboxProps = Omit<Base, "label"> & {
|
|
333
|
+
/** The words next to the box, clickable with it — text, or a sentence with links (terms, privacy). */
|
|
334
|
+
label: React.ReactNode;
|
|
335
|
+
};
|
|
336
|
+
/** A box of the form (`boolean`): terms to accept, an option. */
|
|
337
|
+
declare function Checkbox({ name, label, hint, disabled, className }: CheckboxProps): React.JSX.Element;
|
|
338
|
+
/** An on/off setting of the form (`boolean`): the label and its hint beside the switch. */
|
|
339
|
+
declare function Switch({ name, label, hint, disabled, className }: Base): React.JSX.Element;
|
|
340
|
+
/** A choice of the form: the value stored, its words (passed translated), a line under them. */
|
|
341
|
+
type FormOption = {
|
|
342
|
+
value: string;
|
|
343
|
+
label: string;
|
|
344
|
+
description?: string;
|
|
345
|
+
disabled?: boolean;
|
|
346
|
+
};
|
|
347
|
+
type RadioGroupProps = Base & {
|
|
348
|
+
/** The choices, all visible (two to five). */
|
|
349
|
+
options: readonly FormOption[];
|
|
350
|
+
/** `list` (a dot and its words, default) or `cards` (a whole card is the radio: an amount, a plan, an audience). */
|
|
351
|
+
variant?: "list" | "cards";
|
|
352
|
+
/** `vertical` (default) or `horizontal`. */
|
|
353
|
+
orientation?: "vertical" | "horizontal";
|
|
354
|
+
};
|
|
355
|
+
/** One choice among a few, all visible. */
|
|
356
|
+
declare function RadioGroup({ name, label, hint, disabled, className, options, variant, orientation }: RadioGroupProps): React.JSX.Element;
|
|
357
|
+
type SelectProps = Base & {
|
|
358
|
+
/** The choices, in the platform's own menu (a country, a category). */
|
|
359
|
+
options: readonly FormOption[];
|
|
360
|
+
/** The first line while nothing is chosen ("Choose a category"), passed translated. */
|
|
361
|
+
placeholder?: string;
|
|
362
|
+
};
|
|
363
|
+
/** One choice among many, in the platform's menu (`<select>`: accessible and right on phones). */
|
|
364
|
+
declare function Select({ name, label, hint, disabled, className, options, placeholder }: SelectProps): React.JSX.Element;
|
|
365
|
+
type ComboboxProps = Base & {
|
|
366
|
+
/** The options, filtered as the person types. */
|
|
367
|
+
options?: readonly ComboboxOption[];
|
|
368
|
+
/** Fetches the options for a query (debounced, aborted by a newer one). */
|
|
369
|
+
onSearch?: (query: string, signal: AbortSignal) => Promise<readonly ComboboxOption[]>;
|
|
370
|
+
/** Several values (`string[]`), shown as chips; one (`string`) by default. */
|
|
371
|
+
multiple?: boolean;
|
|
372
|
+
/** Offers to create the query as a value. */
|
|
373
|
+
creatable?: boolean;
|
|
374
|
+
/** The input's placeholder, passed translated. */
|
|
375
|
+
placeholder?: string;
|
|
376
|
+
/** The words of the control, passed translated. */
|
|
377
|
+
labels?: Partial<ComboboxLabels>;
|
|
378
|
+
};
|
|
379
|
+
/** A choice among many, typed to filter or fetched: one value (`string`) or several (`string[]`). */
|
|
380
|
+
declare function Combobox({ name, label, hint, disabled, className, options, onSearch, multiple, creatable, placeholder, labels }: ComboboxProps): React.JSX.Element;
|
|
381
|
+
type DateFieldProps = Base & {
|
|
382
|
+
/** `calendar` (default: a date near today — a publication, an appointment) or `parts` (day · month · year typed: a
|
|
383
|
+
* memorable date, a birth date). */
|
|
384
|
+
entry?: "calendar" | "parts";
|
|
385
|
+
/** The earliest date (ISO), for the calendar. */
|
|
386
|
+
min?: string;
|
|
387
|
+
/** The latest date (ISO), for the calendar (`latestBirthDate(18)` for an age gate). */
|
|
388
|
+
max?: string;
|
|
389
|
+
/** The words of the calendar, or the names of the boxes (`day`, `month`, `year`), passed translated. */
|
|
390
|
+
labels?: Partial<DatePickerLabels> & Partial<Record<DatePart, string>>;
|
|
391
|
+
/** `bday` for a date of birth. */
|
|
392
|
+
autoComplete?: "bday" | "off";
|
|
393
|
+
};
|
|
394
|
+
/** A date of the form (`YYYY-MM-DD`, `""` for none): a calendar, or day · month · year typed in the locale's order. */
|
|
395
|
+
declare function DateField({ name, label, hint, disabled, className, entry, min, max, labels, autoComplete }: DateFieldProps): React.JSX.Element;
|
|
396
|
+
type TagsProps = Base & Pick<TagInputProps, "suggestions" | "normalize" | "max" | "separators" | "placeholder" | "labels">;
|
|
397
|
+
/** Free words of the form as chips (`string[]`): tags, keywords. */
|
|
398
|
+
declare function TagInput({ name, label, hint, disabled, className, ...props }: TagsProps): React.JSX.Element;
|
|
399
|
+
type FileUploadProps = Omit<Base, "hint"> & {
|
|
400
|
+
/** The title in the zone ("Drop your video here"), passed translated. */
|
|
401
|
+
title: string;
|
|
402
|
+
/** The button that opens the file picker, passed translated. */
|
|
403
|
+
browseLabel: string;
|
|
404
|
+
/** What is accepted, in words ("MP4 or MOV, up to 4 GB"). */
|
|
405
|
+
hint?: string;
|
|
406
|
+
/** The types accepted (`video/*`, `image/png,.pdf`). */
|
|
407
|
+
accept?: string;
|
|
408
|
+
/** The largest file, in bytes. */
|
|
409
|
+
maxSize?: number;
|
|
410
|
+
/** Several files (`File[]`, up to `maxFiles`); one by default (still a `File[]` of one). */
|
|
411
|
+
multiple?: boolean;
|
|
412
|
+
/** The most files. */
|
|
413
|
+
maxFiles?: number;
|
|
414
|
+
/** The name of a file's remove button (`(name) => "Remove " + name`), passed translated. */
|
|
415
|
+
removeLabel: (fileName: string) => string;
|
|
416
|
+
/** The words of a refused file (`(r) => r.file.name + " is too large"`). */
|
|
417
|
+
refusalLabel?: React.ComponentProps<typeof Dropzone>["refusalLabel"];
|
|
418
|
+
};
|
|
419
|
+
/** Files of the form (`File[]`): a drop zone, then the chosen files, each removable. */
|
|
420
|
+
declare function FileUpload({ name, label, hint, disabled, className, title, browseLabel, accept, maxSize, multiple, maxFiles, removeLabel, refusalLabel }: FileUploadProps): React.JSX.Element;
|
|
421
|
+
type MoneyProps = Base & {
|
|
422
|
+
/** The currency (ISO 4217: `USD`, `EUR`, `JPY`): its sign before the amount, its decimals. */
|
|
423
|
+
currency: string;
|
|
424
|
+
/** An example of an amount, passed translated. */
|
|
425
|
+
placeholder?: string;
|
|
426
|
+
};
|
|
427
|
+
/** An amount of the form, stored as integer minor units (cents: `1250` = 12.50), typed the locale's way. */
|
|
428
|
+
declare function Money({ name, label, hint, disabled, className, currency, placeholder }: MoneyProps): React.JSX.Element;
|
|
429
|
+
type SliderProps = Base & {
|
|
430
|
+
/** The lowest value. Default 0. */
|
|
431
|
+
min?: number;
|
|
432
|
+
/** The highest value. Default 100. */
|
|
433
|
+
max?: number;
|
|
434
|
+
/** The step. Default 1. */
|
|
435
|
+
step?: number;
|
|
436
|
+
/** The value as words ("15 %", "3 min"), shown and read. */
|
|
437
|
+
formatValue?: (value: number) => string;
|
|
438
|
+
/** For a range (`[number, number]`): each thumb's name, passed translated. */
|
|
439
|
+
thumbLabels?: [string, string];
|
|
440
|
+
};
|
|
441
|
+
/** A number (or a range) of the form, chosen by feel: a share, a volume, a price range. */
|
|
442
|
+
declare function Slider({ name, label, hint, disabled, className, min, max, step, formatValue, thumbLabels }: SliderProps): React.JSX.Element;
|
|
443
|
+
type FormFieldProps<T = unknown> = {
|
|
444
|
+
/** The field's name in the schema (typed by the form). */
|
|
445
|
+
name: string;
|
|
446
|
+
/** Checks the value on the server as it changes. */
|
|
447
|
+
validateAsync?: FieldCheck<T>;
|
|
448
|
+
/** The wait, in ms, before `validateAsync`. */
|
|
449
|
+
asyncDebounceMs?: number;
|
|
450
|
+
/** Draws the field: its value, setter, error and ids (`useKrizakaField`). */
|
|
451
|
+
children: (field: KrizakaField<T>) => React.ReactNode;
|
|
452
|
+
};
|
|
453
|
+
/** A field drawn by the app (a control the package does not have): the form's value, errors and ids, any markup. */
|
|
454
|
+
declare function FormField({ name, validateAsync, asyncDebounceMs, children }: FormFieldProps): React.JSX.Element;
|
|
455
|
+
type FieldArrayProps<T = unknown> = {
|
|
456
|
+
/** The array's name in the schema (typed by the form). */
|
|
457
|
+
name: string;
|
|
458
|
+
/** Draws the items: `items`, `push`, `remove`, `move`, `fieldName(i, "url")` for the fields of an item. */
|
|
459
|
+
children: (array: KrizakaFieldArray<T>) => React.ReactNode;
|
|
460
|
+
};
|
|
461
|
+
/** A list of fields that grows and shrinks (links, tiers, guests): each item's fields named `links[0].url`. */
|
|
462
|
+
declare function FieldArray({ name, children }: FieldArrayProps): React.JSX.Element;
|
|
463
|
+
type WatchProps<T = unknown> = {
|
|
464
|
+
/** The field to follow (typed by the form). */
|
|
465
|
+
name: string;
|
|
466
|
+
/** Draws from its current value: show a part of the form only when a box is ticked. */
|
|
467
|
+
children: (value: T) => React.ReactNode;
|
|
468
|
+
};
|
|
469
|
+
/** Renders from a field's current value (a part shown only when a box is ticked, a total). */
|
|
470
|
+
declare function Watch({ name, children }: WatchProps): React.JSX.Element;
|
|
471
|
+
/**
|
|
472
|
+
* A form, defined once: its schema (zod 4) and default values — the same schema the server parses with. In a component,
|
|
473
|
+
* `signUp.useForm({ onSubmit })` gives the form and its fields, typed by the schema: `<form.Form>`, `<form.TextField
|
|
474
|
+
* name="email" …/>`, `<form.Submit>`. Errors are i18n keys (`form.errors.tooShort`), never the engine's English.
|
|
475
|
+
*/
|
|
476
|
+
declare const createKrizakaForm: <S extends FormSchema>(definition: KrizakaFormDefinition<S>) => KrizakaFormFactory<S, {
|
|
477
|
+
TextField: typeof TextField;
|
|
478
|
+
TextArea: typeof TextArea;
|
|
479
|
+
Checkbox: typeof Checkbox;
|
|
480
|
+
Switch: typeof Switch;
|
|
481
|
+
RadioGroup: typeof RadioGroup;
|
|
482
|
+
Select: typeof Select;
|
|
483
|
+
Combobox: typeof Combobox;
|
|
484
|
+
DateField: typeof DateField;
|
|
485
|
+
TagInput: typeof TagInput;
|
|
486
|
+
FileUpload: typeof FileUpload;
|
|
487
|
+
Money: typeof Money;
|
|
488
|
+
Slider: typeof Slider;
|
|
489
|
+
Field: typeof FormField;
|
|
490
|
+
FieldArray: typeof FieldArray;
|
|
491
|
+
Watch: typeof Watch;
|
|
492
|
+
Submit: typeof Submit;
|
|
493
|
+
FormError: typeof FormError;
|
|
494
|
+
}, Omit<React.DetailedHTMLProps<React.FormHTMLAttributes<HTMLFormElement>, HTMLFormElement>, "onSubmit" | "noValidate"> & {
|
|
495
|
+
/** The fields, the error and the button. */
|
|
496
|
+
children?: React.ReactNode;
|
|
497
|
+
}>;
|
|
498
|
+
/** The form sent by a Server Action: `useFormWithAction(signUp, createAccount)` — `signUp.useFormWithAction(createAccount)`. */
|
|
499
|
+
declare function useFormWithAction<F extends {
|
|
500
|
+
useFormWithAction: (...args: never[]) => unknown;
|
|
501
|
+
}>(form: F, ...args: Parameters<F["useFormWithAction"]>): ReturnType<F["useFormWithAction"]>;
|
|
502
|
+
|
|
503
|
+
export { Checkbox, type CheckboxProps, Combobox, type ComboboxProps, DateField, type DateFieldProps, FieldArray, type FieldArrayProps, type FieldCheck, type FieldName, type FieldOptions, FileUpload, type FileUploadProps, type FormActionResult, type FormContextValue, FormError, type FormErrorProps, FormField, type FormFieldError, type FormFieldProps, type FormI18n, type FormMessageValue, type FormOption, type FormOutput, type FormProblem, type FormProps, type FormSchema, type FormValues, type KrizakaField, type KrizakaFieldArray, type KrizakaFormDefinition, type KrizakaFormFactory, type KrizakaFormInstance, Money, type MoneyProps, RadioGroup, type RadioGroupProps, Select, type SelectProps, Slider, type SliderProps, Submit, type SubmitProps, Switch, TagInput, type TagsProps, TextArea, type TextAreaProps, TextField, type TextFieldProps, type UseKrizakaFormOptions, Watch, type WatchProps, composeDate, createKrizakaForm, currencySymbol, datePartsOrder, formMessage, formMessages, isRealDate, issueError, issuesToFields, latestBirthDate, moneyInputText, parseMoney, problemToErrors, useFormContext, useFormStatus, useFormValue, useFormWithAction, useKrizakaField, useKrizakaFieldArray };
|