@assistant-ui/react-generative-ui 0.0.24 → 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 +4 -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 -153
  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 -584
  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 -337
  306. package/src/temporal.ts +0 -263
  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 -1457
  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 -1109
  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,612 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { decodeBlockAction } from "./decodeBlockAction";
3
- import { fromSlackBlocks } from "./fromSlackBlocks";
4
- import { toSlackBlocks } from "./toSlackBlocks";
5
- import type { SlackActionElement, SlackActionsBlock } from "./types";
6
-
7
- const selection = (element: SlackActionElement): Record<string, unknown> => {
8
- switch (element.type) {
9
- case "datepicker":
10
- return {
11
- type: element.type,
12
- selected_date: element.initial_date ?? null,
13
- };
14
- case "timepicker":
15
- return {
16
- type: element.type,
17
- selected_time: element.initial_time ?? null,
18
- };
19
- case "datetimepicker":
20
- return {
21
- type: element.type,
22
- selected_date_time: element.initial_date_time ?? 1765818000,
23
- };
24
- default:
25
- throw new Error("Expected a picker");
26
- }
27
- };
28
-
29
- const stateFrom = (blocks: readonly SlackActionsBlock[]) =>
30
- Object.fromEntries(
31
- blocks.map((block) => [
32
- block.block_id!,
33
- Object.fromEntries(
34
- block.elements.map((element) => [
35
- element.action_id,
36
- selection(element),
37
- ]),
38
- ),
39
- ]),
40
- );
41
-
42
- const submit = (state: unknown, name = "when") =>
43
- decodeBlockAction(
44
- {
45
- action_id: "save",
46
- value: JSON.stringify({ when: { $field: name, fallback: "stale" } }),
47
- },
48
- state,
49
- );
50
-
51
- const renderPicker = (inputType: string, value: string, name = "when") => {
52
- const result = toSlackBlocks({
53
- $type: "DatePicker",
54
- inputType,
55
- value,
56
- name,
57
- $action: { type: "pick" },
58
- });
59
- return { ...result, blocks: result.blocks as readonly SlackActionsBlock[] };
60
- };
61
-
62
- describe("Slack DatePicker temporal modes", () => {
63
- it.each([
64
- ["date", "2025-12-15", "2025-12-15", ["datepicker"]],
65
- ["time", "17:00", "17:00", ["timepicker"]],
66
- ["time", "17:00:30", "17:00", ["timepicker"]],
67
- [
68
- "datetime",
69
- "2025-12-15T17:00:00Z",
70
- "2025-12-15T17:00:00Z",
71
- ["datetimepicker"],
72
- ],
73
- [
74
- "datetime",
75
- "2025-12-15T17:00:00+02:00",
76
- "2025-12-15T17:00:00+02:00",
77
- ["datetimepicker"],
78
- ],
79
- [
80
- "datetime",
81
- "2025-12-15T17:00",
82
- "2025-12-15T17:00",
83
- ["datepicker", "timepicker"],
84
- ],
85
- ["datetime", "", "2025-12-15T17:00:00Z", ["datetimepicker"]],
86
- [
87
- "datetime",
88
- "2025-12-15T17:00+02:00",
89
- "2025-12-15T17:00:00+02:00",
90
- ["datetimepicker"],
91
- ],
92
- [
93
- "datetime",
94
- "2025-12-15T17:00:30.123",
95
- "2025-12-15T17:00",
96
- ["datepicker", "timepicker"],
97
- ],
98
- ] as const)(
99
- "round trips %s %j through field state and picker actions",
100
- (mode, value, expected, types) => {
101
- const { blocks } = renderPicker(mode, value);
102
- expect(
103
- blocks.flatMap((block) =>
104
- block.elements.map((element) => element.type),
105
- ),
106
- ).toEqual(types);
107
- const state = stateFrom(blocks);
108
- expect(submit(state)).toEqual({ type: "save", when: expected });
109
- for (const block of blocks) {
110
- for (const element of block.elements) {
111
- expect(element).not.toHaveProperty("timezone");
112
- expect(
113
- decodeBlockAction(
114
- {
115
- ...selection(element),
116
- action_id: element.action_id,
117
- block_id: block.block_id,
118
- },
119
- state,
120
- ),
121
- ).toEqual({ type: "pick", $input: expected });
122
- }
123
- }
124
- },
125
- );
126
-
127
- it("preserves the date mode block and metadata exactly", () => {
128
- expect(renderPicker("date", "2025-12-15")).toEqual({
129
- blocks: [
130
- {
131
- type: "actions",
132
- block_id: 'aui:0:[["pick","when"]]',
133
- elements: [
134
- {
135
- type: "datepicker",
136
- action_id: "pick",
137
- initial_date: "2025-12-15",
138
- },
139
- ],
140
- },
141
- ],
142
- warnings: [],
143
- });
144
- });
145
-
146
- it.each(["17:00", "17:00:00", ""])(
147
- "renders a minute timepicker without a warning for %j",
148
- (value) => {
149
- const { blocks, warnings } = renderPicker("time", value);
150
- expect(blocks[0]?.elements[0]).toEqual({
151
- type: "timepicker",
152
- action_id: "pick",
153
- ...(value ? { initial_time: "17:00" } : {}),
154
- });
155
- expect(warnings).toEqual([]);
156
- },
157
- );
158
-
159
- it("warns when Slack drops nonzero time seconds", () => {
160
- const { warnings } = renderPicker("time", "17:00:30");
161
- expect(warnings).toEqual([
162
- {
163
- code: "dropped",
164
- component: "DatePicker",
165
- detail:
166
- "Seconds were dropped because Slack timepickers only support minute precision.",
167
- },
168
- ]);
169
- });
170
-
171
- it.each([
172
- ["2025-12-15T17:00:00Z", 1765818000],
173
- ["2025-12-15T17:00:00+02:00", 1765810800],
174
- ["2025-12-15T17:00:00.999Z", 1765818000],
175
- ])("renders %s as unix seconds", (value, expected) => {
176
- expect(
177
- renderPicker("datetime", value as string).blocks[0]?.elements[0],
178
- ).toEqual({
179
- type: "datetimepicker",
180
- action_id: "pick",
181
- initial_date_time: expected,
182
- });
183
- });
184
-
185
- it.each([
186
- ["2025-12-15T17:00:30.123Z", true],
187
- ["2025-12-15T17:00:30.000001+02:00", true],
188
- ["1969-12-31T23:59:59.999Z", true],
189
- ["2025-12-15T17:00:30.000000Z", false],
190
- ["2025-12-15T17:00:30Z", false],
191
- ["2025-12-15T17:00Z", false],
192
- ])("reports lost fractional seconds for %s", (value, dropped) => {
193
- const { warnings } = renderPicker("datetime", value);
194
- expect(warnings).toEqual(
195
- dropped
196
- ? [
197
- {
198
- code: "dropped",
199
- component: "DatePicker",
200
- detail:
201
- "Fractional seconds were dropped because Slack datetimepickers only support second precision.",
202
- },
203
- ]
204
- : [],
205
- );
206
- });
207
-
208
- it.each(["Z", "+02:00", "-05:30", "+00:00", "-00:00"])(
209
- "preserves %s offset spelling at seconds precision after an edit",
210
- (offset) => {
211
- const { blocks } = renderPicker(
212
- "datetime",
213
- `2025-12-15T17:00:45.123456${offset}`,
214
- );
215
- const block = blocks[0]!;
216
- const element = block.elements[0]!;
217
- if (element.type !== "datetimepicker")
218
- throw new Error("Expected a datetimepicker");
219
- const action = {
220
- type: element.type,
221
- action_id: element.action_id,
222
- block_id: block.block_id,
223
- selected_date_time: element.initial_date_time! + 3600,
224
- };
225
- expect(decodeBlockAction(action)).toEqual({
226
- type: "pick",
227
- $input: `2025-12-15T18:00:45${offset}`,
228
- });
229
- expect(submit({ [block.block_id!]: { pick: action } })).toEqual({
230
- type: "save",
231
- when: `2025-12-15T18:00:45${offset}`,
232
- });
233
- },
234
- );
235
-
236
- it("renders an empty datetime without an initial instant", () => {
237
- expect(renderPicker("datetime", "").blocks[0]?.elements[0]).toEqual({
238
- type: "datetimepicker",
239
- action_id: "pick",
240
- });
241
- });
242
-
243
- it.each(["time", "datetime"])(
244
- "preserves a cleared %s in field state and picker input",
245
- (inputType) => {
246
- const { blocks } = renderPicker(inputType, "");
247
- const block = blocks[0]!;
248
- const action = {
249
- type: `${inputType}picker`,
250
- action_id: "pick",
251
- block_id: block.block_id,
252
- [inputType === "time" ? "selected_time" : "selected_date_time"]: null,
253
- };
254
- expect(decodeBlockAction(action)).toEqual({ type: "pick", $input: "" });
255
- expect(submit({ [block.block_id!]: { pick: action } })).toEqual({
256
- type: "save",
257
- when: "",
258
- });
259
- },
260
- );
261
-
262
- it.each([
263
- ["time", "24:00"],
264
- ["time", "2025-12-15T17:00"],
265
- ["datetime", "2025-02-29T17:00Z"],
266
- ["datetime", "17:00"],
267
- ])("drops an invalid %s value %j with a warning", (inputType, value) => {
268
- const { blocks, warnings } = renderPicker(inputType!, value!);
269
- expect(blocks[0]?.elements[0]).toEqual({
270
- type: `${inputType}picker`,
271
- action_id: "pick",
272
- });
273
- expect(warnings).toEqual([
274
- {
275
- code: "dropped",
276
- component: "DatePicker",
277
- detail: `value did not match the ${inputType} format and was dropped.`,
278
- },
279
- ]);
280
- });
281
-
282
- it.each([
283
- ["timepicker", { selected_time: "24:00" }],
284
- ["timepicker", { selected_time: "2025-12-15T17:00" }],
285
- ["datetimepicker", { selected_date_time: "1765818000" }],
286
- ["datetimepicker", { selected_date_time: Infinity }],
287
- ["datetimepicker", { selected_date_time: 1e20 }],
288
- ["datetimepicker", { selected_date_time: 0.5 }],
289
- ])("rejects malformed %s selection %j", (type, selected) => {
290
- expect(
291
- decodeBlockAction({ type, action_id: "pick", ...selected }),
292
- ).toBeUndefined();
293
- });
294
-
295
- it("decodes standalone native pickers and tolerates malformed metadata", () => {
296
- expect(
297
- decodeBlockAction({
298
- type: "timepicker",
299
- action_id: "pick",
300
- selected_time: "17:00",
301
- }),
302
- ).toEqual({ type: "pick", $input: "17:00" });
303
- for (const block_id of [
304
- undefined,
305
- "aui:0:{",
306
- 'aui:0:[null,42,["pick"]]',
307
- 'aui:0:[["pick","when","DatePicker","datetime",null,"date","invalid"]]',
308
- ]) {
309
- expect(
310
- decodeBlockAction({
311
- type: "datetimepicker",
312
- action_id: "pick",
313
- selected_date_time: 0,
314
- block_id,
315
- }),
316
- ).toEqual({ type: "pick", $input: "1970-01-01T00:00:00Z" });
317
- }
318
- });
319
-
320
- it("carries an unnamed instant's offset in its block ID", () => {
321
- const { blocks } = toSlackBlocks({
322
- $type: "DatePicker",
323
- inputType: "datetime",
324
- value: "2025-12-15T17:00:00+02:00",
325
- $action: { type: "pick" },
326
- });
327
- const block = blocks[0] as SlackActionsBlock;
328
- expect(
329
- decodeBlockAction({
330
- ...selection(block.elements[0]!),
331
- action_id: "pick",
332
- block_id: block.block_id,
333
- }),
334
- ).toEqual({ type: "pick", $input: "2025-12-15T17:00:00+02:00" });
335
- });
336
-
337
- it.each(["date", "time"])(
338
- "clears the field and rejects a split action when the %s half is empty",
339
- (part) => {
340
- const { blocks } = renderPicker("datetime", "2025-12-15T17:00");
341
- const state = stateFrom(blocks);
342
- const clearedBlock = blocks.find(
343
- (block) => block.elements[0]?.type === `${part}picker`,
344
- )!;
345
- const cleared = { type: `${part}picker`, [`selected_${part}`]: null };
346
- state[clearedBlock.block_id!] = { pick: cleared };
347
- expect(submit(state)).toEqual({ type: "save", when: "" });
348
- for (const block of blocks) {
349
- const current = state[block.block_id!]!.pick!;
350
- expect(
351
- decodeBlockAction(
352
- { ...current, action_id: "pick", block_id: block.block_id },
353
- state,
354
- ),
355
- ).toBeUndefined();
356
- }
357
- },
358
- );
359
-
360
- it("uses the fired split selection ahead of stale state and requires its companion", () => {
361
- const { blocks } = renderPicker("datetime", "2025-12-15T17:00");
362
- const block = blocks.find(
363
- (entry) => entry.elements[0]?.type === "timepicker",
364
- )!;
365
- const action = {
366
- type: "timepicker",
367
- action_id: "pick",
368
- block_id: block.block_id,
369
- selected_time: "18:30",
370
- };
371
- expect(decodeBlockAction(action, stateFrom(blocks))).toEqual({
372
- type: "pick",
373
- $input: "2025-12-15T18:30",
374
- });
375
- expect(decodeBlockAction(action)).toBeUndefined();
376
- expect(
377
- submit({ [block.block_id!]: { pick: selection(block.elements[0]!) } }),
378
- ).toEqual({ type: "save", when: "" });
379
- });
380
-
381
- it("keeps unnamed split pairs with repeated action IDs distinct across containers", () => {
382
- const { blocks } = toSlackBlocks(
383
- ["2025-12-15T17:00", "2026-01-02T09:30"].map((value) => ({
384
- $type: "Col",
385
- children: [
386
- {
387
- $type: "DatePicker",
388
- inputType: "datetime",
389
- value,
390
- $action: { type: "pick" },
391
- },
392
- ],
393
- })),
394
- );
395
- const actions = blocks as readonly SlackActionsBlock[];
396
- const state = stateFrom(actions);
397
- const inputs = actions.map(
398
- (block) =>
399
- decodeBlockAction(
400
- {
401
- ...selection(block.elements[0]!),
402
- action_id: "pick",
403
- block_id: block.block_id,
404
- },
405
- state,
406
- )?.["$input"],
407
- );
408
- expect(inputs).toEqual([
409
- "2025-12-15T17:00",
410
- "2025-12-15T17:00",
411
- "2026-01-02T09:30",
412
- "2026-01-02T09:30",
413
- ]);
414
- });
415
-
416
- it("keeps temporal metadata in bounded block IDs and warns when one mapping cannot fit", () => {
417
- const { blocks, warnings } = toSlackBlocks(
418
- Array.from({ length: 5 }, (_, i) => ({
419
- $type: "DatePicker",
420
- inputType: "datetime",
421
- value: "2025-12-15T17:00:00+02:00",
422
- name: `when${i}${"x".repeat(60)}`,
423
- $action: { type: `pick${i}` },
424
- })),
425
- );
426
- expect(warnings).toEqual([]);
427
- expect(blocks.length).toBeGreaterThan(1);
428
- for (const block of blocks as readonly SlackActionsBlock[]) {
429
- expect(block.block_id!.length).toBeLessThanOrEqual(255);
430
- expect(block.block_id).toContain("+02:00");
431
- }
432
- const oversized = renderPicker(
433
- "datetime",
434
- "2025-12-15T17:00:00+02:00",
435
- "x".repeat(300),
436
- );
437
- expect(oversized.blocks[0]).not.toHaveProperty("block_id");
438
- expect(oversized.warnings).toEqual([
439
- {
440
- code: "dropped",
441
- component: "DatePicker",
442
- detail:
443
- "name could not be mapped because its Slack block_id exceeded 255 characters.",
444
- },
445
- ]);
446
- });
447
-
448
- it.each(["message", "modal"] as const)(
449
- "decodes split pairs alongside other controls on a %s",
450
- (surface) => {
451
- const result = toSlackBlocks(
452
- {
453
- $type: "Form",
454
- children: [
455
- {
456
- $type: "DatePicker",
457
- inputType: "datetime",
458
- value: "2025-12-15T17:00",
459
- name: "when",
460
- $action: { type: "pick" },
461
- },
462
- {
463
- $type: "DatePicker",
464
- inputType: "time",
465
- value: "09:30",
466
- name: "start",
467
- $action: { type: "start" },
468
- },
469
- {
470
- $type: "Select",
471
- name: "plan",
472
- options: [{ label: "Pro", value: "pro" }],
473
- $action: { type: "plan" },
474
- },
475
- {
476
- $type: "Button",
477
- label: "Save",
478
- $action: {
479
- type: "save",
480
- when: { $field: "when" },
481
- start: { $field: "start" },
482
- plan: { $field: "plan" },
483
- },
484
- },
485
- ],
486
- },
487
- { surface },
488
- );
489
- const state: Record<string, Record<string, unknown>> = {};
490
- let button: SlackActionElement | undefined;
491
- for (const block of result.blocks) {
492
- if (block.type !== "actions") continue;
493
- const values: Record<string, unknown> = {};
494
- for (const element of block.elements) {
495
- if (element.type === "button") {
496
- if (element.action_id === "save") button = element;
497
- } else if (element.type === "static_select") {
498
- values[element.action_id] = {
499
- type: element.type,
500
- selected_option: { value: "pro" },
501
- };
502
- } else values[element.action_id] = selection(element);
503
- }
504
- if (block.block_id) state[block.block_id] = values;
505
- }
506
- expect(result.warnings).toEqual([]);
507
- expect(decodeBlockAction(button, state)).toEqual({
508
- type: "save",
509
- when: "2025-12-15T17:00",
510
- start: "09:30",
511
- plan: "pro",
512
- });
513
- },
514
- );
515
-
516
- it.each([
517
- ["timepicker", { initial_time: "17:00" }, "time", "17:00"],
518
- [
519
- "datetimepicker",
520
- { initial_date_time: 1765818000 },
521
- "datetime",
522
- "2025-12-15T17:00:00Z",
523
- ],
524
- [
525
- "datetimepicker",
526
- { initial_date_time: 0 },
527
- "datetime",
528
- "1970-01-01T00:00:00Z",
529
- ],
530
- ["timepicker", {}, "time", undefined],
531
- ["datetimepicker", {}, "datetime", undefined],
532
- ])(
533
- "converts native %s %j back to the matching DatePicker mode",
534
- (type, initial, inputType, value) => {
535
- expect(
536
- fromSlackBlocks([
537
- {
538
- type: "actions",
539
- elements: [{ type, action_id: "pick", ...initial }],
540
- },
541
- ]),
542
- ).toEqual({
543
- nodes: [
544
- {
545
- $type: "DatePicker",
546
- inputType,
547
- ...(value === undefined ? {} : { value }),
548
- $action: { type: "pick" },
549
- },
550
- ],
551
- warnings: [],
552
- });
553
- },
554
- );
555
-
556
- it("omits invalid native timestamps without losing sibling blocks", () => {
557
- for (const initial_date_time of [Infinity, NaN, 1e20, 0.5, "1765818000"]) {
558
- expect(
559
- fromSlackBlocks([
560
- {
561
- type: "actions",
562
- elements: [
563
- { type: "datetimepicker", action_id: "pick", initial_date_time },
564
- ],
565
- },
566
- { type: "divider" },
567
- ]).nodes,
568
- ).toEqual([
569
- {
570
- $type: "DatePicker",
571
- inputType: "datetime",
572
- $action: { type: "pick" },
573
- },
574
- { $type: "Divider" },
575
- ]);
576
- }
577
- });
578
-
579
- it.each([
580
- ["timepicker", { initial_time: "17:00" }, "time", "17:00"],
581
- [
582
- "datetimepicker",
583
- { initial_date_time: 1765818000 },
584
- "datetime",
585
- "2025-12-15T17:00:00Z",
586
- ],
587
- ])(
588
- "converts %s in an input block with its label",
589
- (type, initial, inputType, value) => {
590
- expect(
591
- fromSlackBlocks([
592
- {
593
- type: "input",
594
- label: { type: "plain_text", text: "When" },
595
- element: { type, action_id: "pick", ...initial },
596
- },
597
- ]),
598
- ).toEqual({
599
- nodes: [
600
- {
601
- $type: "DatePicker",
602
- inputType,
603
- value,
604
- label: "When",
605
- $action: { type: "pick" },
606
- },
607
- ],
608
- warnings: [],
609
- });
610
- },
611
- );
612
- });