@assistant-ui/react-generative-ui 0.0.23 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (338) hide show
  1. package/README.md +2 -0
  2. package/dist/a2ui.d.ts +1 -4
  3. package/dist/a2ui.d.ts.map +1 -1
  4. package/dist/a2ui.js +1 -4
  5. package/dist/index.d.ts +1 -2
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -10
  8. package/dist/index.server.d.ts +1 -2
  9. package/dist/index.server.d.ts.map +1 -1
  10. package/dist/index.server.js +1 -10
  11. package/dist/ir.d.ts +1 -111
  12. package/dist/ir.d.ts.map +1 -1
  13. package/dist/ir.js +1 -175
  14. package/dist/slack.d.ts +1 -4
  15. package/dist/slack.d.ts.map +1 -1
  16. package/dist/slack.js +1 -4
  17. package/dist/teams.d.ts +1 -4
  18. package/dist/teams.d.ts.map +1 -1
  19. package/dist/teams.js +1 -4
  20. package/package.json +5 -5
  21. package/src/a2ui.ts +1 -22
  22. package/src/index.server.ts +1 -2
  23. package/src/index.test.ts +75 -0
  24. package/src/index.ts +1 -2
  25. package/src/ir.ts +1 -279
  26. package/src/slack.ts +1 -39
  27. package/src/teams.ts +1 -35
  28. package/dist/JSONGenerativeUI.client.d.ts +0 -28
  29. package/dist/JSONGenerativeUI.client.d.ts.map +0 -1
  30. package/dist/JSONGenerativeUI.client.js +0 -101
  31. package/dist/JSONGenerativeUI.client.js.map +0 -1
  32. package/dist/JSONGenerativeUI.server.d.ts +0 -21
  33. package/dist/JSONGenerativeUI.server.d.ts.map +0 -1
  34. package/dist/JSONGenerativeUI.server.js +0 -32
  35. package/dist/JSONGenerativeUI.server.js.map +0 -1
  36. package/dist/JSONGenerativeUI.shared.d.ts +0 -63
  37. package/dist/JSONGenerativeUI.shared.d.ts.map +0 -1
  38. package/dist/JSONGenerativeUI.shared.js +0 -31
  39. package/dist/JSONGenerativeUI.shared.js.map +0 -1
  40. package/dist/RadioGroupScope.d.ts +0 -6
  41. package/dist/RadioGroupScope.d.ts.map +0 -1
  42. package/dist/RadioGroupScope.js +0 -25
  43. package/dist/RadioGroupScope.js.map +0 -1
  44. package/dist/a2ui/BindingContext.d.ts +0 -11
  45. package/dist/a2ui/BindingContext.d.ts.map +0 -1
  46. package/dist/a2ui/BindingContext.js +0 -12
  47. package/dist/a2ui/BindingContext.js.map +0 -1
  48. package/dist/a2ui/PresentRenderer.d.ts +0 -11
  49. package/dist/a2ui/PresentRenderer.d.ts.map +0 -1
  50. package/dist/a2ui/PresentRenderer.js +0 -39
  51. package/dist/a2ui/PresentRenderer.js.map +0 -1
  52. package/dist/a2ui/convert.d.ts +0 -15
  53. package/dist/a2ui/convert.d.ts.map +0 -1
  54. package/dist/a2ui/convert.js +0 -728
  55. package/dist/a2ui/convert.js.map +0 -1
  56. package/dist/a2ui/dataModel.d.ts +0 -9
  57. package/dist/a2ui/dataModel.d.ts.map +0 -1
  58. package/dist/a2ui/dataModel.js +0 -44
  59. package/dist/a2ui/dataModel.js.map +0 -1
  60. package/dist/a2ui/reducer.d.ts +0 -8
  61. package/dist/a2ui/reducer.d.ts.map +0 -1
  62. package/dist/a2ui/reducer.js +0 -242
  63. package/dist/a2ui/reducer.js.map +0 -1
  64. package/dist/a2ui/snapshot.d.ts +0 -3
  65. package/dist/a2ui/snapshot.d.ts.map +0 -1
  66. package/dist/a2ui/snapshot.js +0 -33
  67. package/dist/a2ui/snapshot.js.map +0 -1
  68. package/dist/a2ui/types.d.ts +0 -88
  69. package/dist/a2ui/types.d.ts.map +0 -1
  70. package/dist/a2ui/types.js +0 -6
  71. package/dist/a2ui/types.js.map +0 -1
  72. package/dist/a2ui/valueFunctions.d.ts +0 -16
  73. package/dist/a2ui/valueFunctions.d.ts.map +0 -1
  74. package/dist/a2ui/valueFunctions.js +0 -332
  75. package/dist/a2ui/valueFunctions.js.map +0 -1
  76. package/dist/actionRegistry.d.ts +0 -29
  77. package/dist/actionRegistry.d.ts.map +0 -1
  78. package/dist/actionRegistry.js +0 -41
  79. package/dist/actionRegistry.js.map +0 -1
  80. package/dist/answeredValues.d.ts +0 -16
  81. package/dist/answeredValues.d.ts.map +0 -1
  82. package/dist/answeredValues.js +0 -37
  83. package/dist/answeredValues.js.map +0 -1
  84. package/dist/buildPresentParameters.d.ts +0 -21
  85. package/dist/buildPresentParameters.d.ts.map +0 -1
  86. package/dist/buildPresentParameters.js +0 -95
  87. package/dist/buildPresentParameters.js.map +0 -1
  88. package/dist/constants.d.ts +0 -15
  89. package/dist/constants.d.ts.map +0 -1
  90. package/dist/constants.js +0 -19
  91. package/dist/constants.js.map +0 -1
  92. package/dist/convert/boundSpec.d.ts +0 -48
  93. package/dist/convert/boundSpec.d.ts.map +0 -1
  94. package/dist/convert/boundSpec.js +0 -108
  95. package/dist/convert/boundSpec.js.map +0 -1
  96. package/dist/convert/copyBounded.d.ts +0 -25
  97. package/dist/convert/copyBounded.d.ts.map +0 -1
  98. package/dist/convert/copyBounded.js +0 -38
  99. package/dist/convert/copyBounded.js.map +0 -1
  100. package/dist/convert/isElement.d.ts +0 -3
  101. package/dist/convert/isElement.d.ts.map +0 -1
  102. package/dist/convert/isElement.js +0 -6
  103. package/dist/convert/isElement.js.map +0 -1
  104. package/dist/convert/takeRun.d.ts +0 -6
  105. package/dist/convert/takeRun.d.ts.map +0 -1
  106. package/dist/convert/takeRun.js +0 -20
  107. package/dist/convert/takeRun.js.map +0 -1
  108. package/dist/defineGenerativeComponents.d.ts +0 -29
  109. package/dist/defineGenerativeComponents.d.ts.map +0 -1
  110. package/dist/defineGenerativeComponents.js +0 -34
  111. package/dist/defineGenerativeComponents.js.map +0 -1
  112. package/dist/fieldReferences.d.ts +0 -9
  113. package/dist/fieldReferences.d.ts.map +0 -1
  114. package/dist/fieldReferences.js +0 -53
  115. package/dist/fieldReferences.js.map +0 -1
  116. package/dist/generativeUIToJSX.d.ts +0 -18
  117. package/dist/generativeUIToJSX.d.ts.map +0 -1
  118. package/dist/generativeUIToJSX.js +0 -76
  119. package/dist/generativeUIToJSX.js.map +0 -1
  120. package/dist/index.shared.d.ts +0 -15
  121. package/dist/index.shared.d.ts.map +0 -1
  122. package/dist/index.shared.js +0 -9
  123. package/dist/ir.js.map +0 -1
  124. package/dist/renderGenerativeUI.d.ts +0 -14
  125. package/dist/renderGenerativeUI.d.ts.map +0 -1
  126. package/dist/renderGenerativeUI.js +0 -83
  127. package/dist/renderGenerativeUI.js.map +0 -1
  128. package/dist/scopeSchema.d.ts +0 -6
  129. package/dist/scopeSchema.d.ts.map +0 -1
  130. package/dist/scopeSchema.js +0 -56
  131. package/dist/scopeSchema.js.map +0 -1
  132. package/dist/slack/constants.d.ts +0 -93
  133. package/dist/slack/constants.d.ts.map +0 -1
  134. package/dist/slack/constants.js +0 -146
  135. package/dist/slack/constants.js.map +0 -1
  136. package/dist/slack/decodeBlockAction.d.ts +0 -10
  137. package/dist/slack/decodeBlockAction.d.ts.map +0 -1
  138. package/dist/slack/decodeBlockAction.js +0 -129
  139. package/dist/slack/decodeBlockAction.js.map +0 -1
  140. package/dist/slack/fromSlackBlocks.d.ts +0 -27
  141. package/dist/slack/fromSlackBlocks.d.ts.map +0 -1
  142. package/dist/slack/fromSlackBlocks.js +0 -483
  143. package/dist/slack/fromSlackBlocks.js.map +0 -1
  144. package/dist/slack/toSlackBlocks.d.ts +0 -4
  145. package/dist/slack/toSlackBlocks.d.ts.map +0 -1
  146. package/dist/slack/toSlackBlocks.js +0 -901
  147. package/dist/slack/toSlackBlocks.js.map +0 -1
  148. package/dist/slack/types.d.ts +0 -204
  149. package/dist/slack/types.d.ts.map +0 -1
  150. package/dist/slack/types.js +0 -0
  151. package/dist/teams/constants.d.ts +0 -34
  152. package/dist/teams/constants.d.ts.map +0 -1
  153. package/dist/teams/constants.js +0 -68
  154. package/dist/teams/constants.js.map +0 -1
  155. package/dist/teams/decodeSubmitData.d.ts +0 -26
  156. package/dist/teams/decodeSubmitData.d.ts.map +0 -1
  157. package/dist/teams/decodeSubmitData.js +0 -70
  158. package/dist/teams/decodeSubmitData.js.map +0 -1
  159. package/dist/teams/temporalId.d.ts +0 -5
  160. package/dist/teams/temporalId.d.ts.map +0 -1
  161. package/dist/teams/temporalId.js +0 -28
  162. package/dist/teams/temporalId.js.map +0 -1
  163. package/dist/teams/toAdaptiveCard.d.ts +0 -45
  164. package/dist/teams/toAdaptiveCard.d.ts.map +0 -1
  165. package/dist/teams/toAdaptiveCard.js +0 -642
  166. package/dist/teams/toAdaptiveCard.js.map +0 -1
  167. package/dist/teams/toTeamsAttachments.d.ts +0 -10
  168. package/dist/teams/toTeamsAttachments.d.ts.map +0 -1
  169. package/dist/teams/toTeamsAttachments.js +0 -99
  170. package/dist/teams/toTeamsAttachments.js.map +0 -1
  171. package/dist/teams/types.d.ts +0 -224
  172. package/dist/teams/types.d.ts.map +0 -1
  173. package/dist/teams/types.js +0 -0
  174. package/dist/temporal.d.ts +0 -33
  175. package/dist/temporal.d.ts.map +0 -1
  176. package/dist/temporal.js +0 -152
  177. package/dist/temporal.js.map +0 -1
  178. package/dist/types.d.ts +0 -99
  179. package/dist/types.d.ts.map +0 -1
  180. package/dist/types.js +0 -0
  181. package/dist/vocabulary/alert.d.ts +0 -32
  182. package/dist/vocabulary/alert.d.ts.map +0 -1
  183. package/dist/vocabulary/alert.js +0 -160
  184. package/dist/vocabulary/alert.js.map +0 -1
  185. package/dist/vocabulary/collectFormValues.d.ts +0 -23
  186. package/dist/vocabulary/collectFormValues.d.ts.map +0 -1
  187. package/dist/vocabulary/collectFormValues.js +0 -44
  188. package/dist/vocabulary/collectFormValues.js.map +0 -1
  189. package/dist/vocabulary/data.d.ts +0 -66
  190. package/dist/vocabulary/data.d.ts.map +0 -1
  191. package/dist/vocabulary/data.js +0 -503
  192. package/dist/vocabulary/data.js.map +0 -1
  193. package/dist/vocabulary/dispatch.d.ts +0 -9
  194. package/dist/vocabulary/dispatch.d.ts.map +0 -1
  195. package/dist/vocabulary/dispatch.js +0 -50
  196. package/dist/vocabulary/dispatch.js.map +0 -1
  197. package/dist/vocabulary/fact.d.ts +0 -19
  198. package/dist/vocabulary/fact.d.ts.map +0 -1
  199. package/dist/vocabulary/fact.js +0 -51
  200. package/dist/vocabulary/fact.js.map +0 -1
  201. package/dist/vocabulary/form.d.ts +0 -11
  202. package/dist/vocabulary/form.d.ts.map +0 -1
  203. package/dist/vocabulary/form.js +0 -23
  204. package/dist/vocabulary/form.js.map +0 -1
  205. package/dist/vocabulary/formatValue.d.ts +0 -7
  206. package/dist/vocabulary/formatValue.d.ts.map +0 -1
  207. package/dist/vocabulary/formatValue.js +0 -76
  208. package/dist/vocabulary/formatValue.js.map +0 -1
  209. package/dist/vocabulary/icon.d.ts +0 -41
  210. package/dist/vocabulary/icon.d.ts.map +0 -1
  211. package/dist/vocabulary/icon.js +0 -272
  212. package/dist/vocabulary/icon.js.map +0 -1
  213. package/dist/vocabulary/index.d.ts +0 -12
  214. package/dist/vocabulary/index.d.ts.map +0 -1
  215. package/dist/vocabulary/index.js +0 -36
  216. package/dist/vocabulary/index.js.map +0 -1
  217. package/dist/vocabulary/interactive.d.ts +0 -230
  218. package/dist/vocabulary/interactive.d.ts.map +0 -1
  219. package/dist/vocabulary/interactive.js +0 -530
  220. package/dist/vocabulary/interactive.js.map +0 -1
  221. package/dist/vocabulary/layout.d.ts +0 -79
  222. package/dist/vocabulary/layout.d.ts.map +0 -1
  223. package/dist/vocabulary/layout.js +0 -139
  224. package/dist/vocabulary/layout.js.map +0 -1
  225. package/dist/vocabulary/list.d.ts +0 -14
  226. package/dist/vocabulary/list.d.ts.map +0 -1
  227. package/dist/vocabulary/list.js +0 -50
  228. package/dist/vocabulary/list.js.map +0 -1
  229. package/dist/vocabulary/media.d.ts +0 -25
  230. package/dist/vocabulary/media.d.ts.map +0 -1
  231. package/dist/vocabulary/media.js +0 -42
  232. package/dist/vocabulary/media.js.map +0 -1
  233. package/dist/vocabulary/text.d.ts +0 -57
  234. package/dist/vocabulary/text.d.ts.map +0 -1
  235. package/dist/vocabulary/text.js +0 -49
  236. package/dist/vocabulary/text.js.map +0 -1
  237. package/dist/vocabulary/toTextContent.d.ts +0 -2
  238. package/dist/vocabulary/toTextContent.d.ts.map +0 -1
  239. package/dist/vocabulary/toTextContent.js +0 -6
  240. package/dist/vocabulary/toTextContent.js.map +0 -1
  241. package/src/JSONGenerativeUI.client.tsx +0 -168
  242. package/src/JSONGenerativeUI.server.tsx +0 -39
  243. package/src/JSONGenerativeUI.shared.ts +0 -90
  244. package/src/JSONGenerativeUI.test.tsx +0 -1298
  245. package/src/RadioGroupScope.server.test.tsx +0 -24
  246. package/src/RadioGroupScope.tsx +0 -34
  247. package/src/a2ui/BindingContext.ts +0 -26
  248. package/src/a2ui/PresentRenderer.tsx +0 -83
  249. package/src/a2ui/convert.test.ts +0 -2923
  250. package/src/a2ui/convert.ts +0 -1207
  251. package/src/a2ui/dataModel.ts +0 -69
  252. package/src/a2ui/reducer.test.ts +0 -422
  253. package/src/a2ui/reducer.ts +0 -363
  254. package/src/a2ui/snapshot.test.ts +0 -52
  255. package/src/a2ui/snapshot.ts +0 -52
  256. package/src/a2ui/types.ts +0 -114
  257. package/src/a2ui/valueFunctions.test.ts +0 -271
  258. package/src/a2ui/valueFunctions.ts +0 -523
  259. package/src/actionRegistry.test.ts +0 -90
  260. package/src/actionRegistry.ts +0 -88
  261. package/src/answeredValues.test.ts +0 -33
  262. package/src/answeredValues.tsx +0 -70
  263. package/src/buildPresentParameters.test.ts +0 -246
  264. package/src/buildPresentParameters.ts +0 -130
  265. package/src/constants.ts +0 -17
  266. package/src/convert/boundSpec.test.ts +0 -192
  267. package/src/convert/boundSpec.ts +0 -134
  268. package/src/convert/copyBounded.test.ts +0 -131
  269. package/src/convert/copyBounded.ts +0 -38
  270. package/src/convert/isElement.ts +0 -6
  271. package/src/convert/takeRun.test.ts +0 -64
  272. package/src/convert/takeRun.ts +0 -19
  273. package/src/defineGenerativeComponents.ts +0 -38
  274. package/src/fieldReferences.test.ts +0 -66
  275. package/src/fieldReferences.ts +0 -83
  276. package/src/generativeUIToJSX.test.ts +0 -268
  277. package/src/generativeUIToJSX.ts +0 -152
  278. package/src/index.server.test.ts +0 -106
  279. package/src/index.shared.ts +0 -61
  280. package/src/ir.test.ts +0 -203
  281. package/src/renderGenerativeUI.test.tsx +0 -373
  282. package/src/renderGenerativeUI.tsx +0 -165
  283. package/src/scopeSchema.test.ts +0 -72
  284. package/src/scopeSchema.ts +0 -71
  285. package/src/slack/a2uiSlack.test.ts +0 -198
  286. package/src/slack/constants.ts +0 -194
  287. package/src/slack/datePicker.test.ts +0 -612
  288. package/src/slack/decodeBlockAction.test.ts +0 -233
  289. package/src/slack/decodeBlockAction.ts +0 -232
  290. package/src/slack/fromSlackBlocks.test.ts +0 -1465
  291. package/src/slack/fromSlackBlocks.ts +0 -789
  292. package/src/slack/toSlackBlocks.test.ts +0 -3065
  293. package/src/slack/toSlackBlocks.ts +0 -1818
  294. package/src/slack/types.ts +0 -264
  295. package/src/teams/constants.ts +0 -93
  296. package/src/teams/decodeSubmitData.test.ts +0 -397
  297. package/src/teams/decodeSubmitData.ts +0 -110
  298. package/src/teams/temporal.test.ts +0 -766
  299. package/src/teams/temporalId.ts +0 -56
  300. package/src/teams/toAdaptiveCard.test.ts +0 -1897
  301. package/src/teams/toAdaptiveCard.ts +0 -1057
  302. package/src/teams/toTeamsAttachments.test.ts +0 -74
  303. package/src/teams/toTeamsAttachments.ts +0 -133
  304. package/src/teams/types.ts +0 -266
  305. package/src/temporal.test.ts +0 -319
  306. package/src/temporal.ts +0 -259
  307. package/src/types.ts +0 -119
  308. package/src/vocabulary/alert.test.tsx +0 -427
  309. package/src/vocabulary/alert.tsx +0 -217
  310. package/src/vocabulary/collectFormValues.test.ts +0 -389
  311. package/src/vocabulary/collectFormValues.ts +0 -116
  312. package/src/vocabulary/data.test.tsx +0 -858
  313. package/src/vocabulary/data.tsx +0 -819
  314. package/src/vocabulary/datepicker.dom.test.tsx +0 -1148
  315. package/src/vocabulary/dispatch.ts +0 -83
  316. package/src/vocabulary/fact.test.tsx +0 -94
  317. package/src/vocabulary/fact.tsx +0 -54
  318. package/src/vocabulary/form.test.tsx +0 -113
  319. package/src/vocabulary/form.tsx +0 -39
  320. package/src/vocabulary/formatValue.test.ts +0 -72
  321. package/src/vocabulary/formatValue.ts +0 -127
  322. package/src/vocabulary/icon.test.tsx +0 -64
  323. package/src/vocabulary/icon.tsx +0 -174
  324. package/src/vocabulary/index.test.ts +0 -65
  325. package/src/vocabulary/index.ts +0 -33
  326. package/src/vocabulary/interactive.dispatch.test.tsx +0 -454
  327. package/src/vocabulary/interactive.dom.test.tsx +0 -1747
  328. package/src/vocabulary/interactive.test.tsx +0 -377
  329. package/src/vocabulary/interactive.tsx +0 -1048
  330. package/src/vocabulary/layout.test.tsx +0 -323
  331. package/src/vocabulary/layout.tsx +0 -247
  332. package/src/vocabulary/list.test.tsx +0 -222
  333. package/src/vocabulary/list.tsx +0 -50
  334. package/src/vocabulary/media.test.tsx +0 -85
  335. package/src/vocabulary/media.tsx +0 -56
  336. package/src/vocabulary/text.test.tsx +0 -102
  337. package/src/vocabulary/text.tsx +0 -60
  338. package/src/vocabulary/toTextContent.ts +0 -2
@@ -1,389 +0,0 @@
1
- // @vitest-environment jsdom
2
-
3
- import { describe, it, expect } from "vitest";
4
- import {
5
- collectFormValues,
6
- type FormControlElementLike,
7
- } from "./collectFormValues";
8
- import {
9
- CHECKBOX_GROUP_ATTR,
10
- FIELD_VALUE_ATTR,
11
- GENERATED_NAME_ATTR,
12
- } from "../constants";
13
- import {
14
- normalizeTemporalInputValue,
15
- toLocalDateTime,
16
- toPickerLocalDateTime,
17
- } from "../temporal";
18
-
19
- const el = (
20
- partial: Partial<FormControlElementLike>,
21
- ): FormControlElementLike => ({
22
- name: "",
23
- type: "text",
24
- value: "",
25
- disabled: false,
26
- hasAttribute: () => false,
27
- ...partial,
28
- });
29
-
30
- describe("collectFormValues", () => {
31
- it.each([
32
- ["time", "13:45:30.500", "13:45:30.5"],
33
- ["time", "13:45:30.000", "13:45:30"],
34
- ["datetime-local", "2026-07-16T09:45:30.500", "2026-07-16T09:45:30.5"],
35
- ["datetime-local", "2026-07-16T09:45:30.000", "2026-07-16T09:45:30"],
36
- ])("collects an edited %s in shortest form", (type, value, expected) => {
37
- expect(collectFormValues([el({ name: "when", type, value })])).toEqual({
38
- when: expected,
39
- });
40
- });
41
-
42
- it("retains an untouched instant's offset spelling and precision", () => {
43
- const anchor = "2025-12-15T17:00:00.5Z";
44
- const displayed = toPickerLocalDateTime(anchor);
45
- const getAttribute = (name: string) =>
46
- name === FIELD_VALUE_ATTR ? anchor : null;
47
- expect(
48
- collectFormValues([
49
- el({
50
- name: "when",
51
- type: "datetime-local",
52
- value: displayed,
53
- getAttribute,
54
- }),
55
- ]),
56
- ).toEqual({ when: "2025-12-15T17:00:00.5Z" });
57
- expect(
58
- collectFormValues([
59
- el({
60
- name: "when",
61
- type: "datetime-local",
62
- value: displayed.replace(".500", ".5"),
63
- getAttribute,
64
- }),
65
- ]),
66
- ).toEqual({ when: "2025-12-15T17:00:00.5Z" });
67
- expect(
68
- collectFormValues([
69
- el({
70
- name: "when",
71
- type: "datetime-local",
72
- value: displayed.replace(".500", ".750"),
73
- getAttribute,
74
- }),
75
- ]),
76
- ).toEqual({ when: "2025-12-15T17:00:00.75Z" });
77
- });
78
-
79
- it("uses an instant anchor only for datetime-local controls", () => {
80
- expect(
81
- collectFormValues([
82
- el({
83
- name: "canonical",
84
- type: "datetime-local",
85
- value: normalizeTemporalInputValue(
86
- toLocalDateTime("2025-12-15T17:00:00Z"),
87
- ),
88
- getAttribute: (name) =>
89
- name === FIELD_VALUE_ATTR ? "2025-12-15T17:00:00Z" : null,
90
- }),
91
- el({
92
- name: "ordinary",
93
- value: "live",
94
- getAttribute: (name) => (name === FIELD_VALUE_ATTR ? "stale" : null),
95
- }),
96
- ]),
97
- ).toEqual({ canonical: "2025-12-15T17:00:00Z", ordinary: "live" });
98
- });
99
-
100
- it("uses the declared datetime-local type when the browser reports text", () => {
101
- const anchor = "2025-12-15T17:00:00.250Z";
102
- const displayed = toPickerLocalDateTime(anchor);
103
- const getAttribute = (name: string) =>
104
- name === "type"
105
- ? "datetime-local"
106
- : name === FIELD_VALUE_ATTR
107
- ? anchor
108
- : null;
109
- const control = (value: string) =>
110
- el({ name: "when", type: "text", value, getAttribute });
111
- expect(collectFormValues([control(displayed)])).toEqual({
112
- when: anchor,
113
- });
114
- expect(
115
- collectFormValues([control(displayed.replace(".250", ".750"))]),
116
- ).toEqual({
117
- when: "2025-12-15T17:00:00.750Z",
118
- });
119
- expect(collectFormValues([control(displayed.slice(0, -4))])).toEqual({
120
- when: "2025-12-15T17:00:00.000Z",
121
- });
122
- });
123
-
124
- it("uses the declared time type when the browser reports text", () => {
125
- expect(
126
- collectFormValues([
127
- el({
128
- name: "when",
129
- type: "text",
130
- value: "12:00",
131
- getAttribute: (name) =>
132
- name === "type"
133
- ? "time"
134
- : name === FIELD_VALUE_ATTR
135
- ? "12:00:00"
136
- : null,
137
- }),
138
- ]),
139
- ).toEqual({ when: "12:00:00" });
140
- });
141
-
142
- it.each([
143
- ["time", "17:00:00", "17:00", "08:30:45"],
144
- [
145
- "datetime-local",
146
- "2025-12-15T17:00:00",
147
- "2025-12-15T17:00",
148
- "2025-12-16T08:30:45",
149
- ],
150
- ])(
151
- "uses the canonical %s value only while its projection matches",
152
- (type, canonical, displayed, edited) => {
153
- const getAttribute = (name: string) =>
154
- name === FIELD_VALUE_ATTR ? canonical : null;
155
- expect(
156
- collectFormValues([
157
- el({ name: "value", type, value: displayed, getAttribute }),
158
- ]),
159
- ).toEqual({ value: canonical });
160
- expect(
161
- collectFormValues([
162
- el({ name: "value", type, value: edited, getAttribute }),
163
- ]),
164
- ).toEqual({ value: edited });
165
- expect(
166
- collectFormValues([
167
- el({ name: "value", type, value: "", getAttribute }),
168
- ]),
169
- ).toEqual({ value: "" });
170
- },
171
- );
172
-
173
- it("ignores instant anchors on other control types", () => {
174
- const getAttribute = (name: string) =>
175
- name === FIELD_VALUE_ATTR ? "2025-12-15T17:00:00Z" : null;
176
- expect(
177
- collectFormValues([
178
- el({ name: "date", type: "date", value: "", getAttribute }),
179
- el({ name: "time", type: "time", value: "12:34", getAttribute }),
180
- el({ name: "text", value: "live", getAttribute }),
181
- el({
182
- name: "select",
183
- type: "select-one",
184
- value: "chosen",
185
- getAttribute,
186
- }),
187
- el({ name: "textarea", type: "textarea", value: "text", getAttribute }),
188
- el({
189
- name: "radio",
190
- type: "radio",
191
- value: "chosen",
192
- checked: true,
193
- getAttribute,
194
- }),
195
- el({ name: "checkbox", type: "checkbox", checked: true, getAttribute }),
196
- el({
197
- name: "group",
198
- type: "checkbox",
199
- value: "chosen",
200
- checked: true,
201
- hasAttribute: (name) => name === CHECKBOX_GROUP_ATTR,
202
- getAttribute,
203
- }),
204
- el({ name: "range", type: "range", value: "7", getAttribute }),
205
- ]),
206
- ).toEqual({
207
- date: "",
208
- time: "12:34",
209
- text: "live",
210
- select: "chosen",
211
- textarea: "text",
212
- radio: "chosen",
213
- checkbox: true,
214
- group: ["chosen"],
215
- range: 7,
216
- });
217
- });
218
-
219
- it("resolves a checkbox to its checked boolean", () => {
220
- expect(
221
- collectFormValues([
222
- el({ name: "agree", type: "checkbox", checked: true }),
223
- ]),
224
- ).toEqual({ agree: true });
225
- expect(
226
- collectFormValues([
227
- el({ name: "agree", type: "checkbox", checked: false }),
228
- ]),
229
- ).toEqual({ agree: false });
230
- });
231
-
232
- it("resolves a radio set to the checked option's value", () => {
233
- expect(
234
- collectFormValues([
235
- el({ name: "size", type: "radio", value: "sm", checked: false }),
236
- el({ name: "size", type: "radio", value: "md", checked: true }),
237
- el({ name: "size", type: "radio", value: "lg", checked: false }),
238
- ]),
239
- ).toEqual({ size: "md" });
240
- });
241
-
242
- it("resolves a radio set with no checked option to undefined", () => {
243
- const values = collectFormValues([
244
- el({ name: "size", type: "radio", value: "sm", checked: false }),
245
- el({ name: "size", type: "radio", value: "md", checked: false }),
246
- ]);
247
- expect("size" in values).toBe(true);
248
- expect(values.size).toBeUndefined();
249
- });
250
-
251
- it("resolves a checkbox group to its checked values in document order", () => {
252
- const option = (value: string, checked: boolean) =>
253
- el({
254
- name: "toppings",
255
- type: "checkbox",
256
- value,
257
- checked,
258
- hasAttribute: (name) => name === CHECKBOX_GROUP_ATTR,
259
- });
260
- expect(
261
- collectFormValues([
262
- option("basil", true),
263
- option("olives", false),
264
- option("onion", true),
265
- ]),
266
- ).toEqual({ toppings: ["basil", "onion"] });
267
- expect(collectFormValues([option("basil", false)])).toEqual({
268
- toppings: [],
269
- });
270
- });
271
-
272
- it("resolves select/input/textarea/date controls to their string value", () => {
273
- expect(
274
- collectFormValues([
275
- el({ name: "role", type: "select-one", value: "admin" }),
276
- el({ name: "email", type: "email", value: "a@example.com" }),
277
- el({ name: "bio", type: "textarea", value: "hi" }),
278
- el({ name: "dob", type: "date", value: "2026-01-01" }),
279
- ]),
280
- ).toEqual({
281
- role: "admin",
282
- email: "a@example.com",
283
- bio: "hi",
284
- dob: "2026-01-01",
285
- });
286
- });
287
-
288
- it("resolves a range input to its numeric value", () => {
289
- expect(
290
- collectFormValues([el({ name: "volume", type: "range", value: "7" })]),
291
- ).toEqual({ volume: 7 });
292
- });
293
-
294
- it("collects repeated non-radio names into an array in document order", () => {
295
- expect(
296
- collectFormValues([
297
- el({ name: "tag", type: "text", value: "a" }),
298
- el({ name: "tag", type: "text", value: "b" }),
299
- el({ name: "tag", type: "text", value: "c" }),
300
- ]),
301
- ).toEqual({ tag: ["a", "b", "c"] });
302
- });
303
-
304
- it("skips controls without a name", () => {
305
- expect(
306
- collectFormValues([
307
- el({ name: "", type: "text", value: "unnamed" }),
308
- el({ name: "kept", type: "text", value: "yes" }),
309
- ]),
310
- ).toEqual({ kept: "yes" });
311
- });
312
-
313
- it("skips radio groups whose name is generated only for native grouping", () => {
314
- expect(
315
- collectFormValues([
316
- el({
317
- name: "_R_1_",
318
- type: "radio",
319
- value: "sm",
320
- checked: true,
321
- hasAttribute: (name) => name === GENERATED_NAME_ATTR,
322
- }),
323
- ]),
324
- ).toEqual({});
325
- });
326
-
327
- it("skips disabled controls", () => {
328
- expect(
329
- collectFormValues([
330
- el({ name: "off", type: "text", value: "nope", disabled: true }),
331
- el({ name: "on", type: "text", value: "yep" }),
332
- ]),
333
- ).toEqual({ on: "yep" });
334
- });
335
-
336
- it("skips a disabled control inside an otherwise-checked radio set", () => {
337
- expect(
338
- collectFormValues([
339
- el({
340
- name: "size",
341
- type: "radio",
342
- value: "sm",
343
- checked: true,
344
- disabled: true,
345
- }),
346
- el({ name: "size", type: "radio", value: "md", checked: false }),
347
- ]),
348
- ).toEqual({ size: undefined });
349
- });
350
-
351
- it("skips controls disabled by a fieldset while preserving its first legend", () => {
352
- const form = document.createElement("form");
353
- form.innerHTML = `
354
- <fieldset disabled>
355
- <legend><input name="legend" value="kept" /></legend>
356
- <input name="blocked" value="old" />
357
- </fieldset>
358
- <input name="enabled" value="yes" />
359
- `;
360
- const blocked = form.elements.namedItem("blocked") as HTMLInputElement;
361
-
362
- expect(blocked.disabled).toBe(false);
363
-
364
- expect(
365
- collectFormValues(
366
- form.elements as unknown as ArrayLike<FormControlElementLike>,
367
- ),
368
- ).toEqual({ legend: "kept", enabled: "yes" });
369
- });
370
-
371
- it("returns an empty object for no elements", () => {
372
- expect(collectFormValues([])).toEqual({});
373
- });
374
-
375
- it("a field named __proto__ round-trips as an own data key", () => {
376
- const values = collectFormValues([
377
- el({ name: "__proto__", type: "text", value: "x" }),
378
- ]);
379
- expect(Object.hasOwn(values, "__proto__")).toBe(true);
380
- expect(values["__proto__"]).toBe("x");
381
- });
382
-
383
- it("a field named constructor collects as its submitted string, not an array seeded with the inherited function", () => {
384
- const values = collectFormValues([
385
- el({ name: "constructor", type: "text", value: "hi" }),
386
- ]);
387
- expect(values["constructor"]).toBe("hi");
388
- });
389
- });
@@ -1,116 +0,0 @@
1
- import type { FormEvent } from "react";
2
- import {
3
- CHECKBOX_GROUP_ATTR,
4
- FIELD_NAME_ATTR,
5
- FIELD_VALUE_ATTR,
6
- GENERATED_NAME_ATTR,
7
- } from "../constants";
8
- import {
9
- fromLocalDateTime,
10
- normalizeTemporalInputValue,
11
- toPickerLocalDateTime,
12
- } from "../temporal";
13
-
14
- /**
15
- * The subset of `HTMLInputElement`/`HTMLSelectElement`/`HTMLTextAreaElement` that {@link collectFormValues} reads. A structural type rather than the DOM interfaces themselves, so a plain object can stand in for a form control in tests.
16
- */
17
- export type FormControlElementLike = {
18
- readonly name: string;
19
- readonly type: string;
20
- readonly value: string;
21
- readonly checked?: boolean;
22
- readonly disabled: boolean;
23
- readonly hasAttribute: (name: string) => boolean;
24
- readonly getAttribute?: ((name: string) => string | null) | undefined;
25
- readonly matches?: ((selector: string) => boolean) | undefined;
26
- };
27
-
28
- /**
29
- * Collects a submitted form's named control values into a plain object, keyed by `name`, in document order. Reads each control's live DOM state rather than `FormData`, so a checkbox resolves to its `checked` boolean instead of an on/off string. A radio group resolves to its checked option's `value`, or `undefined` if none is checked. A checkbox group resolves to its checked options' values in document order, or an empty array if none is checked. Any other repeated `name` resolves to an array of its controls' values, in document order. A time or datetime-local control's `data-aui-field-value` preserves the canonical string when its local projection is unchanged; an edited instant retains its original offset and precision. Controls without a `name`, that carry `data-aui-generated-name`, or that are effectively disabled, including through an ancestor disabled fieldset outside its first legend, are skipped entirely.
30
- */
31
- export function collectFormValues(
32
- elements: ArrayLike<FormControlElementLike>,
33
- ): Record<string, unknown> {
34
- const values: Record<string, unknown> = Object.create(null);
35
-
36
- for (const element of Array.from(elements)) {
37
- const { disabled } = element;
38
- const name = element.getAttribute?.(FIELD_NAME_ATTR) ?? element.name;
39
- if (
40
- !name ||
41
- disabled ||
42
- element.hasAttribute(GENERATED_NAME_ATTR) ||
43
- element.matches?.(":disabled")
44
- )
45
- continue;
46
-
47
- if (element.type === "radio") {
48
- if (element.checked) values[name] = element.value;
49
- else if (!Object.hasOwn(values, name)) values[name] = undefined;
50
- continue;
51
- }
52
-
53
- if (
54
- element.type === "checkbox" &&
55
- element.hasAttribute(CHECKBOX_GROUP_ATTR)
56
- ) {
57
- const existing = values[name];
58
- const checked = Array.isArray(existing) ? existing : [];
59
- values[name] = element.checked ? [...checked, element.value] : checked;
60
- continue;
61
- }
62
-
63
- const temporalType =
64
- element.type === "datetime-local" || element.type === "time"
65
- ? element.type
66
- : element.getAttribute?.("type");
67
- const inputValue =
68
- temporalType === "datetime-local" || temporalType === "time"
69
- ? normalizeTemporalInputValue(element.value)
70
- : element.value;
71
- const fieldValue =
72
- temporalType === "datetime-local" || temporalType === "time"
73
- ? element.getAttribute?.(FIELD_VALUE_ATTR)
74
- : undefined;
75
- const value: string | number | boolean =
76
- element.type === "checkbox"
77
- ? (element.checked ?? false)
78
- : element.type === "range"
79
- ? Number(element.value)
80
- : fieldValue && inputValue !== ""
81
- ? normalizeTemporalInputValue(inputValue) ===
82
- normalizeTemporalInputValue(
83
- temporalType === "time"
84
- ? fieldValue
85
- : toPickerLocalDateTime(fieldValue),
86
- )
87
- ? fieldValue
88
- : temporalType === "datetime-local"
89
- ? fromLocalDateTime(inputValue, fieldValue)
90
- : inputValue
91
- : inputValue;
92
-
93
- if (Object.hasOwn(values, name)) {
94
- const existing = values[name];
95
- values[name] = Array.isArray(existing)
96
- ? [...existing, value]
97
- : [existing, value];
98
- } else {
99
- values[name] = value;
100
- }
101
- }
102
-
103
- return values;
104
- }
105
-
106
- /**
107
- * Convenience wrapper around {@link collectFormValues} for a native form submit event; the cast from `HTMLFormControlsCollection` to `ArrayLike<FormControlElementLike>` lives here once instead of at every call site.
108
- */
109
- export function collectFormValuesFromEvent(
110
- event: FormEvent<HTMLFormElement>,
111
- ): Record<string, unknown> {
112
- return collectFormValues(
113
- event.currentTarget
114
- .elements as unknown as ArrayLike<FormControlElementLike>,
115
- );
116
- }