@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,233 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { decodeBlockAction } from "./decodeBlockAction";
3
-
4
- describe("decodeBlockAction", () => {
5
- it.each([
6
- "{}",
7
- '{"sku":"typed text","qty":2}',
8
- '{"type":"other","$input":"typed text"}',
9
- ])("preserves plain-text input containing JSON verbatim: %s", (value) => {
10
- expect(
11
- decodeBlockAction({
12
- type: "plain_text_input",
13
- action_id: "leave_note",
14
- value,
15
- }),
16
- ).toEqual({ type: "leave_note", $input: value });
17
- });
18
-
19
- it("keeps typed text distinct from a button's serialized payload", () => {
20
- const value = '{"sku":"abc123","$input":"ignored"}';
21
- expect(
22
- decodeBlockAction({ type: "button", action_id: "buy_item", value }),
23
- ).toEqual({
24
- type: "buy_item",
25
- sku: "abc123",
26
- });
27
- });
28
-
29
- it("decodes a button action, spreading its JSON value into the payload", () => {
30
- const action = {
31
- action_id: "buy_item",
32
- value: JSON.stringify({ sku: "abc123", qty: 2 }),
33
- };
34
- expect(decodeBlockAction(action)).toEqual({
35
- type: "buy_item",
36
- sku: "abc123",
37
- qty: 2,
38
- });
39
- });
40
-
41
- it("resolves nested field references from named plain-text input state", () => {
42
- const blockId = `aui:0:${JSON.stringify([["note", "note"]])}`;
43
- expect(
44
- decodeBlockAction(
45
- {
46
- action_id: "save",
47
- value: JSON.stringify({ context: { note: { $field: "note" } } }),
48
- },
49
- {
50
- [blockId]: {
51
- note: { type: "plain_text_input", value: "edited" },
52
- },
53
- },
54
- ),
55
- ).toEqual({ type: "save", context: { note: "edited" } });
56
- });
57
-
58
- it("maps each action in a shared block and preserves empty checkbox selections", () => {
59
- const blockId = `aui:1:${JSON.stringify([
60
- ["choose_plan", "plan"],
61
- ["choose_tags", "tags"],
62
- ["choose_date", "date"],
63
- ["choose_size", "size"],
64
- ])}`;
65
- expect(
66
- decodeBlockAction(
67
- {
68
- action_id: "submit",
69
- value: JSON.stringify({
70
- context: {
71
- plan: { $field: "plan" },
72
- tags: { $field: "tags" },
73
- date: { $field: "date" },
74
- size: { $field: "size" },
75
- missing: { $field: "missing" },
76
- },
77
- }),
78
- },
79
- {
80
- [blockId]: {
81
- choose_plan: {
82
- type: "static_select",
83
- selected_option: { value: "pro" },
84
- },
85
- choose_tags: { type: "checkboxes", selected_options: [] },
86
- choose_date: { type: "datepicker", selected_date: "2026-09-25" },
87
- choose_size: {
88
- type: "radio_buttons",
89
- selected_option: { value: "small" },
90
- },
91
- },
92
- },
93
- ),
94
- ).toEqual({
95
- type: "submit",
96
- context: {
97
- plan: "pro",
98
- tags: [],
99
- date: "2026-09-25",
100
- size: "small",
101
- },
102
- });
103
- });
104
-
105
- it("decodes a static_select action, reading $input from selected_option.value", () => {
106
- const action = {
107
- action_id: "choose_color",
108
- selected_option: {
109
- value: "red",
110
- text: { type: "plain_text", text: "Red" },
111
- },
112
- };
113
- expect(decodeBlockAction(action)).toEqual({
114
- type: "choose_color",
115
- $input: "red",
116
- });
117
- });
118
-
119
- it("decodes a datepicker action, reading $input from selected_date", () => {
120
- const action = { action_id: "pick_date", selected_date: "2026-07-15" };
121
- expect(decodeBlockAction(action)).toEqual({
122
- type: "pick_date",
123
- $input: "2026-07-15",
124
- });
125
- });
126
-
127
- it("decodes a number input action as a number", () => {
128
- expect(
129
- decodeBlockAction({
130
- type: "number_input",
131
- action_id: "set_quantity",
132
- value: "3.5",
133
- }),
134
- ).toEqual({ type: "set_quantity", $input: 3.5 });
135
- });
136
-
137
- it("decodes a checkboxes action, reading $input from selected_options[].value", () => {
138
- const action = {
139
- action_id: "toggle_opts",
140
- selected_options: [{ value: "a" }, { value: "b" }],
141
- };
142
- expect(decodeBlockAction(action)).toEqual({
143
- type: "toggle_opts",
144
- $input: ["a", "b"],
145
- });
146
- });
147
-
148
- it("decodes a radio_buttons action, reading $input from selected_option.value", () => {
149
- const action = {
150
- action_id: "select_plan",
151
- selected_option: { value: "pro" },
152
- };
153
- expect(decodeBlockAction(action)).toEqual({
154
- type: "select_plan",
155
- $input: "pro",
156
- });
157
- });
158
-
159
- it("treats a value that parses to a non-object JSON literal as the raw-string $input instead of swallowing it", () => {
160
- for (const value of ["42", "true", "null", "[1]"]) {
161
- expect(decodeBlockAction({ action_id: "note", value })).toEqual({
162
- type: "note",
163
- $input: value,
164
- });
165
- }
166
- });
167
-
168
- it("uses the raw value as $input when it is not JSON", () => {
169
- const action = { action_id: "leave_note", value: "hello world" };
170
- expect(decodeBlockAction(action)).toEqual({
171
- type: "leave_note",
172
- $input: "hello world",
173
- });
174
- });
175
-
176
- it("spreads a JSON value into the payload and separately captures the runtime selection as $input", () => {
177
- const action = {
178
- action_id: "combo_case",
179
- value: JSON.stringify({ context: "c1" }),
180
- selected_option: { value: "x" },
181
- };
182
- expect(decodeBlockAction(action)).toEqual({
183
- type: "combo_case",
184
- context: "c1",
185
- $input: "x",
186
- });
187
- });
188
-
189
- it("drops a $input key inside the JSON value payload when nothing was selected", () => {
190
- const action = {
191
- action_id: "spoof_case",
192
- value: JSON.stringify({ context: "c1", $input: "spoofed" }),
193
- };
194
- const decoded = decodeBlockAction(action);
195
- expect(decoded).toEqual({ type: "spoof_case", context: "c1" });
196
- expect(decoded !== undefined && "$input" in decoded).toBe(false);
197
- });
198
-
199
- it("drops a payload $input in favor of the runtime selection", () => {
200
- const action = {
201
- action_id: "spoof_case",
202
- value: JSON.stringify({ $input: "spoofed" }),
203
- selected_option: { value: "x" },
204
- };
205
- expect(decodeBlockAction(action)).toEqual({
206
- type: "spoof_case",
207
- $input: "x",
208
- });
209
- });
210
-
211
- it("ignores a non-string value without throwing", () => {
212
- expect(decodeBlockAction({ action_id: "weird", value: 42 })).toEqual({
213
- type: "weird",
214
- });
215
- });
216
-
217
- it("returns undefined for malformed or non-actionable input", () => {
218
- const malformed: unknown[] = [
219
- null,
220
- undefined,
221
- 42,
222
- "just a string",
223
- true,
224
- [],
225
- {},
226
- { action_id: 123 },
227
- { action_id: "" },
228
- ];
229
- for (const input of malformed) {
230
- expect(decodeBlockAction(input)).toBeUndefined();
231
- }
232
- });
233
- });
@@ -1,232 +0,0 @@
1
- import type { Action } from "../ir";
2
- import { resolveFieldReferences } from "../fieldReferences";
3
- import {
4
- formatTemporalUnixSeconds,
5
- mergeTemporalMinutes,
6
- splitTemporalMinutes,
7
- } from "../temporal";
8
- import type { FieldMapping } from "./types";
9
-
10
- const isRecord = (value: unknown): value is Record<string, unknown> =>
11
- typeof value === "object" && value !== null && !Array.isArray(value);
12
-
13
- const optionValue = (value: unknown): string | undefined =>
14
- isRecord(value) && typeof value["value"] === "string"
15
- ? value["value"]
16
- : undefined;
17
-
18
- const selectedValue = (
19
- action: Record<string, unknown>,
20
- field?: FieldMapping,
21
- ): unknown => {
22
- if (action["type"] === "datetimepicker" || "selected_date_time" in action) {
23
- const seconds = action["selected_date_time"];
24
- return seconds === null
25
- ? ""
26
- : typeof seconds === "number"
27
- ? formatTemporalUnixSeconds(seconds, field?.offset)
28
- : undefined;
29
- }
30
- if (action["type"] === "timepicker" || "selected_time" in action) {
31
- const time = action["selected_time"];
32
- if (time === null) return "";
33
- const parts =
34
- typeof time === "string" ? splitTemporalMinutes(time) : undefined;
35
- return parts?.date === undefined ? parts?.time : undefined;
36
- }
37
- if (field?.part === "date" && action["selected_date"] === null) return "";
38
- const selectedOption = optionValue(action["selected_option"]);
39
- const selectedDate =
40
- typeof action["selected_date"] === "string"
41
- ? action["selected_date"]
42
- : undefined;
43
- const selectedOptions = Array.isArray(action["selected_options"])
44
- ? action["selected_options"]
45
- .map(optionValue)
46
- .filter((value): value is string => value !== undefined)
47
- : undefined;
48
- const numberInput =
49
- action["type"] === "number_input" && typeof action["value"] === "string"
50
- ? Number(action["value"])
51
- : undefined;
52
- return (
53
- selectedOption ??
54
- selectedDate ??
55
- (selectedOptions !== undefined
56
- ? selectedOptions
57
- : Number.isFinite(numberInput)
58
- ? numberInput
59
- : undefined)
60
- );
61
- };
62
-
63
- const fieldMappingsFromBlockId = (blockId: string): readonly FieldMapping[] => {
64
- if (!/^aui:\d+:/.test(blockId)) return [];
65
- const mappingStart = blockId.indexOf(":", 4);
66
- if (mappingStart === -1) return [];
67
- try {
68
- const parsed: unknown = JSON.parse(blockId.slice(mappingStart + 1));
69
- if (!Array.isArray(parsed)) return [];
70
- return parsed.flatMap((entry): FieldMapping[] => {
71
- if (
72
- !Array.isArray(entry) ||
73
- typeof entry[0] !== "string" ||
74
- typeof entry[1] !== "string"
75
- )
76
- return [];
77
- return [
78
- {
79
- actionId: entry[0],
80
- name: entry[1],
81
- component: typeof entry[2] === "string" ? entry[2] : "",
82
- ...(entry[2] === "DatePicker" &&
83
- (entry[3] === "time" || entry[3] === "datetime")
84
- ? {
85
- inputType: entry[3],
86
- ...(typeof entry[4] === "string" ? { offset: entry[4] } : {}),
87
- ...((entry[5] === "date" || entry[5] === "time") &&
88
- Number.isSafeInteger(entry[6]) &&
89
- entry[6] >= 0
90
- ? { part: entry[5], pairId: entry[6] }
91
- : {}),
92
- }
93
- : {}),
94
- },
95
- ];
96
- });
97
- } catch {
98
- return [];
99
- }
100
- };
101
-
102
- const checkboxValue = (
103
- value: Record<string, unknown>,
104
- name: string,
105
- ): boolean | undefined =>
106
- Array.isArray(value["selected_options"])
107
- ? value["selected_options"].some((option) => optionValue(option) === name)
108
- : undefined;
109
-
110
- const stateFieldValue = (value: unknown, field: FieldMapping): unknown => {
111
- if (!isRecord(value)) return undefined;
112
- if (field.component === "Checkbox" && value["type"] === "checkboxes") {
113
- return checkboxValue(value, field.name);
114
- }
115
- if (value["type"] === "plain_text_input") {
116
- return typeof value["value"] === "string" ? value["value"] : undefined;
117
- }
118
- return selectedValue(value, field);
119
- };
120
-
121
- const fieldValuesFromState = (
122
- stateValues: unknown,
123
- action: Record<string, unknown>,
124
- actionField: FieldMapping | undefined,
125
- ) => {
126
- const fields: Record<string, unknown> = Object.create(null);
127
- const pairs = new Map<number, { name: string; date: string; time: string }>();
128
- const collect = (raw: unknown, field: FieldMapping) => {
129
- const value = stateFieldValue(raw, field);
130
- if (field.part !== undefined && field.pairId !== undefined) {
131
- const pair = pairs.get(field.pairId) ?? {
132
- name: field.name,
133
- date: "",
134
- time: "",
135
- };
136
- pair[field.part] = typeof value === "string" ? value : "";
137
- pairs.set(field.pairId, pair);
138
- } else if (field.name && value !== undefined) {
139
- fields[field.name] = value;
140
- }
141
- };
142
- if (isRecord(stateValues)) {
143
- for (const [blockId, rawBlockValues] of Object.entries(stateValues)) {
144
- if (!isRecord(rawBlockValues)) continue;
145
- for (const field of fieldMappingsFromBlockId(blockId)) {
146
- if (Object.hasOwn(rawBlockValues, field.actionId))
147
- collect(rawBlockValues[field.actionId], field);
148
- }
149
- }
150
- }
151
- if (actionField?.inputType !== undefined) collect(action, actionField);
152
- for (const pair of pairs.values()) {
153
- if (pair.name)
154
- fields[pair.name] = mergeTemporalMinutes(pair.date, pair.time);
155
- }
156
- return { fields, pairs };
157
- };
158
-
159
- /**
160
- * Decodes one structural entry from a Slack `block_actions` payload.
161
- * `$input` is reserved for the runtime selection: a `$input` key inside the
162
- * button's JSON `value` payload is dropped rather than spread into the result.
163
- * `stateValues` is the companion `state.values` object used to resolve named
164
- * control values referenced by `{ "$field": name }` in the action payload.
165
- */
166
- export function decodeBlockAction(
167
- action: unknown,
168
- stateValues?: unknown,
169
- ): Action | undefined {
170
- try {
171
- if (!isRecord(action) || typeof action["action_id"] !== "string") {
172
- return undefined;
173
- }
174
-
175
- const actionId = action["action_id"];
176
- if (!actionId) return undefined;
177
-
178
- const rawValue = action["value"];
179
- let payload: Record<string, unknown> = {};
180
- let plainValue: string | undefined;
181
- if (action["type"] === "plain_text_input" && typeof rawValue === "string") {
182
- plainValue = rawValue;
183
- } else if (typeof rawValue === "string") {
184
- try {
185
- const parsed: unknown = JSON.parse(rawValue);
186
- if (isRecord(parsed)) {
187
- payload = parsed;
188
- } else {
189
- plainValue = rawValue;
190
- }
191
- } catch {
192
- plainValue = rawValue;
193
- }
194
- }
195
-
196
- const actionField =
197
- typeof action["block_id"] === "string"
198
- ? fieldMappingsFromBlockId(action["block_id"]).find(
199
- (field) => field.actionId === actionId,
200
- )
201
- : undefined;
202
- const { fields, pairs } = fieldValuesFromState(
203
- stateValues,
204
- action,
205
- actionField,
206
- );
207
- let input = selectedValue(action, actionField) ?? plainValue;
208
- if (actionField?.pairId !== undefined) {
209
- const pair = pairs.get(actionField.pairId);
210
- input =
211
- pair === undefined ? "" : mergeTemporalMinutes(pair.date, pair.time);
212
- if (!input) return undefined;
213
- }
214
- if (
215
- (action["type"] === "timepicker" ||
216
- action["type"] === "datetimepicker") &&
217
- input === undefined
218
- )
219
- return undefined;
220
-
221
- const decoded = {
222
- ...Object.fromEntries(
223
- Object.entries(payload).filter(([key]) => key !== "$input"),
224
- ),
225
- type: actionId,
226
- ...(input !== undefined ? { $input: input } : {}),
227
- };
228
- return resolveFieldReferences(decoded, fields) as Action;
229
- } catch {
230
- return undefined;
231
- }
232
- }