@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.
Files changed (310) hide show
  1. package/README.md +1 -1
  2. package/dist/achievement.js +1 -1
  3. package/dist/action-bar.js +1 -1
  4. package/dist/app-shell.js +1 -1
  5. package/dist/argument-card.js +2 -2
  6. package/dist/card-grid.js +1 -1
  7. package/dist/card.js +1 -1
  8. package/dist/choice-grid.js +1 -1
  9. package/dist/{chunk-OGJI5X4W.js → chunk-2PUDI7JS.js} +2 -2
  10. package/dist/chunk-4LOMIQVX.js +21 -0
  11. package/dist/chunk-4LOMIQVX.js.map +1 -0
  12. package/dist/{chunk-K3SWZXYT.js → chunk-DGQMDJXK.js} +2 -2
  13. package/dist/chunk-G3ETON2A.js +22 -0
  14. package/dist/chunk-G3ETON2A.js.map +1 -0
  15. package/dist/chunk-I5RVM5CW.js +27 -0
  16. package/dist/chunk-I5RVM5CW.js.map +1 -0
  17. package/dist/chunk-IZ7MV6HM.js +26 -0
  18. package/dist/chunk-IZ7MV6HM.js.map +1 -0
  19. package/dist/chunk-ZNMFG62E.js +20 -0
  20. package/dist/chunk-ZNMFG62E.js.map +1 -0
  21. package/dist/cn.d.ts +9 -1
  22. package/dist/cn.js +5 -3
  23. package/dist/code-block.js +1 -1
  24. package/dist/command.js +1 -1
  25. package/dist/confirm-button.js +1 -1
  26. package/dist/continue-prompt.js +1 -1
  27. package/dist/cta-panel.js +1 -1
  28. package/dist/data-table.js +2 -2
  29. package/dist/date-picker.d.ts +650 -61
  30. package/dist/date-picker.js +918 -122
  31. package/dist/date-picker.js.map +1 -1
  32. package/dist/decision-list.js +1 -1
  33. package/dist/dropdown-menu.js +1 -1
  34. package/dist/error-page.js +1 -1
  35. package/dist/field.js +1 -1
  36. package/dist/filter-bar.js +2 -2
  37. package/dist/form/server.d.ts +96 -0
  38. package/dist/form/server.js +82 -0
  39. package/dist/form/server.js.map +1 -0
  40. package/dist/form.d.ts +503 -0
  41. package/dist/form.js +760 -0
  42. package/dist/form.js.map +1 -0
  43. package/dist/gallery-page.js +1 -1
  44. package/dist/glow-card.js +1 -1
  45. package/dist/goal-gauge.js +1 -1
  46. package/dist/how-it-works-page.js +1 -1
  47. package/dist/landscape.js +1 -1
  48. package/dist/link-card.js +1 -1
  49. package/dist/longform-page.js +1 -1
  50. package/dist/marquee.js +1 -1
  51. package/dist/mascot-quote.js +1 -1
  52. package/dist/motion.css +18 -1
  53. package/dist/native/form.d.ts +376 -0
  54. package/dist/native/form.js +650 -0
  55. package/dist/native/form.js.map +1 -0
  56. package/dist/native.d.ts +351 -3
  57. package/dist/native.js +1205 -399
  58. package/dist/native.js.map +1 -1
  59. package/dist/nav-bar.js +1 -1
  60. package/dist/page-hero.js +2 -2
  61. package/dist/page.js +2 -2
  62. package/dist/popover.js +1 -1
  63. package/dist/pricing-page.js +1 -1
  64. package/dist/product-page.js +1 -1
  65. package/dist/profile-card.js +1 -1
  66. package/dist/promise-card.js +1 -1
  67. package/dist/scroll-stage.js +1 -1
  68. package/dist/stage-banner.js +1 -1
  69. package/dist/terminal-replay.js +1 -1
  70. package/dist/toast.js +1 -1
  71. package/dist/toc.js +1 -1
  72. package/dist/tooltip.js +1 -1
  73. package/package.json +50 -7
  74. package/registry/achievement.json +1 -1
  75. package/registry/action-bar.json +1 -1
  76. package/registry/alert.json +1 -1
  77. package/registry/app-shell.json +1 -1
  78. package/registry/argument-card.json +1 -1
  79. package/registry/audio-player.json +1 -1
  80. package/registry/auth-layout.json +1 -1
  81. package/registry/avatar.json +1 -1
  82. package/registry/badge.json +1 -1
  83. package/registry/button.json +1 -1
  84. package/registry/card-grid.json +1 -1
  85. package/registry/card.json +1 -1
  86. package/registry/catalogue-page.json +1 -1
  87. package/registry/changelog-page.json +1 -1
  88. package/registry/checkbox.json +89 -4
  89. package/registry/chip-link.json +1 -1
  90. package/registry/chip.json +1 -1
  91. package/registry/choice-grid.json +1 -1
  92. package/registry/cn.json +1 -1
  93. package/registry/code-block.json +1 -1
  94. package/registry/combobox.json +1 -1
  95. package/registry/command.json +1 -1
  96. package/registry/component-page.json +1 -1
  97. package/registry/confirm-button.json +1 -1
  98. package/registry/contact-page.json +1 -1
  99. package/registry/continue-prompt.json +1 -1
  100. package/registry/countdown.json +1 -1
  101. package/registry/cta-panel.json +1 -1
  102. package/registry/dashboard-layout.json +1 -1
  103. package/registry/data-table.json +1 -1
  104. package/registry/date-picker.json +1811 -236
  105. package/registry/decision-list.json +1 -1
  106. package/registry/detail-layout.json +1 -1
  107. package/registry/dialog.json +1 -1
  108. package/registry/docs-article-page.json +1 -1
  109. package/registry/docs-hub-page.json +1 -1
  110. package/registry/docs-layout.json +1 -1
  111. package/registry/dropdown-menu.json +1 -1
  112. package/registry/empty-state.json +1 -1
  113. package/registry/error-page.json +1 -1
  114. package/registry/examples/checkbox/native/terms.d.ts +4 -0
  115. package/registry/examples/checkbox/native/terms.dark.png +0 -0
  116. package/registry/examples/checkbox/native/terms.js +26 -0
  117. package/registry/examples/checkbox/native/terms.light.png +0 -0
  118. package/registry/examples/checkbox/native/terms.tsx +28 -0
  119. package/registry/examples/date-picker/birth-date-wheel.d.ts +4 -0
  120. package/registry/examples/date-picker/birth-date-wheel.js +13 -0
  121. package/registry/examples/date-picker/birth-date-wheel.tsx +11 -0
  122. package/registry/examples/date-picker/birth-date.d.ts +4 -0
  123. package/registry/examples/date-picker/birth-date.js +25 -0
  124. package/registry/examples/date-picker/birth-date.tsx +21 -0
  125. package/registry/examples/date-picker/calendar.js +1 -1
  126. package/registry/examples/date-picker/calendar.tsx +2 -6
  127. package/registry/examples/date-picker/day-strip.d.ts +4 -0
  128. package/registry/examples/date-picker/day-strip.js +28 -0
  129. package/registry/examples/date-picker/day-strip.tsx +27 -0
  130. package/registry/examples/date-picker/month-year.d.ts +4 -0
  131. package/registry/examples/date-picker/month-year.js +13 -0
  132. package/registry/examples/date-picker/month-year.tsx +10 -0
  133. package/registry/examples/date-picker/native/birth-date-wheel.d.ts +4 -0
  134. package/registry/examples/date-picker/native/birth-date-wheel.dark.png +0 -0
  135. package/registry/examples/date-picker/native/birth-date-wheel.js +13 -0
  136. package/registry/examples/date-picker/native/birth-date-wheel.light.png +0 -0
  137. package/registry/examples/date-picker/native/birth-date-wheel.tsx +9 -0
  138. package/registry/examples/date-picker/native/birth-date.d.ts +4 -0
  139. package/registry/examples/date-picker/native/birth-date.dark.png +0 -0
  140. package/registry/examples/date-picker/native/birth-date.js +30 -0
  141. package/registry/examples/date-picker/native/birth-date.light.png +0 -0
  142. package/registry/examples/date-picker/native/birth-date.tsx +27 -0
  143. package/registry/examples/date-picker/native/calendar.d.ts +4 -0
  144. package/registry/examples/date-picker/native/calendar.dark.png +0 -0
  145. package/registry/examples/date-picker/native/calendar.js +13 -0
  146. package/registry/examples/date-picker/native/calendar.light.png +0 -0
  147. package/registry/examples/date-picker/native/calendar.tsx +8 -0
  148. package/registry/examples/date-picker/native/day-strip.d.ts +4 -0
  149. package/registry/examples/date-picker/native/day-strip.dark.png +0 -0
  150. package/registry/examples/date-picker/native/day-strip.js +29 -0
  151. package/registry/examples/date-picker/native/day-strip.light.png +0 -0
  152. package/registry/examples/date-picker/native/day-strip.tsx +26 -0
  153. package/registry/examples/date-picker/native/month-year.d.ts +4 -0
  154. package/registry/examples/date-picker/native/month-year.dark.png +0 -0
  155. package/registry/examples/date-picker/native/month-year.js +13 -0
  156. package/registry/examples/date-picker/native/month-year.light.png +0 -0
  157. package/registry/examples/date-picker/native/month-year.tsx +8 -0
  158. package/registry/examples/date-picker/native/time-slots.d.ts +4 -0
  159. package/registry/examples/date-picker/native/time-slots.dark.png +0 -0
  160. package/registry/examples/date-picker/native/time-slots.js +15 -0
  161. package/registry/examples/date-picker/native/time-slots.light.png +0 -0
  162. package/registry/examples/date-picker/native/time-slots.tsx +15 -0
  163. package/registry/examples/date-picker/native/time-wheel.d.ts +4 -0
  164. package/registry/examples/date-picker/native/time-wheel.dark.png +0 -0
  165. package/registry/examples/date-picker/native/time-wheel.js +13 -0
  166. package/registry/examples/date-picker/native/time-wheel.light.png +0 -0
  167. package/registry/examples/date-picker/native/time-wheel.tsx +8 -0
  168. package/registry/examples/date-picker/time-slots.d.ts +4 -0
  169. package/registry/examples/date-picker/time-slots.js +26 -0
  170. package/registry/examples/date-picker/time-slots.tsx +25 -0
  171. package/registry/examples/date-picker/time-wheel.d.ts +4 -0
  172. package/registry/examples/date-picker/time-wheel.js +13 -0
  173. package/registry/examples/date-picker/time-wheel.tsx +10 -0
  174. package/registry/examples/date-picker/wheel.d.ts +4 -0
  175. package/registry/examples/date-picker/wheel.js +14 -0
  176. package/registry/examples/date-picker/wheel.tsx +16 -0
  177. package/registry/examples/field/native/default.d.ts +4 -0
  178. package/registry/examples/field/native/default.dark.png +0 -0
  179. package/registry/examples/field/native/default.js +29 -0
  180. package/registry/examples/field/native/default.light.png +0 -0
  181. package/registry/examples/field/native/default.tsx +23 -0
  182. package/registry/examples/form/amount.d.ts +4 -0
  183. package/registry/examples/form/amount.js +30 -0
  184. package/registry/examples/form/amount.tsx +36 -0
  185. package/registry/examples/form/audience.d.ts +4 -0
  186. package/registry/examples/form/audience.js +29 -0
  187. package/registry/examples/form/audience.tsx +31 -0
  188. package/registry/examples/form/native/amount.d.ts +4 -0
  189. package/registry/examples/form/native/amount.dark.png +0 -0
  190. package/registry/examples/form/native/amount.js +26 -0
  191. package/registry/examples/form/native/amount.light.png +0 -0
  192. package/registry/examples/form/native/amount.tsx +30 -0
  193. package/registry/examples/form/native/audience.d.ts +4 -0
  194. package/registry/examples/form/native/audience.dark.png +0 -0
  195. package/registry/examples/form/native/audience.js +24 -0
  196. package/registry/examples/form/native/audience.light.png +0 -0
  197. package/registry/examples/form/native/audience.tsx +24 -0
  198. package/registry/examples/form/native/profile.d.ts +4 -0
  199. package/registry/examples/form/native/profile.dark.png +0 -0
  200. package/registry/examples/form/native/profile.js +33 -0
  201. package/registry/examples/form/native/profile.light.png +0 -0
  202. package/registry/examples/form/native/profile.tsx +36 -0
  203. package/registry/examples/form/native/settings.d.ts +4 -0
  204. package/registry/examples/form/native/settings.dark.png +0 -0
  205. package/registry/examples/form/native/settings.js +25 -0
  206. package/registry/examples/form/native/settings.light.png +0 -0
  207. package/registry/examples/form/native/settings.tsx +23 -0
  208. package/registry/examples/form/native/sign-up.d.ts +4 -0
  209. package/registry/examples/form/native/sign-up.dark.png +0 -0
  210. package/registry/examples/form/native/sign-up.js +38 -0
  211. package/registry/examples/form/native/sign-up.light.png +0 -0
  212. package/registry/examples/form/native/sign-up.tsx +34 -0
  213. package/registry/examples/form/native/upload.d.ts +4 -0
  214. package/registry/examples/form/native/upload.dark.png +0 -0
  215. package/registry/examples/form/native/upload.js +23 -0
  216. package/registry/examples/form/native/upload.light.png +0 -0
  217. package/registry/examples/form/native/upload.tsx +22 -0
  218. package/registry/examples/form/profile.d.ts +4 -0
  219. package/registry/examples/form/profile.js +35 -0
  220. package/registry/examples/form/profile.tsx +40 -0
  221. package/registry/examples/form/report.d.ts +4 -0
  222. package/registry/examples/form/report.js +30 -0
  223. package/registry/examples/form/report.tsx +30 -0
  224. package/registry/examples/form/settings.d.ts +4 -0
  225. package/registry/examples/form/settings.js +30 -0
  226. package/registry/examples/form/settings.tsx +31 -0
  227. package/registry/examples/form/sign-up.d.ts +4 -0
  228. package/registry/examples/form/sign-up.js +38 -0
  229. package/registry/examples/form/sign-up.tsx +37 -0
  230. package/registry/examples/form/upload.d.ts +4 -0
  231. package/registry/examples/form/upload.js +32 -0
  232. package/registry/examples/form/upload.tsx +32 -0
  233. package/registry/field.json +104 -4
  234. package/registry/file-upload.json +1 -1
  235. package/registry/filter-bar.json +1 -1
  236. package/registry/form.json +2940 -0
  237. package/registry/gallery-page.json +1 -1
  238. package/registry/glow-card.json +1 -1
  239. package/registry/goal-gauge.json +1 -1
  240. package/registry/how-it-works-page.json +1 -1
  241. package/registry/image-cropper.json +1 -1
  242. package/registry/index.json +43 -10
  243. package/registry/iso.json +1 -1
  244. package/registry/kbd.json +1 -1
  245. package/registry/landing-page.json +1 -1
  246. package/registry/landscape.json +1 -1
  247. package/registry/legal-page.json +1 -1
  248. package/registry/link-card.json +1 -1
  249. package/registry/loader.json +1 -1
  250. package/registry/longform-page.json +1 -1
  251. package/registry/marketing-layout.json +1 -1
  252. package/registry/marquee.json +1 -1
  253. package/registry/mascot-quote.json +1 -1
  254. package/registry/mascot-state.json +2 -2
  255. package/registry/mascots.json +2 -2
  256. package/registry/media-capture.json +1 -1
  257. package/registry/meter.json +1 -1
  258. package/registry/nav-bar.json +1 -1
  259. package/registry/number-ticker.json +1 -1
  260. package/registry/page-header.json +1 -1
  261. package/registry/page-hero.json +1 -1
  262. package/registry/page.json +1 -1
  263. package/registry/pagination.json +1 -1
  264. package/registry/pointer-light.json +1 -1
  265. package/registry/popover.json +1 -1
  266. package/registry/pricing-page.json +1 -1
  267. package/registry/product-page.json +1 -1
  268. package/registry/profile-card.json +1 -1
  269. package/registry/progress.json +1 -1
  270. package/registry/promise-card.json +1 -1
  271. package/registry/radio-group.json +1 -1
  272. package/registry/scroll-stage.json +1 -1
  273. package/registry/search-field.json +1 -1
  274. package/registry/section-backdrop.json +1 -1
  275. package/registry/separator.json +1 -1
  276. package/registry/settings-layout.json +1 -1
  277. package/registry/skeleton.json +1 -1
  278. package/registry/slider.json +1 -1
  279. package/registry/slot.json +1 -1
  280. package/registry/spinner.json +1 -1
  281. package/registry/split-view.json +1 -1
  282. package/registry/stage-banner.json +1 -1
  283. package/registry/stat.json +1 -1
  284. package/registry/status-page.json +1 -1
  285. package/registry/stepper.json +1 -1
  286. package/registry/switch.json +1 -1
  287. package/registry/tabs.json +1 -1
  288. package/registry/tag-input.json +1 -1
  289. package/registry/terminal-replay.json +1 -1
  290. package/registry/theme.json +8 -1
  291. package/registry/toast.json +1 -1
  292. package/registry/toc.json +1 -1
  293. package/registry/tooltip.json +1 -1
  294. package/registry/video-player.json +1 -1
  295. package/registry/video-trimmer.json +1 -1
  296. package/registry/virtual.json +1 -1
  297. package/skills/date-picker/SKILL.md +43 -0
  298. package/skills/form/SKILL.md +44 -0
  299. package/dist/chunk-2USC2YT6.js +0 -17
  300. package/dist/chunk-2USC2YT6.js.map +0 -1
  301. package/dist/chunk-5AXLA4LK.js +0 -13
  302. package/dist/chunk-5AXLA4LK.js.map +0 -1
  303. package/dist/chunk-FJDSLNXK.js +0 -11
  304. package/dist/chunk-FJDSLNXK.js.map +0 -1
  305. package/dist/chunk-KNBYZGSQ.js +0 -11
  306. package/dist/chunk-KNBYZGSQ.js.map +0 -1
  307. package/dist/chunk-VCAAHCHL.js +0 -17
  308. package/dist/chunk-VCAAHCHL.js.map +0 -1
  309. /package/dist/{chunk-OGJI5X4W.js.map → chunk-2PUDI7JS.js.map} +0 -0
  310. /package/dist/{chunk-K3SWZXYT.js.map → chunk-DGQMDJXK.js.map} +0 -0
package/dist/form.js ADDED
@@ -0,0 +1,760 @@
1
+ "use client";
2
+
3
+ // src/date-picker/parts.ts
4
+ var DATE_PART_LENGTH = { day: 2, month: 2, year: 4 };
5
+ function datePartsOrder(locale) {
6
+ try {
7
+ const parts = new Intl.DateTimeFormat(locale, { day: "2-digit", month: "2-digit", year: "numeric" }).formatToParts(new Date(2001, 10, 22));
8
+ const order = parts.map((p) => p.type).filter((t) => t === "day" || t === "month" || t === "year");
9
+ if (order.length === 3) return order;
10
+ } catch {
11
+ }
12
+ return ["day", "month", "year"];
13
+ }
14
+ function composeDate(parts) {
15
+ if (!parts.day && !parts.month && !parts.year) return "";
16
+ const pad = (v) => v.length === 1 ? `0${v}` : v;
17
+ return `${parts.year}-${pad(parts.month)}-${pad(parts.day)}`;
18
+ }
19
+ function splitDate(value) {
20
+ const m = /^(\d*)-(\d*)-(\d*)$/.exec(value ?? "");
21
+ return m ? { year: m[1], month: m[2], day: m[3] } : { day: "", month: "", year: "" };
22
+ }
23
+ function isRealDate(value) {
24
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
25
+ const date = /* @__PURE__ */ new Date(`${value}T00:00:00Z`);
26
+ return !Number.isNaN(date.getTime()) && date.toISOString().slice(0, 10) === value;
27
+ }
28
+ function latestBirthDate(age, today = /* @__PURE__ */ new Date()) {
29
+ return new Date(Date.UTC(today.getFullYear() - age, today.getMonth(), today.getDate())).toISOString().slice(0, 10);
30
+ }
31
+
32
+ // src/form/core.tsx
33
+ import { useField, useForm, useStore } from "@tanstack/react-form";
34
+ import * as React from "react";
35
+
36
+ // src/form/errors.ts
37
+ var formMessages = {
38
+ required: "This field is required.",
39
+ tooShort: "Use at least {minimum} characters.",
40
+ tooLong: "Use at most {maximum} characters.",
41
+ tooSmall: "Enter {minimum} or more.",
42
+ tooBig: "Enter {maximum} or less.",
43
+ tooFew: "Choose at least {minimum}.",
44
+ tooMany: "Choose at most {maximum}.",
45
+ invalidEmail: "Enter an e-mail address, like name@example.com.",
46
+ invalidUrl: "Enter a web address, like https://example.com.",
47
+ invalidDate: "Enter a real date.",
48
+ invalidNumber: "Enter a number.",
49
+ invalidFormat: "Check the format of this field.",
50
+ invalidChoice: "Choose one of the options.",
51
+ invalid: "Check this field.",
52
+ // The whole form (a server's answer without fields): the codes of @krizaka/query's KrizakaApiError.
53
+ network: "No connection. Check your network and try again.",
54
+ "rate-limited": "Too many tries. Wait a moment and try again.",
55
+ unauthorized: "Your session has ended. Sign in again.",
56
+ forbidden: "You are not allowed to do this.",
57
+ conflict: "This was changed meanwhile. Reload and try again.",
58
+ "validation-failed": "Some fields need your attention.",
59
+ unavailable: "The service is unavailable for a moment. Try again shortly.",
60
+ server: "Something went wrong on our side. Try again."
61
+ };
62
+ var KEY = /^[\w-]+(\.[\w-]+)+$/;
63
+ var fill = (text, values) => values ? text.replace(/\{(\w+)\}/g, (all, name) => values[name] === void 0 ? all : String(values[name])) : text;
64
+ function formMessage(error, i18n = {}) {
65
+ const e = typeof error === "string" ? { key: error } : error;
66
+ const translated = i18n.t?.(e.key, e.values);
67
+ if (translated && translated !== e.key) return translated;
68
+ const code = e.key.slice(e.key.lastIndexOf(".") + 1);
69
+ const fallback = formMessages[code];
70
+ if (e.message && (code === "invalid" || !fallback)) return e.message;
71
+ if (fallback && (e.key.startsWith(`${i18n.prefix ?? "form.errors"}.`) || e.key.startsWith(`${i18n.apiPrefix ?? "apiErrors"}.`))) return fill(fallback, e.values);
72
+ return e.message ?? (KEY.test(e.key) ? fill(fallback ?? formMessages.invalid, e.values) : e.key);
73
+ }
74
+ function pathName(path) {
75
+ let name = "";
76
+ for (const part of path ?? []) {
77
+ const key = typeof part === "object" && part !== null ? part.key : part;
78
+ if (typeof key === "number") name += `[${key}]`;
79
+ else name += name ? `.${String(key)}` : String(key);
80
+ }
81
+ return name;
82
+ }
83
+ var num = (value) => value === void 0 ? void 0 : Number(value);
84
+ function issueError(issue, prefix = "form.errors") {
85
+ if (KEY.test(issue.message)) return { key: issue.message };
86
+ const key = (code, values) => ({ key: `${prefix}.${code}`, ...values ? { values } : {} });
87
+ const collection = issue.origin === "array" || issue.origin === "set";
88
+ switch (issue.code) {
89
+ case "invalid_type":
90
+ return issue.input === void 0 || issue.input === null || issue.input === "" ? key("required") : issue.expected === "number" ? key("invalidNumber") : issue.expected === "date" ? key("invalidDate") : key("invalid");
91
+ case "too_small": {
92
+ const minimum = num(issue.minimum);
93
+ if (issue.origin === "string") return minimum <= 1 ? key("required") : key("tooShort", { minimum });
94
+ if (collection) return minimum <= 1 ? key("required") : key("tooFew", { minimum });
95
+ return key("tooSmall", { minimum });
96
+ }
97
+ case "too_big": {
98
+ const maximum = num(issue.maximum);
99
+ return issue.origin === "string" ? key("tooLong", { maximum }) : collection ? key("tooMany", { maximum }) : key("tooBig", { maximum });
100
+ }
101
+ case "invalid_format":
102
+ if (issue.format === "email") return key("invalidEmail");
103
+ if (issue.format === "url") return key("invalidUrl");
104
+ if (issue.format === "date" || issue.format === "datetime") return key("invalidDate");
105
+ return key("invalidFormat");
106
+ case "invalid_value":
107
+ return issue.input === false || issue.input === void 0 ? key("required") : key("invalidChoice");
108
+ case "invalid_union":
109
+ case "invalid_key":
110
+ case "invalid_element":
111
+ return key("invalidChoice");
112
+ default:
113
+ return key("invalid");
114
+ }
115
+ }
116
+ function issuesToFields(issues, prefix) {
117
+ const fields2 = {};
118
+ for (const issue of issues) {
119
+ const name = pathName(issue.path);
120
+ if (!(name in fields2)) fields2[name] = { ...issueError(issue, prefix), source: "schema" };
121
+ }
122
+ return fields2;
123
+ }
124
+ function isFormProblem(value) {
125
+ return typeof value === "object" && value !== null && ("fields" in value || "code" in value || "status" in value);
126
+ }
127
+ function problemToErrors(problem, i18n = {}, known, codeFields = {}) {
128
+ const prefix = i18n.prefix ?? "form.errors";
129
+ const fields2 = {};
130
+ let unplaced = false;
131
+ const owner = problem.code ? codeFields[problem.code] : void 0;
132
+ if (owner && (!known || known(owner)) && !problem.fields?.[owner]) {
133
+ fields2[owner] = { key: `${prefix}.${problem.code}`, ...problem.detail ? { message: problem.detail } : {}, source: "server" };
134
+ if (!problem.fields || Object.keys(problem.fields).length === 0) return { fields: fields2, form: null };
135
+ }
136
+ for (const [name, refusal] of Object.entries(problem.fields ?? {})) {
137
+ if (known && !known(name)) {
138
+ unplaced = true;
139
+ continue;
140
+ }
141
+ const error = typeof refusal === "string" ? { key: refusal } : "key" in refusal ? refusal : { key: refusal.code ? KEY.test(refusal.code) ? refusal.code : `${prefix}.${refusal.code}` : `${prefix}.invalid`, message: refusal.message };
142
+ fields2[name] = { ...error, source: "server" };
143
+ }
144
+ const placed = Object.keys(fields2).length > 0;
145
+ if (placed && !unplaced) return { fields: fields2, form: null };
146
+ const code = problem.code ?? (problem.status === 0 ? "network" : problem.status && problem.status >= 500 ? "server" : "invalid");
147
+ const retryIn = problem.retryAt ? Math.max(1, Math.ceil((problem.retryAt - Date.now()) / 1e3)) : void 0;
148
+ return {
149
+ fields: fields2,
150
+ form: { key: KEY.test(code) ? code : `${i18n.apiPrefix ?? "apiErrors"}.${code}`, ...problem.detail ? { message: problem.detail } : {}, ...retryIn ? { values: { seconds: retryIn } } : {}, source: "server" }
151
+ };
152
+ }
153
+
154
+ // src/form/core.tsx
155
+ import { jsx } from "react/jsx-runtime";
156
+ var FormContext = React.createContext(null);
157
+ function useFormContext() {
158
+ const ctx = React.useContext(FormContext);
159
+ if (!ctx) throw new Error("A Krizaka form field must be inside its form's `form.Form`.");
160
+ return ctx;
161
+ }
162
+ var run = (schema, value) => schema["~standard"].validate(value);
163
+ function useKrizakaFormCore(definition, options = {}) {
164
+ const i18n = { ...definition.i18n, ...options.i18n };
165
+ const latest = React.useRef({ options, i18n });
166
+ React.useLayoutEffect(() => {
167
+ latest.current = { options, i18n };
168
+ });
169
+ const [error, setError] = React.useState(null);
170
+ const refused = React.useRef(false);
171
+ const api = useForm({
172
+ defaultValues: options.defaultValues ?? definition.defaultValues,
173
+ validators: {
174
+ // Every change: the schema, synchronously (an async refinement waits for the submit). Errors are computed for
175
+ // every field; each field shows its own once the person left it or tried to send.
176
+ onChange: ({ value }) => {
177
+ const result = run(definition.schema, value);
178
+ if (result instanceof Promise || !result.issues?.length) return void 0;
179
+ const fields2 = issuesToFields(result.issues, latest.current.i18n.prefix);
180
+ delete fields2[""];
181
+ return { fields: fields2 };
182
+ }
183
+ },
184
+ onSubmit: async ({ value }) => {
185
+ const parsed = await run(definition.schema, value);
186
+ if (parsed.issues?.length) {
187
+ apply({ fields: issuesToFields(parsed.issues, latest.current.i18n.prefix) });
188
+ return;
189
+ }
190
+ let result;
191
+ try {
192
+ result = await latest.current.options.onSubmit?.(parsed.value);
193
+ } catch (thrown) {
194
+ apply(isFormProblem(thrown) ? thrown : { code: "server" });
195
+ return;
196
+ }
197
+ if (result && typeof result === "object" && "ok" in result && result.ok === false) {
198
+ apply(result.problem);
199
+ return;
200
+ }
201
+ const data = result && typeof result === "object" && "ok" in result ? result.data : result;
202
+ latest.current.options.onSuccess?.(data);
203
+ }
204
+ });
205
+ const known = (name) => name in api.fieldInfo;
206
+ function apply(problem) {
207
+ refused.current = true;
208
+ const { fields: fields2, form } = problemToErrors(problem, latest.current.i18n, known, latest.current.options.codeFields);
209
+ for (const [name, fieldError] of Object.entries(fields2)) {
210
+ api.setFieldMeta(name, (meta) => ({ ...meta, isBlurred: true, errorMap: { ...meta.errorMap, onServer: fieldError } }));
211
+ }
212
+ setError(form);
213
+ }
214
+ const submit = React.useCallback(async () => {
215
+ setError(null);
216
+ refused.current = false;
217
+ for (const name of Object.keys(api.fieldInfo)) {
218
+ if (api.getFieldMeta(name)?.errorMap?.onServer) api.setFieldMeta(name, (meta) => ({ ...meta, errorMap: { ...meta.errorMap, onServer: void 0 } }));
219
+ }
220
+ await api.handleSubmit();
221
+ const state = api.state;
222
+ return !refused.current && state.isSubmitSuccessful && state.isValid && !Object.values(state.fieldMeta).some((meta) => meta?.errorMap?.onServer);
223
+ }, [api]);
224
+ return React.useMemo(() => ({ api, i18n, error, submit }), [api, error, submit, i18n.t, i18n.prefix, i18n.apiPrefix, i18n.locale]);
225
+ }
226
+ var UNCHECKED = /* @__PURE__ */ Symbol("unchecked");
227
+ function useKrizakaField(name, options = {}) {
228
+ const ctx = useFormContext();
229
+ const id = React.useId();
230
+ const [checked, setChecked] = React.useState(UNCHECKED);
231
+ const check = options.validateAsync;
232
+ const field = useField({
233
+ form: ctx.api,
234
+ name,
235
+ ...check ? {
236
+ asyncDebounceMs: options.asyncDebounceMs ?? 350,
237
+ validators: {
238
+ onChangeAsyncDebounceMs: options.asyncDebounceMs ?? 350,
239
+ onChangeAsync: async ({ value: value2, signal }) => {
240
+ const answer = await check(value2, signal);
241
+ if (signal.aborted) return void 0;
242
+ if (!answer) {
243
+ setChecked(value2);
244
+ return void 0;
245
+ }
246
+ setChecked(UNCHECKED);
247
+ const error = typeof answer === "string" ? { key: answer } : answer;
248
+ return { ...error, immediate: true, source: "check" };
249
+ }
250
+ }
251
+ } : {}
252
+ });
253
+ const attempts = useStore(ctx.api.store, (state) => state.submissionAttempts);
254
+ const meta = field.state.meta;
255
+ const map = meta.errorMap;
256
+ const all = [map.onServer, map.onChange, map.onBlur, map.onSubmit, map.onMount].filter(Boolean);
257
+ const first = all[0];
258
+ const detail = typeof first === "string" ? { key: first } : first;
259
+ const visible = detail && (detail.immediate || detail.source === "server" || meta.isBlurred || attempts > 0) ? detail : void 0;
260
+ const value = field.state.value;
261
+ return {
262
+ id,
263
+ name,
264
+ value,
265
+ setValue: (next) => {
266
+ if (map.onServer) ctx.api.setFieldMeta(name, (m) => ({ ...m, errorMap: { ...m.errorMap, onServer: void 0 } }));
267
+ field.handleChange(next);
268
+ },
269
+ onBlur: () => field.handleBlur(),
270
+ error: visible ? formMessage(visible, ctx.i18n) : void 0,
271
+ errorDetail: visible,
272
+ validating: Boolean(meta.isValidating),
273
+ available: Boolean(check) && checked !== UNCHECKED && Object.is(checked, value) && !meta.isValidating && all.length === 0,
274
+ hintId: `${id}-hint`,
275
+ errorId: `${id}-error`,
276
+ locale: ctx.i18n.locale ?? "en",
277
+ api: field
278
+ };
279
+ }
280
+ function useKrizakaFieldArray(name) {
281
+ const ctx = useFormContext();
282
+ const field = useField({ form: ctx.api, name, mode: "array" });
283
+ const attempts = useStore(ctx.api.store, (state) => state.submissionAttempts);
284
+ const map = field.state.meta.errorMap;
285
+ const first = map.onServer ?? map.onChange ?? map.onSubmit;
286
+ return {
287
+ name,
288
+ items: field.state.value ?? [],
289
+ push: (item) => field.pushValue(item),
290
+ remove: (index) => void field.removeValue(index),
291
+ move: (from, to) => field.moveValue(from, to),
292
+ fieldName: (index, key) => `${name}[${index}]${key ? `.${key}` : ""}`,
293
+ error: first && (attempts > 0 || first.source === "server" || field.state.meta.isDirty) ? formMessage(first, ctx.i18n) : void 0
294
+ };
295
+ }
296
+ function useFormValue(name) {
297
+ const ctx = useFormContext();
298
+ return useStore(ctx.api.store, (state) => getPath(state.values, name));
299
+ }
300
+ function useFormStatus() {
301
+ const ctx = useFormContext();
302
+ const submitting = useStore(ctx.api.store, (s) => s.isSubmitting);
303
+ const succeeded = useStore(ctx.api.store, (s) => s.isSubmitSuccessful);
304
+ const attempts = useStore(ctx.api.store, (s) => s.submissionAttempts);
305
+ return { submitting, succeeded: succeeded && !ctx.error, attempts };
306
+ }
307
+ function getPath(source, path) {
308
+ let current = source;
309
+ for (const part of path.replace(/\[(\d+)\]/g, ".$1").split(".")) {
310
+ if (current === null || typeof current !== "object") return void 0;
311
+ current = current[part];
312
+ }
313
+ return current;
314
+ }
315
+ function makeCreateKrizakaForm(components, Root) {
316
+ return function createKrizakaForm2(definition) {
317
+ function useForm2(options) {
318
+ const ctx = useKrizakaFormCore(definition, options);
319
+ const [store] = React.useState(() => createStore(ctx));
320
+ React.useLayoutEffect(() => store.set(ctx), [store, ctx]);
321
+ const Form = React.useCallback((props) => /* @__PURE__ */ jsx(Bound, { store, Root, props }), [store]);
322
+ return React.useMemo(
323
+ () => ({
324
+ ...components,
325
+ Form,
326
+ submit: ctx.submit,
327
+ reset: (values) => ctx.api.reset(values),
328
+ setValue: (name, value) => ctx.api.setFieldValue(name, value),
329
+ getValues: () => ctx.api.state.values,
330
+ error: ctx.error,
331
+ api: ctx.api
332
+ }),
333
+ [ctx, Form]
334
+ );
335
+ }
336
+ return {
337
+ schema: definition.schema,
338
+ defaultValues: definition.defaultValues,
339
+ useForm: useForm2,
340
+ useFormWithAction: (action, options) => useForm2({ ...options, onSubmit: action })
341
+ };
342
+ };
343
+ }
344
+ function createStore(initial) {
345
+ let value = initial;
346
+ const listeners = /* @__PURE__ */ new Set();
347
+ return {
348
+ get: () => value,
349
+ set: (next) => {
350
+ if (Object.is(next, value)) return;
351
+ value = next;
352
+ listeners.forEach((listener) => listener());
353
+ },
354
+ subscribe: (listener) => {
355
+ listeners.add(listener);
356
+ return () => listeners.delete(listener);
357
+ }
358
+ };
359
+ }
360
+ function Bound({ store, Root, props }) {
361
+ const value = React.useSyncExternalStore(store.subscribe, store.get, store.get);
362
+ return /* @__PURE__ */ jsx(Root, { ...props, value });
363
+ }
364
+
365
+ // src/form/money.ts
366
+ import { currencyDigits } from "@krizaka/intl";
367
+ function decimalSeparator(locale) {
368
+ return new Intl.NumberFormat(locale).formatToParts(1.5).find((p) => p.type === "decimal")?.value ?? ".";
369
+ }
370
+ function currencySymbol(currency, locale) {
371
+ return new Intl.NumberFormat(locale, { style: "currency", currency }).formatToParts(0).find((p) => p.type === "currency")?.value ?? currency;
372
+ }
373
+ function parseMoney(text, currency, locale = "en") {
374
+ const digits = currencyDigits(currency);
375
+ const t = text.replace(/[\s\u00a0\u202f']/g, "");
376
+ if (!t) return void 0;
377
+ const negative = t.startsWith("-");
378
+ const body = negative ? t.slice(1) : t;
379
+ if (!/^[\d.,]+$/.test(body)) return null;
380
+ let int = body;
381
+ let frac = "";
382
+ const last = Math.max(body.lastIndexOf("."), body.lastIndexOf(","));
383
+ if (last >= 0) {
384
+ const tail = body.slice(last + 1);
385
+ if (body[last] === decimalSeparator(locale) || tail.length !== 3) {
386
+ if (tail.length > digits || !/^\d*$/.test(tail)) return null;
387
+ int = body.slice(0, last);
388
+ frac = tail;
389
+ }
390
+ }
391
+ if (!/^(\d*|\d{1,3}([.,]\d{3})+)$/.test(int)) return null;
392
+ int = int.replace(/[.,]/g, "");
393
+ if (!int && !frac) return null;
394
+ const units = BigInt(int || "0") * 10n ** BigInt(digits) + BigInt((frac || "").padEnd(digits, "0") || "0");
395
+ const value = Number(negative ? -units : units);
396
+ return Number.isSafeInteger(value) ? value : null;
397
+ }
398
+ function moneyInputText(minorUnits, currency, locale = "en") {
399
+ if (minorUnits === null || minorUnits === void 0 || !Number.isFinite(minorUnits)) return "";
400
+ const digits = currencyDigits(currency);
401
+ const sign = minorUnits < 0 ? "-" : "";
402
+ const abs = String(Math.abs(Math.trunc(minorUnits))).padStart(digits + 1, "0");
403
+ return digits === 0 ? sign + abs : `${sign}${abs.slice(0, -digits)}${decimalSeparator(locale)}${abs.slice(-digits)}`;
404
+ }
405
+
406
+ // src/form/web.tsx
407
+ import { Alert } from "@krizaka/ui/alert";
408
+ import { Button } from "@krizaka/ui/button";
409
+ import { Checkbox as CheckboxControl } from "@krizaka/ui/checkbox";
410
+ import { Combobox as ComboboxControl } from "@krizaka/ui/combobox";
411
+ import { DatePicker } from "@krizaka/ui/date-picker";
412
+ import { Field, Input, Select as SelectControl, Textarea } from "@krizaka/ui/field";
413
+ import { Dropzone, formatBytes } from "@krizaka/ui/file-upload";
414
+ import { RadioGroup as RadioGroupControl } from "@krizaka/ui/radio-group";
415
+ import { Slider as SliderControl } from "@krizaka/ui/slider";
416
+ import { Spinner } from "@krizaka/ui/spinner";
417
+ import { Switch as SwitchControl } from "@krizaka/ui/switch";
418
+ import { TagInput as TagInputControl } from "@krizaka/ui/tag-input";
419
+ import * as React2 from "react";
420
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
421
+ var ids = (f, hint) => [hint && !f.error ? f.hintId : null, f.error ? f.errorId : null].filter(Boolean).join(" ") || void 0;
422
+ function Caption({ field, hint, ok }) {
423
+ const suggestion = field.errorDetail?.suggestion;
424
+ if (field.error)
425
+ return /* @__PURE__ */ jsx2(Field.Error, { id: field.errorId, children: /* @__PURE__ */ jsxs("span", { children: [
426
+ field.error,
427
+ suggestion ? /* @__PURE__ */ jsxs(Fragment, { children: [
428
+ " ",
429
+ /* @__PURE__ */ jsx2("button", { type: "button", className: "font-semibold text-accent-text underline underline-offset-2", onClick: () => field.setValue(suggestion.value), children: suggestion.label })
430
+ ] }) : null
431
+ ] }) });
432
+ const text = field.available && ok ? hint ? `${ok} \xB7 ${hint}` : ok : hint;
433
+ return text ? /* @__PURE__ */ jsx2(Field.Hint, { id: field.hintId, children: text }) : null;
434
+ }
435
+ var Check = () => /* @__PURE__ */ jsx2("svg", { viewBox: "0 0 24 24", className: "h-4 w-4 text-fg-success", fill: "none", stroke: "currentColor", strokeWidth: "2.5", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": true, children: /* @__PURE__ */ jsx2("path", { d: "M20 6 9 17l-5-5" }) });
436
+ function FormRoot({ value, className, children, ...props }) {
437
+ const ref = React2.useRef(null);
438
+ return /* @__PURE__ */ jsx2(FormContext.Provider, { value, children: /* @__PURE__ */ jsx2(
439
+ "form",
440
+ {
441
+ ref,
442
+ noValidate: true,
443
+ className: className ?? "flex flex-col gap-4",
444
+ onSubmit: async (event) => {
445
+ event.preventDefault();
446
+ if (!await value.submit()) requestAnimationFrame(() => ref.current?.querySelector('[aria-invalid="true"], [data-invalid] input')?.focus());
447
+ },
448
+ ...props,
449
+ children
450
+ }
451
+ ) });
452
+ }
453
+ function Submit({ variant = "primary", children, ...props }) {
454
+ const { submitting } = useFormStatus();
455
+ return /* @__PURE__ */ jsx2(Button, { type: "submit", variant, loading: submitting, ...props, children });
456
+ }
457
+ function FormError({ title, className }) {
458
+ const ctx = useFormContext();
459
+ if (!ctx.error) return null;
460
+ return /* @__PURE__ */ jsx2(Alert, { tone: "danger", title, className, children: formMessage(ctx.error, ctx.i18n) });
461
+ }
462
+ function TextField({ name, label, hint, disabled, className, type = "text", transform, validateAsync, asyncDebounceMs, availableHint, checkingLabel = "Checking", start, ...input }) {
463
+ const f = useKrizakaField(name, { validateAsync, asyncDebounceMs });
464
+ const end = f.validating ? /* @__PURE__ */ jsx2(Spinner, { size: "sm", label: checkingLabel }) : f.available ? /* @__PURE__ */ jsx2(Check, {}) : null;
465
+ return /* @__PURE__ */ jsxs(Field.Root, { className, children: [
466
+ /* @__PURE__ */ jsx2(Field.Label, { htmlFor: f.id, children: label }),
467
+ /* @__PURE__ */ jsxs("span", { className: "relative flex items-center", children: [
468
+ start ? /* @__PURE__ */ jsx2("span", { className: "pointer-events-none absolute left-3 text-fg-muted", children: start }) : null,
469
+ /* @__PURE__ */ jsx2(
470
+ Input,
471
+ {
472
+ id: f.id,
473
+ name,
474
+ type,
475
+ value: f.value ?? "",
476
+ disabled,
477
+ invalid: Boolean(f.error),
478
+ "aria-describedby": ids(f, hint ?? availableHint),
479
+ onChange: (event) => f.setValue(transform ? transform(event.target.value) : event.target.value),
480
+ onBlur: f.onBlur,
481
+ className: [start ? "pl-9" : "", end ? "pr-10" : ""].join(" ").trim() || void 0,
482
+ ...input
483
+ }
484
+ ),
485
+ end ? /* @__PURE__ */ jsx2("span", { className: "absolute right-3 flex", children: end }) : null
486
+ ] }),
487
+ /* @__PURE__ */ jsx2(Caption, { field: f, hint, ok: availableHint })
488
+ ] });
489
+ }
490
+ function TextArea({ name, label, hint, disabled, className, rows = 4, ...input }) {
491
+ const f = useKrizakaField(name);
492
+ return /* @__PURE__ */ jsxs(Field.Root, { className, children: [
493
+ /* @__PURE__ */ jsx2(Field.Label, { htmlFor: f.id, children: label }),
494
+ /* @__PURE__ */ jsx2(Textarea, { id: f.id, name, rows, value: f.value ?? "", disabled, invalid: Boolean(f.error), "aria-describedby": ids(f, hint), onChange: (e) => f.setValue(e.target.value), onBlur: f.onBlur, ...input }),
495
+ /* @__PURE__ */ jsx2(Caption, { field: f, hint })
496
+ ] });
497
+ }
498
+ function Checkbox({ name, label, hint, disabled, className }) {
499
+ const f = useKrizakaField(name);
500
+ return /* @__PURE__ */ jsxs("div", { className: className ?? "flex flex-col gap-1.5", children: [
501
+ /* @__PURE__ */ jsx2(CheckboxControl, { id: f.id, name, checked: f.value === true, disabled, invalid: Boolean(f.error), "aria-describedby": ids(f, hint), onCheckedChange: (c) => f.setValue(c === true), onBlur: f.onBlur, children: label }),
502
+ /* @__PURE__ */ jsx2(Caption, { field: f, hint })
503
+ ] });
504
+ }
505
+ function Switch({ name, label, hint, disabled, className }) {
506
+ const f = useKrizakaField(name);
507
+ return /* @__PURE__ */ jsxs("div", { className: className ?? "flex items-start justify-between gap-4", children: [
508
+ /* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-col gap-0.5", children: [
509
+ /* @__PURE__ */ jsx2(Field.Label, { htmlFor: f.id, className: "text-sm font-medium text-fg", children: label }),
510
+ /* @__PURE__ */ jsx2(Caption, { field: f, hint })
511
+ ] }),
512
+ /* @__PURE__ */ jsx2(SwitchControl, { id: f.id, name, checked: f.value === true, disabled, invalid: Boolean(f.error), "aria-describedby": ids(f, hint), onCheckedChange: (c) => f.setValue(c), onBlur: f.onBlur })
513
+ ] });
514
+ }
515
+ function RadioGroup({ name, label, hint, disabled, className, options, variant = "list", orientation = "vertical" }) {
516
+ const f = useKrizakaField(name);
517
+ return /* @__PURE__ */ jsxs(Field.Root, { className, role: "group", "aria-labelledby": `${f.id}-label`, children: [
518
+ /* @__PURE__ */ jsx2("span", { id: `${f.id}-label`, className: "text-xs font-semibold text-fg-secondary", children: label }),
519
+ /* @__PURE__ */ jsx2(
520
+ RadioGroupControl.Root,
521
+ {
522
+ "aria-labelledby": `${f.id}-label`,
523
+ value: f.value ?? "",
524
+ onValueChange: (v) => f.setValue(v),
525
+ onBlur: f.onBlur,
526
+ orientation,
527
+ disabled,
528
+ invalid: Boolean(f.error),
529
+ "aria-describedby": ids(f, hint),
530
+ name,
531
+ className: variant === "cards" ? "grid grid-cols-[repeat(auto-fit,minmax(9rem,1fr))] gap-2" : void 0,
532
+ children: options.map(
533
+ (o) => variant === "cards" ? /* @__PURE__ */ jsxs(RadioGroupControl.Card, { value: o.value, disabled: o.disabled, children: [
534
+ /* @__PURE__ */ jsx2("span", { className: "font-semibold", children: o.label }),
535
+ o.description ? /* @__PURE__ */ jsx2("span", { className: "text-xs text-fg-secondary", children: o.description }) : null
536
+ ] }, o.value) : /* @__PURE__ */ jsxs(RadioGroupControl.Item, { value: o.value, disabled: o.disabled, children: [
537
+ o.label,
538
+ o.description ? /* @__PURE__ */ jsx2("span", { className: "block text-xs text-fg-secondary", children: o.description }) : null
539
+ ] }, o.value)
540
+ )
541
+ }
542
+ ),
543
+ /* @__PURE__ */ jsx2(Caption, { field: f, hint })
544
+ ] });
545
+ }
546
+ function Select({ name, label, hint, disabled, className, options, placeholder }) {
547
+ const f = useKrizakaField(name);
548
+ return /* @__PURE__ */ jsxs(Field.Root, { className, children: [
549
+ /* @__PURE__ */ jsx2(Field.Label, { htmlFor: f.id, children: label }),
550
+ /* @__PURE__ */ jsxs(SelectControl, { id: f.id, name, value: f.value ?? "", disabled, invalid: Boolean(f.error), "aria-describedby": ids(f, hint), onChange: (e) => f.setValue(e.target.value), onBlur: f.onBlur, children: [
551
+ placeholder !== void 0 ? /* @__PURE__ */ jsx2("option", { value: "", disabled: true, children: placeholder }) : null,
552
+ options.map((o) => /* @__PURE__ */ jsx2("option", { value: o.value, disabled: o.disabled, children: o.label }, o.value))
553
+ ] }),
554
+ /* @__PURE__ */ jsx2(Caption, { field: f, hint })
555
+ ] });
556
+ }
557
+ function Combobox({ name, label, hint, disabled, className, options = [], onSearch, multiple, creatable, placeholder, labels }) {
558
+ const f = useKrizakaField(name);
559
+ const [seen, setSeen] = React2.useState({});
560
+ const option = (value) => options.find((o) => o.value === value) ?? seen[value] ?? { value, label: value };
561
+ const remember = (o) => setSeen((known) => known[o.value] === o ? known : { ...known, [o.value]: o });
562
+ const shared = { id: f.id, label, hint, error: f.error, disabled, placeholder, labels, options, onSearch, creatable, name };
563
+ return /* @__PURE__ */ jsx2("div", { className, onBlur: (e) => !e.currentTarget.contains(e.relatedTarget) && f.onBlur(), children: multiple ? /* @__PURE__ */ jsx2(
564
+ ComboboxControl,
565
+ {
566
+ ...shared,
567
+ multiple: true,
568
+ value: (f.value ?? []).map(option),
569
+ onValueChange: (next) => {
570
+ next.forEach(remember);
571
+ f.setValue(next.map((o) => o.value));
572
+ }
573
+ }
574
+ ) : /* @__PURE__ */ jsx2(
575
+ ComboboxControl,
576
+ {
577
+ ...shared,
578
+ value: f.value ? option(f.value) : null,
579
+ onValueChange: (next) => {
580
+ if (next) remember(next);
581
+ f.setValue(next?.value ?? "");
582
+ }
583
+ }
584
+ ) });
585
+ }
586
+ var PART_NAMES = { day: "Day", month: "Month", year: "Year" };
587
+ function DateField({ name, label, hint, disabled, className, entry = "calendar", min, max, labels, autoComplete }) {
588
+ const f = useKrizakaField(name);
589
+ if (entry === "calendar")
590
+ return /* @__PURE__ */ jsx2("div", { className, onBlur: (e) => !e.currentTarget.contains(e.relatedTarget) && f.onBlur(), children: /* @__PURE__ */ jsx2(DatePicker, { label, hint, error: f.error, disabled, name, locale: f.locale, min, max, labels, value: f.value || null, onValueChange: (v) => f.setValue(v ?? "") }) });
591
+ return /* @__PURE__ */ jsx2(DateParts, { field: f, label, hint, disabled, className, names: { ...PART_NAMES, ...labels }, autoComplete });
592
+ }
593
+ function DateParts({ field: f, label, hint, disabled, className, names, autoComplete }) {
594
+ const order = React2.useMemo(() => datePartsOrder(f.locale), [f.locale]);
595
+ const [parts, setParts] = React2.useState(() => splitDate(f.value));
596
+ if ((f.value ?? "") !== composeDate(parts) && composeDate(splitDate(f.value)) === (f.value ?? "")) setParts(splitDate(f.value));
597
+ const refs = { day: React2.useRef(null), month: React2.useRef(null), year: React2.useRef(null) };
598
+ return /* @__PURE__ */ jsxs("fieldset", { className: className ?? "flex flex-col gap-1.5", "aria-describedby": ids(f, hint), onBlur: (e) => !e.currentTarget.contains(e.relatedTarget) && f.onBlur(), children: [
599
+ /* @__PURE__ */ jsx2("legend", { className: "mb-1.5 text-xs font-semibold text-fg-secondary", children: label }),
600
+ /* @__PURE__ */ jsx2("div", { className: "flex gap-2", children: order.map((part) => /* @__PURE__ */ jsxs("label", { className: "flex flex-col gap-1 text-xs text-fg-secondary", children: [
601
+ names[part],
602
+ /* @__PURE__ */ jsx2(
603
+ Input,
604
+ {
605
+ ref: refs[part],
606
+ inputMode: "numeric",
607
+ autoComplete: autoComplete === "bday" ? `bday-${part}` : "off",
608
+ maxLength: DATE_PART_LENGTH[part],
609
+ value: parts[part],
610
+ disabled,
611
+ invalid: Boolean(f.error),
612
+ className: part === "year" ? "w-20 text-center tabular-nums" : "w-14 text-center tabular-nums",
613
+ onChange: (e) => {
614
+ const digits = e.target.value.replace(/\D/g, "").slice(0, DATE_PART_LENGTH[part]);
615
+ const next = { ...parts, [part]: digits };
616
+ setParts(next);
617
+ f.setValue(composeDate(next));
618
+ const following = order[order.indexOf(part) + 1];
619
+ if (digits.length === DATE_PART_LENGTH[part] && following) refs[following].current?.focus();
620
+ }
621
+ }
622
+ )
623
+ ] }, part)) }),
624
+ /* @__PURE__ */ jsx2(Caption, { field: f, hint })
625
+ ] });
626
+ }
627
+ function TagInput({ name, label, hint, disabled, className, ...props }) {
628
+ const f = useKrizakaField(name);
629
+ return /* @__PURE__ */ jsx2("div", { className, onBlur: (e) => !e.currentTarget.contains(e.relatedTarget) && f.onBlur(), children: /* @__PURE__ */ jsx2(TagInputControl, { label, hint, error: f.error, disabled, name, value: f.value ?? [], onValueChange: (tags) => f.setValue(tags), ...props }) });
630
+ }
631
+ function FileUpload({ name, label, hint, disabled, className, title, browseLabel, accept, maxSize, multiple, maxFiles, removeLabel, refusalLabel }) {
632
+ const f = useKrizakaField(name);
633
+ const files = f.value ?? [];
634
+ return /* @__PURE__ */ jsxs("div", { className: className ?? "flex flex-col gap-2", role: "group", "aria-label": label, onBlur: (e) => !e.currentTarget.contains(e.relatedTarget) && f.onBlur(), children: [
635
+ /* @__PURE__ */ jsx2("span", { className: "text-xs font-semibold text-fg-secondary", children: label }),
636
+ /* @__PURE__ */ jsx2(
637
+ Dropzone,
638
+ {
639
+ title,
640
+ browseLabel,
641
+ hint,
642
+ accept,
643
+ maxSize,
644
+ multiple,
645
+ maxFiles,
646
+ disabled,
647
+ refusalLabel,
648
+ onFiles: (chosen) => f.setValue(multiple ? [...files, ...chosen].slice(0, maxFiles ?? Infinity) : chosen.slice(0, 1))
649
+ }
650
+ ),
651
+ files.length > 0 ? /* @__PURE__ */ jsx2("ul", { className: "flex flex-col gap-1.5", children: files.map((file, i) => /* @__PURE__ */ jsxs("li", { className: "flex items-center justify-between gap-3 rounded-lg border border-border-default bg-surface-1 px-3 py-2 text-sm", children: [
652
+ /* @__PURE__ */ jsx2("span", { className: "min-w-0 truncate text-fg", children: file.name }),
653
+ /* @__PURE__ */ jsxs("span", { className: "flex shrink-0 items-center gap-2", children: [
654
+ /* @__PURE__ */ jsx2("span", { className: "font-mono text-xs text-fg-secondary", children: formatBytes(file.size, f.locale) }),
655
+ /* @__PURE__ */ jsx2(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => f.setValue(files.filter((_, j) => j !== i)), children: removeLabel(file.name) })
656
+ ] })
657
+ ] }, `${file.name}-${i}`)) }) : null,
658
+ f.error ? /* @__PURE__ */ jsx2(Field.Error, { id: f.errorId, children: f.error }) : null
659
+ ] });
660
+ }
661
+ function Money({ name, label, hint, disabled, className, currency, placeholder }) {
662
+ const f = useKrizakaField(name);
663
+ const [text, setText] = React2.useState(() => moneyInputText(f.value, currency, f.locale));
664
+ const [editing, setEditing] = React2.useState(false);
665
+ const shown = editing ? text : moneyInputText(f.value, currency, f.locale);
666
+ return /* @__PURE__ */ jsxs(Field.Root, { className, children: [
667
+ /* @__PURE__ */ jsx2(Field.Label, { htmlFor: f.id, children: label }),
668
+ /* @__PURE__ */ jsxs("span", { className: "relative flex items-center", children: [
669
+ /* @__PURE__ */ jsx2("span", { className: "pointer-events-none absolute left-3 text-sm text-fg-secondary", "aria-hidden": true, children: currencySymbol(currency, f.locale) }),
670
+ /* @__PURE__ */ jsx2(
671
+ Input,
672
+ {
673
+ id: f.id,
674
+ name,
675
+ inputMode: "decimal",
676
+ value: shown,
677
+ placeholder,
678
+ disabled,
679
+ invalid: Boolean(f.error),
680
+ "aria-describedby": ids(f, hint),
681
+ className: "pl-9 tabular-nums",
682
+ onFocus: () => {
683
+ setText(moneyInputText(f.value, currency, f.locale));
684
+ setEditing(true);
685
+ },
686
+ onChange: (e) => {
687
+ setText(e.target.value);
688
+ const units = parseMoney(e.target.value, currency, f.locale);
689
+ f.setValue(units === void 0 ? null : units === null ? Number.NaN : units);
690
+ },
691
+ onBlur: () => {
692
+ setEditing(false);
693
+ f.onBlur();
694
+ }
695
+ }
696
+ )
697
+ ] }),
698
+ /* @__PURE__ */ jsx2(Caption, { field: f, hint })
699
+ ] });
700
+ }
701
+ function Slider({ name, label, hint, disabled, className, min, max, step, formatValue, thumbLabels }) {
702
+ const f = useKrizakaField(name);
703
+ return /* @__PURE__ */ jsxs("div", { className: className ?? "flex flex-col gap-1.5", children: [
704
+ /* @__PURE__ */ jsx2(SliderControl, { label, showLabel: true, value: f.value, min, max, step, disabled, formatValue, thumbLabels, "aria-describedby": ids(f, hint), onValueChange: (v) => f.setValue(v), onValueCommit: () => f.onBlur() }),
705
+ /* @__PURE__ */ jsx2(Caption, { field: f, hint })
706
+ ] });
707
+ }
708
+ function FormField({ name, validateAsync, asyncDebounceMs, children }) {
709
+ return /* @__PURE__ */ jsx2(Fragment, { children: children(useKrizakaField(name, { validateAsync, asyncDebounceMs })) });
710
+ }
711
+ function FieldArray({ name, children }) {
712
+ return /* @__PURE__ */ jsx2(Fragment, { children: children(useKrizakaFieldArray(name)) });
713
+ }
714
+ function Watch({ name, children }) {
715
+ return /* @__PURE__ */ jsx2(Fragment, { children: children(useFormValue(name)) });
716
+ }
717
+ var fields = { TextField, TextArea, Checkbox, Switch, RadioGroup, Select, Combobox, DateField, TagInput, FileUpload, Money, Slider, Field: FormField, FieldArray, Watch, Submit, FormError };
718
+ var createKrizakaForm = makeCreateKrizakaForm(fields, FormRoot);
719
+ function useFormWithAction(form, ...args) {
720
+ return form.useFormWithAction(...args);
721
+ }
722
+ export {
723
+ Checkbox,
724
+ Combobox,
725
+ DateField,
726
+ FieldArray,
727
+ FileUpload,
728
+ FormError,
729
+ FormField,
730
+ Money,
731
+ RadioGroup,
732
+ Select,
733
+ Slider,
734
+ Submit,
735
+ Switch,
736
+ TagInput,
737
+ TextArea,
738
+ TextField,
739
+ Watch,
740
+ composeDate,
741
+ createKrizakaForm,
742
+ currencySymbol,
743
+ datePartsOrder,
744
+ formMessage,
745
+ formMessages,
746
+ isRealDate,
747
+ issueError,
748
+ issuesToFields,
749
+ latestBirthDate,
750
+ moneyInputText,
751
+ parseMoney,
752
+ problemToErrors,
753
+ useFormContext,
754
+ useFormStatus,
755
+ useFormValue,
756
+ useFormWithAction,
757
+ useKrizakaField,
758
+ useKrizakaFieldArray
759
+ };
760
+ //# sourceMappingURL=form.js.map