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

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 (212) hide show
  1. package/LICENSE +1 -1
  2. package/dist/JSONGenerativeUI.client.d.ts.map +1 -1
  3. package/dist/JSONGenerativeUI.client.js +30 -13
  4. package/dist/JSONGenerativeUI.client.js.map +1 -1
  5. package/dist/RadioGroupScope.d.ts +6 -0
  6. package/dist/RadioGroupScope.d.ts.map +1 -0
  7. package/dist/RadioGroupScope.js +25 -0
  8. package/dist/RadioGroupScope.js.map +1 -0
  9. package/dist/a2ui/BindingContext.d.ts +11 -0
  10. package/dist/a2ui/BindingContext.d.ts.map +1 -0
  11. package/dist/a2ui/BindingContext.js +12 -0
  12. package/dist/a2ui/BindingContext.js.map +1 -0
  13. package/dist/a2ui/PresentRenderer.d.ts +11 -0
  14. package/dist/a2ui/PresentRenderer.d.ts.map +1 -0
  15. package/dist/a2ui/PresentRenderer.js +39 -0
  16. package/dist/a2ui/PresentRenderer.js.map +1 -0
  17. package/dist/a2ui/convert.d.ts +6 -0
  18. package/dist/a2ui/convert.d.ts.map +1 -1
  19. package/dist/a2ui/convert.js +363 -85
  20. package/dist/a2ui/convert.js.map +1 -1
  21. package/dist/a2ui/dataModel.d.ts +9 -0
  22. package/dist/a2ui/dataModel.d.ts.map +1 -0
  23. package/dist/a2ui/dataModel.js +44 -0
  24. package/dist/a2ui/dataModel.js.map +1 -0
  25. package/dist/a2ui/reducer.d.ts +5 -0
  26. package/dist/a2ui/reducer.d.ts.map +1 -1
  27. package/dist/a2ui/reducer.js +3 -3
  28. package/dist/a2ui/reducer.js.map +1 -1
  29. package/dist/a2ui/types.d.ts +8 -2
  30. package/dist/a2ui/types.d.ts.map +1 -1
  31. package/dist/a2ui/types.js.map +1 -1
  32. package/dist/a2ui/valueFunctions.d.ts +16 -0
  33. package/dist/a2ui/valueFunctions.d.ts.map +1 -0
  34. package/dist/a2ui/valueFunctions.js +332 -0
  35. package/dist/a2ui/valueFunctions.js.map +1 -0
  36. package/dist/actionRegistry.d.ts +1 -1
  37. package/dist/actionRegistry.js.map +1 -1
  38. package/dist/answeredValues.d.ts +16 -0
  39. package/dist/answeredValues.d.ts.map +1 -0
  40. package/dist/answeredValues.js +37 -0
  41. package/dist/answeredValues.js.map +1 -0
  42. package/dist/constants.d.ts +3 -0
  43. package/dist/constants.d.ts.map +1 -1
  44. package/dist/constants.js +4 -1
  45. package/dist/constants.js.map +1 -1
  46. package/dist/fieldReferences.d.ts +9 -0
  47. package/dist/fieldReferences.d.ts.map +1 -0
  48. package/dist/fieldReferences.js +53 -0
  49. package/dist/fieldReferences.js.map +1 -0
  50. package/dist/generativeUIToJSX.js +3 -3
  51. package/dist/generativeUIToJSX.js.map +1 -1
  52. package/dist/index.js +2 -2
  53. package/dist/index.server.js +2 -2
  54. package/dist/index.shared.js +2 -2
  55. package/dist/ir.d.ts +1 -1
  56. package/dist/ir.js.map +1 -1
  57. package/dist/renderGenerativeUI.d.ts.map +1 -1
  58. package/dist/renderGenerativeUI.js +12 -1
  59. package/dist/renderGenerativeUI.js.map +1 -1
  60. package/dist/slack/constants.d.ts +2 -0
  61. package/dist/slack/constants.d.ts.map +1 -1
  62. package/dist/slack/constants.js +3 -1
  63. package/dist/slack/constants.js.map +1 -1
  64. package/dist/slack/decodeBlockAction.d.ts +3 -1
  65. package/dist/slack/decodeBlockAction.d.ts.map +1 -1
  66. package/dist/slack/decodeBlockAction.js +96 -7
  67. package/dist/slack/decodeBlockAction.js.map +1 -1
  68. package/dist/slack/fromSlackBlocks.d.ts.map +1 -1
  69. package/dist/slack/fromSlackBlocks.js +43 -3
  70. package/dist/slack/fromSlackBlocks.js.map +1 -1
  71. package/dist/slack/toSlackBlocks.d.ts.map +1 -1
  72. package/dist/slack/toSlackBlocks.js +244 -20
  73. package/dist/slack/toSlackBlocks.js.map +1 -1
  74. package/dist/slack/types.d.ts +24 -1
  75. package/dist/slack/types.d.ts.map +1 -1
  76. package/dist/slack.d.ts +1 -1
  77. package/dist/slack.d.ts.map +1 -1
  78. package/dist/teams/constants.d.ts +1 -1
  79. package/dist/teams/constants.d.ts.map +1 -1
  80. package/dist/teams/constants.js +1 -1
  81. package/dist/teams/constants.js.map +1 -1
  82. package/dist/teams/decodeSubmitData.d.ts +4 -0
  83. package/dist/teams/decodeSubmitData.d.ts.map +1 -1
  84. package/dist/teams/decodeSubmitData.js +29 -3
  85. package/dist/teams/decodeSubmitData.js.map +1 -1
  86. package/dist/teams/temporalId.d.ts +5 -0
  87. package/dist/teams/temporalId.d.ts.map +1 -0
  88. package/dist/teams/temporalId.js +28 -0
  89. package/dist/teams/temporalId.js.map +1 -0
  90. package/dist/teams/toAdaptiveCard.d.ts +5 -4
  91. package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
  92. package/dist/teams/toAdaptiveCard.js +136 -18
  93. package/dist/teams/toAdaptiveCard.js.map +1 -1
  94. package/dist/teams/toTeamsAttachments.d.ts.map +1 -1
  95. package/dist/teams/toTeamsAttachments.js.map +1 -1
  96. package/dist/teams/types.d.ts +23 -2
  97. package/dist/teams/types.d.ts.map +1 -1
  98. package/dist/teams.d.ts +1 -1
  99. package/dist/teams.d.ts.map +1 -1
  100. package/dist/temporal.d.ts +33 -0
  101. package/dist/temporal.d.ts.map +1 -0
  102. package/dist/temporal.js +152 -0
  103. package/dist/temporal.js.map +1 -0
  104. package/dist/vocabulary/alert.d.ts +9 -2
  105. package/dist/vocabulary/alert.d.ts.map +1 -1
  106. package/dist/vocabulary/alert.js +116 -19
  107. package/dist/vocabulary/alert.js.map +1 -1
  108. package/dist/vocabulary/collectFormValues.d.ts +2 -1
  109. package/dist/vocabulary/collectFormValues.d.ts.map +1 -1
  110. package/dist/vocabulary/collectFormValues.js +15 -4
  111. package/dist/vocabulary/collectFormValues.js.map +1 -1
  112. package/dist/vocabulary/data.d.ts +22 -5
  113. package/dist/vocabulary/data.d.ts.map +1 -1
  114. package/dist/vocabulary/data.js +278 -83
  115. package/dist/vocabulary/data.js.map +1 -1
  116. package/dist/vocabulary/dispatch.d.ts +3 -2
  117. package/dist/vocabulary/dispatch.d.ts.map +1 -1
  118. package/dist/vocabulary/dispatch.js +33 -4
  119. package/dist/vocabulary/dispatch.js.map +1 -1
  120. package/dist/vocabulary/fact.d.ts +8 -1
  121. package/dist/vocabulary/fact.d.ts.map +1 -1
  122. package/dist/vocabulary/fact.js +37 -11
  123. package/dist/vocabulary/fact.js.map +1 -1
  124. package/dist/vocabulary/form.d.ts.map +1 -1
  125. package/dist/vocabulary/form.js +3 -3
  126. package/dist/vocabulary/form.js.map +1 -1
  127. package/dist/vocabulary/formatValue.d.ts +7 -0
  128. package/dist/vocabulary/formatValue.d.ts.map +1 -0
  129. package/dist/vocabulary/formatValue.js +76 -0
  130. package/dist/vocabulary/formatValue.js.map +1 -0
  131. package/dist/vocabulary/icon.d.ts +1 -1
  132. package/dist/vocabulary/interactive.d.ts +143 -8
  133. package/dist/vocabulary/interactive.d.ts.map +1 -1
  134. package/dist/vocabulary/interactive.js +458 -107
  135. package/dist/vocabulary/interactive.js.map +1 -1
  136. package/dist/vocabulary/layout.d.ts.map +1 -1
  137. package/dist/vocabulary/layout.js +4 -4
  138. package/dist/vocabulary/layout.js.map +1 -1
  139. package/dist/vocabulary/list.js +2 -2
  140. package/dist/vocabulary/list.js.map +1 -1
  141. package/dist/vocabulary/text.d.ts +1 -1
  142. package/package.json +8 -5
  143. package/src/JSONGenerativeUI.client.tsx +84 -40
  144. package/src/JSONGenerativeUI.test.tsx +964 -68
  145. package/src/RadioGroupScope.server.test.tsx +24 -0
  146. package/src/RadioGroupScope.tsx +34 -0
  147. package/src/a2ui/BindingContext.ts +26 -0
  148. package/src/a2ui/PresentRenderer.tsx +83 -0
  149. package/src/a2ui/convert.test.ts +2054 -229
  150. package/src/a2ui/convert.ts +665 -134
  151. package/src/a2ui/dataModel.ts +69 -0
  152. package/src/a2ui/reducer.test.ts +35 -0
  153. package/src/a2ui/reducer.ts +5 -4
  154. package/src/a2ui/types.ts +9 -2
  155. package/src/a2ui/valueFunctions.test.ts +271 -0
  156. package/src/a2ui/valueFunctions.ts +523 -0
  157. package/src/actionRegistry.ts +1 -1
  158. package/src/answeredValues.test.ts +33 -0
  159. package/src/answeredValues.tsx +70 -0
  160. package/src/constants.ts +5 -0
  161. package/src/fieldReferences.test.ts +66 -0
  162. package/src/fieldReferences.ts +83 -0
  163. package/src/generativeUIToJSX.test.ts +18 -0
  164. package/src/generativeUIToJSX.ts +4 -6
  165. package/src/index.server.test.ts +106 -0
  166. package/src/ir.ts +1 -1
  167. package/src/renderGenerativeUI.test.tsx +14 -5
  168. package/src/renderGenerativeUI.tsx +30 -1
  169. package/src/slack/a2uiSlack.test.ts +198 -0
  170. package/src/slack/constants.ts +3 -0
  171. package/src/slack/datePicker.test.ts +612 -0
  172. package/src/slack/decodeBlockAction.test.ts +98 -0
  173. package/src/slack/decodeBlockAction.ts +182 -16
  174. package/src/slack/fromSlackBlocks.test.ts +222 -3
  175. package/src/slack/fromSlackBlocks.ts +82 -2
  176. package/src/slack/toSlackBlocks.test.ts +682 -3
  177. package/src/slack/toSlackBlocks.ts +463 -25
  178. package/src/slack/types.ts +28 -0
  179. package/src/slack.ts +2 -0
  180. package/src/teams/constants.ts +1 -1
  181. package/src/teams/decodeSubmitData.test.ts +249 -0
  182. package/src/teams/decodeSubmitData.ts +57 -4
  183. package/src/teams/temporal.test.ts +766 -0
  184. package/src/teams/temporalId.ts +56 -0
  185. package/src/teams/toAdaptiveCard.test.ts +219 -0
  186. package/src/teams/toAdaptiveCard.ts +259 -21
  187. package/src/teams/toTeamsAttachments.ts +4 -1
  188. package/src/teams/types.ts +21 -1
  189. package/src/teams.ts +1 -0
  190. package/src/temporal.test.ts +319 -0
  191. package/src/temporal.ts +259 -0
  192. package/src/vocabulary/alert.test.tsx +288 -7
  193. package/src/vocabulary/alert.tsx +168 -26
  194. package/src/vocabulary/collectFormValues.test.ts +225 -1
  195. package/src/vocabulary/collectFormValues.ts +54 -5
  196. package/src/vocabulary/data.test.tsx +248 -6
  197. package/src/vocabulary/data.tsx +398 -100
  198. package/src/vocabulary/datepicker.dom.test.tsx +1148 -0
  199. package/src/vocabulary/dispatch.ts +63 -2
  200. package/src/vocabulary/fact.test.tsx +67 -0
  201. package/src/vocabulary/fact.tsx +40 -9
  202. package/src/vocabulary/form.tsx +7 -2
  203. package/src/vocabulary/formatValue.test.ts +72 -0
  204. package/src/vocabulary/formatValue.ts +127 -0
  205. package/src/vocabulary/index.test.ts +2 -0
  206. package/src/vocabulary/interactive.dispatch.test.tsx +167 -79
  207. package/src/vocabulary/interactive.dom.test.tsx +1747 -0
  208. package/src/vocabulary/interactive.test.tsx +60 -4
  209. package/src/vocabulary/interactive.tsx +910 -155
  210. package/src/vocabulary/layout.test.tsx +6 -4
  211. package/src/vocabulary/layout.tsx +13 -2
  212. package/src/vocabulary/list.tsx +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"generativeUIToJSX.js","names":[],"sources":["../src/generativeUIToJSX.ts"],"sourcesContent":["import { TYPE_KEY } from \"./constants\";\n\n/** Options for {@link generativeUIToJSX}. */\nexport interface GenerativeUIToJSXOptions {\n /** Escape string children so the output is safe to paste back into a JSX file. Defaults to `false`. */\n escape?: boolean;\n /** Pretty-print nested element trees with two-space indentation. Defaults to `false`. */\n pretty?: boolean;\n}\n\n/**\n * Serializes a generative-UI node to a JSX-like string for display: the \"view source\" of a model-produced tree. The wire form `{ $type: \"Weather\", id: \"x\" }` becomes `<Weather id=\"x\" />`, and nested `children` render between tags: `<Card title=\"Hi\"><Text>hello</Text></Card>`. A model-provided `$key` becomes the JSX `key` attribute.\n *\n * By default this is a faithful textual rendering, not a parser: text children are emitted verbatim (not HTML/JSX-escaped), so the result is meant to be shown, not re-parsed. Returns `\"\"` for nodes that aren't renderable (no `$type` yet, `null`, booleans).\n *\n * Pass `{ escape: true }` to make the output safe to paste back into JSX: any string child containing `<`, `>`, `&`, `{`, or `}` is emitted as a JSON-stringified expression (`{JSON.stringify(child)}`) instead of verbatim, while a string child with none of those characters still renders verbatim.\n *\n * Pass `{ pretty: true }` to pretty-print nested element trees with two-space indentation, while pure text children stay on one line.\n */\nexport function generativeUIToJSX(\n node: unknown,\n options?: GenerativeUIToJSXOptions,\n): string {\n return toJSX(node, 0, options?.escape ?? false, options?.pretty ?? false, 0);\n}\n\n/**\n * The deepest tree we serialize. Input is model-produced, so a runaway/\n * adversarial response could nest arbitrarily deep and overflow the stack — past\n * this depth (far beyond any real UI) we stop. Mirrors the renderer's bound.\n */\nconst MAX_DEPTH = 64;\n\n/** Characters that would break JSX if emitted verbatim inside a text child. */\nconst UNSAFE_CHILD_CHARS = /[<>&{}]/;\n\nfunction toJSX(\n node: unknown,\n depth: number,\n escape: boolean,\n pretty: boolean,\n indent: number,\n): string {\n if (depth > MAX_DEPTH) return \"\";\n if (node == null || typeof node === \"boolean\") return \"\";\n if (typeof node === \"string\") return formatChildText(node, escape);\n if (typeof node === \"number\") return String(node);\n if (Array.isArray(node)) {\n if (pretty) {\n return collectBlockLines(node, depth, escape, indent).join(\"\\n\");\n }\n return node\n .map((child) => toJSX(child, depth + 1, escape, false, 0))\n .join(\"\");\n }\n if (typeof node !== \"object\") return \"\";\n\n const {\n [TYPE_KEY]: type,\n $key,\n children,\n ...props\n } = node as Record<string, unknown>;\n if (typeof type !== \"string\") return \"\";\n\n const attrs =\n formatAttr(\"key\", $key) +\n Object.entries(props)\n .map(([key, value]) => formatAttr(key, value))\n .join(\"\");\n\n if (!pretty) {\n const inner =\n children === undefined\n ? \"\"\n : toJSX(children, depth + 1, escape, false, 0);\n\n return inner === \"\"\n ? `<${type}${attrs} />`\n : `<${type}${attrs}>${inner}</${type}>`;\n }\n\n const pad = \" \".repeat(indent);\n\n if (children === undefined || !hasElementChild(children)) {\n const inner =\n children === undefined\n ? \"\"\n : toJSX(children, depth + 1, escape, false, 0);\n return inner === \"\"\n ? `${pad}<${type}${attrs} />`\n : `${pad}<${type}${attrs}>${inner}</${type}>`;\n }\n\n const lines = collectBlockLines(children, depth + 1, escape, indent + 1);\n if (lines.length === 0) {\n return `${pad}<${type}${attrs} />`;\n }\n return `${pad}<${type}${attrs}>\\n${lines.join(\"\\n\")}\\n${pad}</${type}>`;\n}\n\nfunction hasElementChild(node: unknown, depth = 0): boolean {\n if (depth > MAX_DEPTH) return false;\n if (Array.isArray(node))\n return node.some((child) => hasElementChild(child, depth + 1));\n return (\n node != null &&\n typeof node === \"object\" &&\n typeof (node as Record<string, unknown>)[TYPE_KEY] === \"string\"\n );\n}\n\nfunction collectBlockLines(\n node: unknown,\n depth: number,\n escape: boolean,\n indent: number,\n): string[] {\n if (depth > MAX_DEPTH) return [];\n if (node == null || typeof node === \"boolean\") return [];\n if (Array.isArray(node)) {\n return node.flatMap((child) =>\n collectBlockLines(child, depth + 1, escape, indent),\n );\n }\n if (typeof node === \"string\") {\n return [`${\" \".repeat(indent)}${formatChildText(node, escape)}`];\n }\n if (typeof node === \"number\") {\n return [`${\" \".repeat(indent)}${String(node)}`];\n }\n const rendered = toJSX(node, depth, escape, true, indent);\n return rendered === \"\" ? [] : [rendered];\n}\n\nfunction formatChildText(value: string, escape: boolean): string {\n return escape && UNSAFE_CHILD_CHARS.test(value)\n ? `{${JSON.stringify(value)}}`\n : value;\n}\n\n/** Formats one prop as a JSX attribute (`id=\"x\"`, `count={3}`, `open`, …). */\nfunction formatAttr(key: string, value: unknown): string {\n if (value === undefined) return \"\";\n if (value === true) return ` ${key}`;\n if (typeof value === \"string\") {\n // Plain double-quoted form when safe; expression form when a quote or\n // newline would break the attribute.\n return /[\"\\n]/.test(value)\n ? ` ${key}={${JSON.stringify(value)}}`\n : ` ${key}=\"${value}\"`;\n }\n return ` ${key}={${JSON.stringify(value)}}`;\n}\n"],"mappings":";;;;;;;;;;;AAmBA,SAAgB,kBACd,MACA,SACQ;CACR,OAAO,MAAM,MAAM,GAAG,SAAS,UAAU,OAAO,SAAS,UAAU,OAAO,CAAC;AAC7E;;;;;;AAOA,MAAM,YAAY;;AAGlB,MAAM,qBAAqB;AAE3B,SAAS,MACP,MACA,OACA,QACA,QACA,QACQ;CACR,IAAI,QAAQ,WAAW,OAAO;CAC9B,IAAI,QAAQ,QAAQ,OAAO,SAAS,WAAW,OAAO;CACtD,IAAI,OAAO,SAAS,UAAU,OAAO,gBAAgB,MAAM,MAAM;CACjE,IAAI,OAAO,SAAS,UAAU,OAAO,OAAO,IAAI;CAChD,IAAI,MAAM,QAAQ,IAAI,GAAG;EACvB,IAAI,QACF,OAAO,kBAAkB,MAAM,OAAO,QAAQ,MAAM,CAAC,CAAC,KAAK,IAAI;EAEjE,OAAO,KACJ,KAAK,UAAU,MAAM,OAAO,QAAQ,GAAG,QAAQ,OAAO,CAAC,CAAC,CAAC,CACzD,KAAK,EAAE;CACZ;CACA,IAAI,OAAO,SAAS,UAAU,OAAO;CAErC,MAAM,GACH,WAAW,MACZ,MACA,UACA,GAAG,UACD;CACJ,IAAI,OAAO,SAAS,UAAU,OAAO;CAErC,MAAM,QACJ,WAAW,OAAO,IAAI,IACtB,OAAO,QAAQ,KAAK,CAAC,CAClB,KAAK,CAAC,KAAK,WAAW,WAAW,KAAK,KAAK,CAAC,CAAC,CAC7C,KAAK,EAAE;CAEZ,IAAI,CAAC,QAAQ;EACX,MAAM,QACJ,aAAa,KAAA,IACT,KACA,MAAM,UAAU,QAAQ,GAAG,QAAQ,OAAO,CAAC;EAEjD,OAAO,UAAU,KACb,IAAI,OAAO,MAAM,OACjB,IAAI,OAAO,MAAM,GAAG,MAAM,IAAI,KAAK;CACzC;CAEA,MAAM,MAAM,KAAK,OAAO,MAAM;CAE9B,IAAI,aAAa,KAAA,KAAa,CAAC,gBAAgB,QAAQ,GAAG;EACxD,MAAM,QACJ,aAAa,KAAA,IACT,KACA,MAAM,UAAU,QAAQ,GAAG,QAAQ,OAAO,CAAC;EACjD,OAAO,UAAU,KACb,GAAG,IAAI,GAAG,OAAO,MAAM,OACvB,GAAG,IAAI,GAAG,OAAO,MAAM,GAAG,MAAM,IAAI,KAAK;CAC/C;CAEA,MAAM,QAAQ,kBAAkB,UAAU,QAAQ,GAAG,QAAQ,SAAS,CAAC;CACvE,IAAI,MAAM,WAAW,GACnB,OAAO,GAAG,IAAI,GAAG,OAAO,MAAM;CAEhC,OAAO,GAAG,IAAI,GAAG,OAAO,MAAM,KAAK,MAAM,KAAK,IAAI,EAAE,IAAI,IAAI,IAAI,KAAK;AACvE;AAEA,SAAS,gBAAgB,MAAe,QAAQ,GAAY;CAC1D,IAAI,QAAQ,WAAW,OAAO;CAC9B,IAAI,MAAM,QAAQ,IAAI,GACpB,OAAO,KAAK,MAAM,UAAU,gBAAgB,OAAO,QAAQ,CAAC,CAAC;CAC/D,OACE,QAAQ,QACR,OAAO,SAAS,YAChB,OAAQ,KAAA,aAA+C;AAE3D;AAEA,SAAS,kBACP,MACA,OACA,QACA,QACU;CACV,IAAI,QAAQ,WAAW,OAAO,CAAC;CAC/B,IAAI,QAAQ,QAAQ,OAAO,SAAS,WAAW,OAAO,CAAC;CACvD,IAAI,MAAM,QAAQ,IAAI,GACpB,OAAO,KAAK,SAAS,UACnB,kBAAkB,OAAO,QAAQ,GAAG,QAAQ,MAAM,CACpD;CAEF,IAAI,OAAO,SAAS,UAClB,OAAO,CAAC,GAAG,KAAK,OAAO,MAAM,IAAI,gBAAgB,MAAM,MAAM,GAAG;CAElE,IAAI,OAAO,SAAS,UAClB,OAAO,CAAC,GAAG,KAAK,OAAO,MAAM,IAAI,OAAO,IAAI,GAAG;CAEjD,MAAM,WAAW,MAAM,MAAM,OAAO,QAAQ,MAAM,MAAM;CACxD,OAAO,aAAa,KAAK,CAAC,IAAI,CAAC,QAAQ;AACzC;AAEA,SAAS,gBAAgB,OAAe,QAAyB;CAC/D,OAAO,UAAU,mBAAmB,KAAK,KAAK,IAC1C,IAAI,KAAK,UAAU,KAAK,EAAE,KAC1B;AACN;;AAGA,SAAS,WAAW,KAAa,OAAwB;CACvD,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,IAAI,UAAU,MAAM,OAAO,IAAI;CAC/B,IAAI,OAAO,UAAU,UAGnB,OAAO,QAAQ,KAAK,KAAK,IACrB,IAAI,IAAI,IAAI,KAAK,UAAU,KAAK,EAAE,KAClC,IAAI,IAAI,IAAI,MAAM;CAExB,OAAO,IAAI,IAAI,IAAI,KAAK,UAAU,KAAK,EAAE;AAC3C"}
1
+ {"version":3,"file":"generativeUIToJSX.js","names":[],"sources":["../src/generativeUIToJSX.ts"],"sourcesContent":["import { TYPE_KEY } from \"./constants\";\n\n/** Options for {@link generativeUIToJSX}. */\nexport interface GenerativeUIToJSXOptions {\n /** Escape string children so the output is safe to paste back into a JSX file. Defaults to `false`. */\n escape?: boolean;\n /** Pretty-print nested element trees with two-space indentation. Defaults to `false`. */\n pretty?: boolean;\n}\n\n/**\n * Serializes a generative-UI node to a JSX-like string for display: the \"view source\" of a model-produced tree. The wire form `{ $type: \"Weather\", id: \"x\" }` becomes `<Weather id=\"x\" />`, and nested `children` render between tags: `<Card title=\"Hi\"><Text>hello</Text></Card>`. A model-provided `$key` becomes the JSX `key` attribute.\n *\n * By default this is a faithful textual rendering, not a parser: text children are emitted verbatim (not HTML/JSX-escaped), so the result is meant to be shown, not re-parsed. Returns `\"\"` for nodes that aren't renderable (no `$type` yet, `null`, booleans).\n *\n * Pass `{ escape: true }` to make the output safe to paste back into JSX: any string child containing `<`, `>`, `&`, `{`, or `}` is emitted as a JSON-stringified expression (`{JSON.stringify(child)}`) instead of verbatim, while a string child with none of those characters still renders verbatim.\n *\n * Pass `{ pretty: true }` to pretty-print nested element trees with two-space indentation, while pure text children stay on one line.\n */\nexport function generativeUIToJSX(\n node: unknown,\n options?: GenerativeUIToJSXOptions,\n): string {\n return toJSX(node, 0, options?.escape ?? false, options?.pretty ?? false, 0);\n}\n\n/**\n * The deepest tree we serialize. Input is model-produced, so a runaway/\n * adversarial response could nest arbitrarily deep and overflow the stack — past\n * this depth (far beyond any real UI) we stop. Mirrors the renderer's bound.\n */\nconst MAX_DEPTH = 64;\n\n/** Characters that would break JSX if emitted verbatim inside a text child. */\nconst UNSAFE_CHILD_CHARS = /[<>&{}]/;\n\nfunction toJSX(\n node: unknown,\n depth: number,\n escape: boolean,\n pretty: boolean,\n indent: number,\n): string {\n if (depth > MAX_DEPTH) return \"\";\n if (node == null || typeof node === \"boolean\") return \"\";\n if (typeof node === \"string\") return formatChildText(node, escape);\n if (typeof node === \"number\") return String(node);\n if (Array.isArray(node)) {\n if (pretty) {\n return collectBlockLines(node, depth, escape, indent).join(\"\\n\");\n }\n return node\n .map((child) => toJSX(child, depth + 1, escape, false, 0))\n .join(\"\");\n }\n if (typeof node !== \"object\") return \"\";\n\n const {\n [TYPE_KEY]: type,\n $key,\n children,\n ...props\n } = node as Record<string, unknown>;\n if (typeof type !== \"string\") return \"\";\n\n const attrs =\n formatAttr(\"key\", $key, escape) +\n Object.entries(props)\n .map(([key, value]) => formatAttr(key, value, escape))\n .join(\"\");\n\n if (!pretty) {\n const inner =\n children === undefined\n ? \"\"\n : toJSX(children, depth + 1, escape, false, 0);\n\n return inner === \"\"\n ? `<${type}${attrs} />`\n : `<${type}${attrs}>${inner}</${type}>`;\n }\n\n const pad = \" \".repeat(indent);\n\n if (children === undefined || !hasElementChild(children)) {\n const inner =\n children === undefined\n ? \"\"\n : toJSX(children, depth + 1, escape, false, 0);\n return inner === \"\"\n ? `${pad}<${type}${attrs} />`\n : `${pad}<${type}${attrs}>${inner}</${type}>`;\n }\n\n const lines = collectBlockLines(children, depth + 1, escape, indent + 1);\n if (lines.length === 0) {\n return `${pad}<${type}${attrs} />`;\n }\n return `${pad}<${type}${attrs}>\\n${lines.join(\"\\n\")}\\n${pad}</${type}>`;\n}\n\nfunction hasElementChild(node: unknown, depth = 0): boolean {\n if (depth > MAX_DEPTH) return false;\n if (Array.isArray(node))\n return node.some((child) => hasElementChild(child, depth + 1));\n return (\n node != null &&\n typeof node === \"object\" &&\n typeof (node as Record<string, unknown>)[TYPE_KEY] === \"string\"\n );\n}\n\nfunction collectBlockLines(\n node: unknown,\n depth: number,\n escape: boolean,\n indent: number,\n): string[] {\n if (depth > MAX_DEPTH) return [];\n if (node == null || typeof node === \"boolean\") return [];\n if (Array.isArray(node)) {\n return node.flatMap((child) =>\n collectBlockLines(child, depth + 1, escape, indent),\n );\n }\n if (typeof node === \"string\") {\n return [`${\" \".repeat(indent)}${formatChildText(node, escape)}`];\n }\n if (typeof node === \"number\") {\n return [`${\" \".repeat(indent)}${String(node)}`];\n }\n const rendered = toJSX(node, depth, escape, true, indent);\n return rendered === \"\" ? [] : [rendered];\n}\n\nfunction formatChildText(value: string, escape: boolean): string {\n return escape && UNSAFE_CHILD_CHARS.test(value)\n ? `{${JSON.stringify(value)}}`\n : value;\n}\n\n/** Formats one prop as a JSX attribute (`id=\"x\"`, `count={3}`, `open`, …). */\nfunction formatAttr(key: string, value: unknown, escape: boolean): string {\n if (value === undefined) return \"\";\n if (value === true) return ` ${key}`;\n if (typeof value === \"string\") {\n return /[\"\\n]/.test(value) || (escape && value.includes(\"&\"))\n ? ` ${key}={${JSON.stringify(value)}}`\n : ` ${key}=\"${value}\"`;\n }\n return ` ${key}={${JSON.stringify(value)}}`;\n}\n"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  import { TYPE_KEY } from "./constants.js";
2
- import { buildPresentParameters } from "./buildPresentParameters.js";
3
- import { createActionRegistry, emptyActionRegistry } from "./actionRegistry.js";
4
2
  import { ALERT_TONES, ALIGNS, BUTTON_STYLES, COLORS, ICON_NAMES, IMAGE_SIZE_TOKENS, JUSTIFIES, TEXT_SIZES, WEIGHTS, normalizeSpec, normalizeUINode } from "./ir.js";
5
3
  import { renderGenerativeUI } from "./renderGenerativeUI.js";
4
+ import { buildPresentParameters } from "./buildPresentParameters.js";
5
+ import { createActionRegistry, emptyActionRegistry } from "./actionRegistry.js";
6
6
  import { JSONGenerativeUI } from "./JSONGenerativeUI.client.js";
7
7
  import { defineGenerativeComponents } from "./defineGenerativeComponents.js";
8
8
  import { generativeUIToJSX } from "./generativeUIToJSX.js";
@@ -1,8 +1,8 @@
1
1
  import { TYPE_KEY } from "./constants.js";
2
- import { buildPresentParameters } from "./buildPresentParameters.js";
3
- import { createActionRegistry, emptyActionRegistry } from "./actionRegistry.js";
4
2
  import { ALERT_TONES, ALIGNS, BUTTON_STYLES, COLORS, ICON_NAMES, IMAGE_SIZE_TOKENS, JUSTIFIES, TEXT_SIZES, WEIGHTS, normalizeSpec, normalizeUINode } from "./ir.js";
5
3
  import { renderGenerativeUI } from "./renderGenerativeUI.js";
4
+ import { buildPresentParameters } from "./buildPresentParameters.js";
5
+ import { createActionRegistry, emptyActionRegistry } from "./actionRegistry.js";
6
6
  import { JSONGenerativeUI } from "./JSONGenerativeUI.server.js";
7
7
  import { defineGenerativeComponents } from "./defineGenerativeComponents.js";
8
8
  import { generativeUIToJSX } from "./generativeUIToJSX.js";
@@ -1,8 +1,8 @@
1
1
  import { TYPE_KEY } from "./constants.js";
2
- import { buildPresentParameters } from "./buildPresentParameters.js";
3
- import { createActionRegistry, emptyActionRegistry } from "./actionRegistry.js";
4
2
  import { ALERT_TONES, ALIGNS, BUTTON_STYLES, COLORS, ICON_NAMES, IMAGE_SIZE_TOKENS, JUSTIFIES, TEXT_SIZES, WEIGHTS, normalizeSpec, normalizeUINode } from "./ir.js";
5
3
  import { renderGenerativeUI } from "./renderGenerativeUI.js";
4
+ import { buildPresentParameters } from "./buildPresentParameters.js";
5
+ import { createActionRegistry, emptyActionRegistry } from "./actionRegistry.js";
6
6
  import { defineGenerativeComponents } from "./defineGenerativeComponents.js";
7
7
  import { generativeUIToJSX } from "./generativeUIToJSX.js";
8
8
  import { defaultGenerativeUILibrary } from "./vocabulary/index.js";
package/dist/ir.d.ts CHANGED
@@ -35,7 +35,7 @@ export declare const ICON_NAMES: readonly ["sun", "moon", "cloud", "rain", "snow
35
35
  /** A name from the built-in icon set; the closed enum lets the model see exactly which icons exist. */
36
36
  export type IconName = (typeof ICON_NAMES)[number];
37
37
  /**
38
- * Behavior payload carried by an interactive node. `type` is resolved by the host's action registry, not the renderer; keeping behavior as data keeps the tree serializable, so the same node renders on web while converters may bind the type to a native action id on other platforms.
38
+ * Behavior payload carried by an interactive node. `type` is resolved by the host's action registry, not the renderer; keeping behavior as data keeps the tree serializable, so the same node renders on web while converters may bind the type to a native action id on other platforms. A `{ "$field": name }` value anywhere in the payload refers to the current value of the control with that `name`, resolved when the action fires, and `{ "$field": name, "fallback": value }` resolves to `fallback` when no control holds a value.
39
39
  */
40
40
  export interface Action {
41
41
  readonly type: string;
package/dist/ir.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"ir.js","names":[],"sources":["../src/ir.ts"],"sourcesContent":["/**\n * The generative-ui intermediate representation: a model-emitted, cross-platform\n * UI tree. React-free, so converters and non-web runtimes consume it without\n * pulling React. The flat `$type` shape is the canonical form; the legacy\n * `component` shape is accepted as a backward-compatible alias.\n *\n * Reserved keys are partitioned off from component props so the component prop\n * namespace stays fully free:\n *\n * - `$`-prefixed keys are framework-reserved (`$type`, `$key`, `$action`,\n * `$status`). Components never declare `$`-prefixed props, so a component\n * can use `type`, `status`, `variant`, etc. as ordinary props without\n * colliding with the framework.\n * - `children` is additionally reserved (the JSX convention).\n * - every other key is an inline prop passed straight to the component.\n */\n\nimport { TYPE_KEY } from \"./constants\";\n\nexport const TEXT_SIZES = [\"sm\", \"md\", \"lg\", \"xl\", \"2xl\", \"3xl\"] as const;\nexport type TextSize = (typeof TEXT_SIZES)[number];\n\nexport const IMAGE_SIZE_TOKENS = [\"sm\", \"md\", \"lg\"] as const;\nexport type ImageSize = (typeof IMAGE_SIZE_TOKENS)[number] | number;\n\nexport const WEIGHTS = [\"normal\", \"medium\", \"semibold\", \"bold\"] as const;\nexport type Weight = (typeof WEIGHTS)[number];\n\nexport const COLORS = [\n \"emphasis\",\n \"secondary\",\n \"alpha-70\",\n \"white\",\n \"white-70\",\n \"white-50\",\n] as const;\nexport type Color = (typeof COLORS)[number];\n\nexport const ALIGNS = [\"start\", \"center\", \"end\"] as const;\nexport type Align = (typeof ALIGNS)[number];\n\nexport const JUSTIFIES = [\"start\", \"center\", \"end\", \"between\"] as const;\nexport type Justify = (typeof JUSTIFIES)[number];\n\nexport const BUTTON_STYLES = [\n \"primary\",\n \"secondary\",\n \"outline\",\n \"ghost\",\n \"danger\",\n] as const;\nexport type ButtonStyle = (typeof BUTTON_STYLES)[number];\n\nexport const ALERT_TONES = [\"info\", \"success\", \"warning\", \"danger\"] as const;\n/** A severity level, from informational to destructive. */\nexport type AlertTone = (typeof ALERT_TONES)[number];\n\nexport const ICON_NAMES = [\n \"sun\",\n \"moon\",\n \"cloud\",\n \"rain\",\n \"snow\",\n \"wind\",\n \"play\",\n \"pause\",\n \"check\",\n \"x\",\n \"star\",\n \"heart\",\n \"arrow-right\",\n \"arrow-up-right\",\n \"chevron-right\",\n \"calendar\",\n \"clock\",\n \"map-pin\",\n \"plane\",\n \"truck\",\n \"credit-card\",\n \"user\",\n \"search\",\n \"bell\",\n] as const;\n/** A name from the built-in icon set; the closed enum lets the model see exactly which icons exist. */\nexport type IconName = (typeof ICON_NAMES)[number];\n\n/**\n * Behavior payload carried by an interactive node. `type` is resolved by the host's action registry, not the renderer; keeping behavior as data keeps the tree serializable, so the same node renders on web while converters may bind the type to a native action id on other platforms.\n */\nexport interface Action {\n readonly type: string;\n readonly [payload: string]: unknown;\n}\n\n/**\n * Anything renderable as generative UI, as the model emits it. The renderer\n * also accepts `number`, `boolean`, `null`, `undefined`, and arrays at the\n * input boundary (numbers render as text, falsy/boolean as nothing, arrays as\n * lists); {@link normalizeUINode} accepts that full range.\n */\nexport type UINode = string | number | UIElement | LegacyComponentNode;\n\nexport type UIChildren = UINode | readonly UINode[];\n\n/** The flat node shape: inline props keep the tree compact and natural for a\n * model to emit, instead of a nested `{ type, props }` bag. Reserved keys are\n * stripped before props reach the component (see the module header). */\nexport interface UIElement {\n readonly $type: string;\n readonly $key?: string | number;\n readonly children?: UIChildren;\n readonly $action?: Action;\n readonly [prop: string]: unknown;\n}\n\n/**\n * The legacy node shape: a `component` name plus a nested `props` bag. Kept\n * for backward compatibility. New code authors the flat {@link UIElement}\n * shape instead.\n */\nexport interface LegacyComponentNode {\n readonly component: string;\n readonly props?: Record<string, unknown>;\n readonly children?: UIChildren;\n readonly key?: string;\n}\n\nexport type UISpec = UINode | readonly UINode[];\n\n/**\n * A node normalized to a single canonical shape: a `type` string, an inline\n * `props` bag, recursive `children`, an optional `key`, and an optional\n * `action`. Renderers and platform converters consume this form, so they never\n * branch on whether the model emitted the flat `$type` shape or the legacy\n * `component` shape, and they never see the reserved `$`-prefixed keys leak\n * into component props.\n */\nexport interface NormalizedUIElement {\n readonly type: string;\n readonly props: Readonly<Record<string, unknown>>;\n readonly children?: NormalizedUINode | undefined;\n readonly key?: string | number | undefined;\n readonly action?: Action | undefined;\n}\n\nexport type NormalizedUINode =\n | string\n | number\n | readonly NormalizedUINode[]\n | NormalizedUIElement\n | null;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\ntype LegacyNodeRecord = { component: string } & Record<string, unknown>;\n\nconst isLegacyNode = (\n node: Record<string, unknown>,\n): node is LegacyNodeRecord => typeof node[\"component\"] === \"string\";\n\nconst isTypeNode = (node: Record<string, unknown>): node is UIElement =>\n typeof node[TYPE_KEY] === \"string\";\n\n/** Bounds recursion so a runaway or adversarial model response cannot overflow\n * the stack; past this depth (far beyond any real UI) we stop. */\nconst MAX_DEPTH = 64;\n\n/**\n * Steps a streaming partial path down into `key`. `partialPath` is the remaining\n * segment of the parse meta's partial path relative to `node` (`undefined` once\n * the walk leaves the partial frontier, i.e. everything below is complete).\n */\nfunction descend(\n partialPath: readonly string[] | undefined,\n key: string,\n): readonly string[] | undefined {\n return partialPath?.[0] === key ? partialPath.slice(1) : undefined;\n}\n\n/**\n * Normalizes a generative-ui input to {@link NormalizedUINode}. The flat\n * `$type` shape and the legacy `component` shape both map to the same canonical\n * element, with reserved keys (`$type`, `$key`, `$action`, `children`) stripped\n * from the prop bag. A node that carries neither a `$type` nor a `component`\n * string is not renderable and resolves to `null` rather than throwing, so a\n * partially-streamed or malformed node degrades to \"render nothing\".\n *\n * `partialPath` carries streaming state from the tool-args parse meta: a node\n * whose `$type` is still mid-arrival is held back (resolves to `null`) until it\n * completes, and the path is threaded into `children` so a nested streaming\n * node is held back while completed siblings render. Omit it for a\n * non-streaming (converter) normalize.\n */\nexport function normalizeUINode(\n node: unknown,\n partialPath?: readonly string[] | undefined,\n depth = 0,\n): NormalizedUINode {\n if (depth > MAX_DEPTH) return null;\n if (node == null || typeof node === \"boolean\") return null;\n if (typeof node === \"string\" || typeof node === \"number\") return node;\n if (Array.isArray(node))\n return node.map((child, index) =>\n normalizeUINode(child, descend(partialPath, String(index)), depth + 1),\n );\n if (!isRecord(node)) return null;\n\n // The flat `$type` shape is the canonical form; detect it first so a flat\n // node that happens to use `component` as an ordinary prop is not swallowed\n // by the legacy `component`-shape branch.\n if (isTypeNode(node)) {\n if (partialPath?.length === 1 && partialPath[0] === TYPE_KEY) return null;\n const { [TYPE_KEY]: type, $key, $action, children, ...rest } = node;\n // The `$`-prefixed namespace is framework-reserved (see the module header),\n // so any model-supplied `$`-prefixed key is stripped from the prop bag the\n // component sees. `$type`/`$key`/`$action` are pulled above; sweep the rest\n // (e.g. a stray `$status`) so it never leaks to converters or components.\n const props = stripReservedProps(rest);\n return {\n type,\n props,\n children: normalizeChildren(children, partialPath, depth),\n key: $key,\n action: $action,\n };\n }\n\n if (isLegacyNode(node)) {\n const props = stripReservedProps(\n (node.props ?? {}) as Record<string, unknown>,\n );\n return {\n type: node.component,\n props,\n children: normalizeChildren(node.children, partialPath, depth),\n key: node.key as string | undefined,\n };\n }\n\n return null;\n}\n\nfunction stripReservedProps(\n props: Record<string, unknown>,\n): Record<string, unknown> {\n let out: Record<string, unknown> | undefined;\n for (const key of Object.keys(props)) {\n if (key.startsWith(\"$\")) {\n out ??= { ...props };\n delete out[key];\n }\n }\n return out ?? props;\n}\n\nfunction normalizeChildren(\n children: unknown,\n partialPath: readonly string[] | undefined,\n depth: number,\n): NormalizedUINode | undefined {\n if (children === undefined) return undefined;\n return normalizeUINode(children, descend(partialPath, \"children\"), depth + 1);\n}\n\n/**\n * Normalizes the root of a {@link UISpec}, preserving whether the root was a\n * single node or a list.\n */\nexport function normalizeSpec(spec: UISpec): {\n readonly root: NormalizedUINode | readonly NormalizedUINode[];\n} {\n if (Array.isArray(spec)) {\n return {\n root: (spec as readonly UINode[]).map((node) => normalizeUINode(node)),\n };\n }\n return { root: normalizeUINode(spec) };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,MAAa,aAAa;CAAC;CAAM;CAAM;CAAM;CAAM;CAAO;AAAK;AAG/D,MAAa,oBAAoB;CAAC;CAAM;CAAM;AAAI;AAGlD,MAAa,UAAU;CAAC;CAAU;CAAU;CAAY;AAAM;AAG9D,MAAa,SAAS;CACpB;CACA;CACA;CACA;CACA;CACA;AACF;AAGA,MAAa,SAAS;CAAC;CAAS;CAAU;AAAK;AAG/C,MAAa,YAAY;CAAC;CAAS;CAAU;CAAO;AAAS;AAG7D,MAAa,gBAAgB;CAC3B;CACA;CACA;CACA;CACA;AACF;AAGA,MAAa,cAAc;CAAC;CAAQ;CAAW;CAAW;AAAQ;AAIlE,MAAa,aAAa;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAsEA,MAAM,YAAY,UAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAIrE,MAAM,gBACJ,SAC6B,OAAO,KAAK,iBAAiB;AAE5D,MAAM,cAAc,SAClB,OAAO,KAAK,cAAc;;;AAI5B,MAAM,YAAY;;;;;;AAOlB,SAAS,QACP,aACA,KAC+B;CAC/B,OAAO,cAAc,OAAO,MAAM,YAAY,MAAM,CAAC,IAAI,KAAA;AAC3D;;;;;;;;;;;;;;;AAgBA,SAAgB,gBACd,MACA,aACA,QAAQ,GACU;CAClB,IAAI,QAAQ,WAAW,OAAO;CAC9B,IAAI,QAAQ,QAAQ,OAAO,SAAS,WAAW,OAAO;CACtD,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU,OAAO;CACjE,IAAI,MAAM,QAAQ,IAAI,GACpB,OAAO,KAAK,KAAK,OAAO,UACtB,gBAAgB,OAAO,QAAQ,aAAa,OAAO,KAAK,CAAC,GAAG,QAAQ,CAAC,CACvE;CACF,IAAI,CAAC,SAAS,IAAI,GAAG,OAAO;CAK5B,IAAI,WAAW,IAAI,GAAG;EACpB,IAAI,aAAa,WAAW,KAAK,YAAY,OAAA,SAAiB,OAAO;EACrE,MAAM,GAAG,WAAW,MAAM,MAAM,SAAS,UAAU,GAAG,SAAS;EAM/D,OAAO;GACL;GACA,OAHY,mBAAmB,IAG3B;GACJ,UAAU,kBAAkB,UAAU,aAAa,KAAK;GACxD,KAAK;GACL,QAAQ;EACV;CACF;CAEA,IAAI,aAAa,IAAI,GAAG;EACtB,MAAM,QAAQ,mBACX,KAAK,SAAS,CAAC,CAClB;EACA,OAAO;GACL,MAAM,KAAK;GACX;GACA,UAAU,kBAAkB,KAAK,UAAU,aAAa,KAAK;GAC7D,KAAK,KAAK;EACZ;CACF;CAEA,OAAO;AACT;AAEA,SAAS,mBACP,OACyB;CACzB,IAAI;CACJ,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GACjC,IAAI,IAAI,WAAW,GAAG,GAAG;EACvB,QAAQ,EAAE,GAAG,MAAM;EACnB,OAAO,IAAI;CACb;CAEF,OAAO,OAAO;AAChB;AAEA,SAAS,kBACP,UACA,aACA,OAC8B;CAC9B,IAAI,aAAa,KAAA,GAAW,OAAO,KAAA;CACnC,OAAO,gBAAgB,UAAU,QAAQ,aAAa,UAAU,GAAG,QAAQ,CAAC;AAC9E;;;;;AAMA,SAAgB,cAAc,MAE5B;CACA,IAAI,MAAM,QAAQ,IAAI,GACpB,OAAO,EACL,MAAO,KAA2B,KAAK,SAAS,gBAAgB,IAAI,CAAC,EACvE;CAEF,OAAO,EAAE,MAAM,gBAAgB,IAAI,EAAE;AACvC"}
1
+ {"version":3,"file":"ir.js","names":[],"sources":["../src/ir.ts"],"sourcesContent":["/**\n * The generative-ui intermediate representation: a model-emitted, cross-platform\n * UI tree. React-free, so converters and non-web runtimes consume it without\n * pulling React. The flat `$type` shape is the canonical form; the legacy\n * `component` shape is accepted as a backward-compatible alias.\n *\n * Reserved keys are partitioned off from component props so the component prop\n * namespace stays fully free:\n *\n * - `$`-prefixed keys are framework-reserved (`$type`, `$key`, `$action`,\n * `$status`). Components never declare `$`-prefixed props, so a component\n * can use `type`, `status`, `variant`, etc. as ordinary props without\n * colliding with the framework.\n * - `children` is additionally reserved (the JSX convention).\n * - every other key is an inline prop passed straight to the component.\n */\n\nimport { TYPE_KEY } from \"./constants\";\n\nexport const TEXT_SIZES = [\"sm\", \"md\", \"lg\", \"xl\", \"2xl\", \"3xl\"] as const;\nexport type TextSize = (typeof TEXT_SIZES)[number];\n\nexport const IMAGE_SIZE_TOKENS = [\"sm\", \"md\", \"lg\"] as const;\nexport type ImageSize = (typeof IMAGE_SIZE_TOKENS)[number] | number;\n\nexport const WEIGHTS = [\"normal\", \"medium\", \"semibold\", \"bold\"] as const;\nexport type Weight = (typeof WEIGHTS)[number];\n\nexport const COLORS = [\n \"emphasis\",\n \"secondary\",\n \"alpha-70\",\n \"white\",\n \"white-70\",\n \"white-50\",\n] as const;\nexport type Color = (typeof COLORS)[number];\n\nexport const ALIGNS = [\"start\", \"center\", \"end\"] as const;\nexport type Align = (typeof ALIGNS)[number];\n\nexport const JUSTIFIES = [\"start\", \"center\", \"end\", \"between\"] as const;\nexport type Justify = (typeof JUSTIFIES)[number];\n\nexport const BUTTON_STYLES = [\n \"primary\",\n \"secondary\",\n \"outline\",\n \"ghost\",\n \"danger\",\n] as const;\nexport type ButtonStyle = (typeof BUTTON_STYLES)[number];\n\nexport const ALERT_TONES = [\"info\", \"success\", \"warning\", \"danger\"] as const;\n/** A severity level, from informational to destructive. */\nexport type AlertTone = (typeof ALERT_TONES)[number];\n\nexport const ICON_NAMES = [\n \"sun\",\n \"moon\",\n \"cloud\",\n \"rain\",\n \"snow\",\n \"wind\",\n \"play\",\n \"pause\",\n \"check\",\n \"x\",\n \"star\",\n \"heart\",\n \"arrow-right\",\n \"arrow-up-right\",\n \"chevron-right\",\n \"calendar\",\n \"clock\",\n \"map-pin\",\n \"plane\",\n \"truck\",\n \"credit-card\",\n \"user\",\n \"search\",\n \"bell\",\n] as const;\n/** A name from the built-in icon set; the closed enum lets the model see exactly which icons exist. */\nexport type IconName = (typeof ICON_NAMES)[number];\n\n/**\n * Behavior payload carried by an interactive node. `type` is resolved by the host's action registry, not the renderer; keeping behavior as data keeps the tree serializable, so the same node renders on web while converters may bind the type to a native action id on other platforms. A `{ \"$field\": name }` value anywhere in the payload refers to the current value of the control with that `name`, resolved when the action fires, and `{ \"$field\": name, \"fallback\": value }` resolves to `fallback` when no control holds a value.\n */\nexport interface Action {\n readonly type: string;\n readonly [payload: string]: unknown;\n}\n\n/**\n * Anything renderable as generative UI, as the model emits it. The renderer\n * also accepts `number`, `boolean`, `null`, `undefined`, and arrays at the\n * input boundary (numbers render as text, falsy/boolean as nothing, arrays as\n * lists); {@link normalizeUINode} accepts that full range.\n */\nexport type UINode = string | number | UIElement | LegacyComponentNode;\n\nexport type UIChildren = UINode | readonly UINode[];\n\n/** The flat node shape: inline props keep the tree compact and natural for a\n * model to emit, instead of a nested `{ type, props }` bag. Reserved keys are\n * stripped before props reach the component (see the module header). */\nexport interface UIElement {\n readonly $type: string;\n readonly $key?: string | number;\n readonly children?: UIChildren;\n readonly $action?: Action;\n readonly [prop: string]: unknown;\n}\n\n/**\n * The legacy node shape: a `component` name plus a nested `props` bag. Kept\n * for backward compatibility. New code authors the flat {@link UIElement}\n * shape instead.\n */\nexport interface LegacyComponentNode {\n readonly component: string;\n readonly props?: Record<string, unknown>;\n readonly children?: UIChildren;\n readonly key?: string;\n}\n\nexport type UISpec = UINode | readonly UINode[];\n\n/**\n * A node normalized to a single canonical shape: a `type` string, an inline\n * `props` bag, recursive `children`, an optional `key`, and an optional\n * `action`. Renderers and platform converters consume this form, so they never\n * branch on whether the model emitted the flat `$type` shape or the legacy\n * `component` shape, and they never see the reserved `$`-prefixed keys leak\n * into component props.\n */\nexport interface NormalizedUIElement {\n readonly type: string;\n readonly props: Readonly<Record<string, unknown>>;\n readonly children?: NormalizedUINode | undefined;\n readonly key?: string | number | undefined;\n readonly action?: Action | undefined;\n}\n\nexport type NormalizedUINode =\n | string\n | number\n | readonly NormalizedUINode[]\n | NormalizedUIElement\n | null;\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\ntype LegacyNodeRecord = { component: string } & Record<string, unknown>;\n\nconst isLegacyNode = (\n node: Record<string, unknown>,\n): node is LegacyNodeRecord => typeof node[\"component\"] === \"string\";\n\nconst isTypeNode = (node: Record<string, unknown>): node is UIElement =>\n typeof node[TYPE_KEY] === \"string\";\n\n/** Bounds recursion so a runaway or adversarial model response cannot overflow\n * the stack; past this depth (far beyond any real UI) we stop. */\nconst MAX_DEPTH = 64;\n\n/**\n * Steps a streaming partial path down into `key`. `partialPath` is the remaining\n * segment of the parse meta's partial path relative to `node` (`undefined` once\n * the walk leaves the partial frontier, i.e. everything below is complete).\n */\nfunction descend(\n partialPath: readonly string[] | undefined,\n key: string,\n): readonly string[] | undefined {\n return partialPath?.[0] === key ? partialPath.slice(1) : undefined;\n}\n\n/**\n * Normalizes a generative-ui input to {@link NormalizedUINode}. The flat\n * `$type` shape and the legacy `component` shape both map to the same canonical\n * element, with reserved keys (`$type`, `$key`, `$action`, `children`) stripped\n * from the prop bag. A node that carries neither a `$type` nor a `component`\n * string is not renderable and resolves to `null` rather than throwing, so a\n * partially-streamed or malformed node degrades to \"render nothing\".\n *\n * `partialPath` carries streaming state from the tool-args parse meta: a node\n * whose `$type` is still mid-arrival is held back (resolves to `null`) until it\n * completes, and the path is threaded into `children` so a nested streaming\n * node is held back while completed siblings render. Omit it for a\n * non-streaming (converter) normalize.\n */\nexport function normalizeUINode(\n node: unknown,\n partialPath?: readonly string[] | undefined,\n depth = 0,\n): NormalizedUINode {\n if (depth > MAX_DEPTH) return null;\n if (node == null || typeof node === \"boolean\") return null;\n if (typeof node === \"string\" || typeof node === \"number\") return node;\n if (Array.isArray(node))\n return node.map((child, index) =>\n normalizeUINode(child, descend(partialPath, String(index)), depth + 1),\n );\n if (!isRecord(node)) return null;\n\n // The flat `$type` shape is the canonical form; detect it first so a flat\n // node that happens to use `component` as an ordinary prop is not swallowed\n // by the legacy `component`-shape branch.\n if (isTypeNode(node)) {\n if (partialPath?.length === 1 && partialPath[0] === TYPE_KEY) return null;\n const { [TYPE_KEY]: type, $key, $action, children, ...rest } = node;\n // The `$`-prefixed namespace is framework-reserved (see the module header),\n // so any model-supplied `$`-prefixed key is stripped from the prop bag the\n // component sees. `$type`/`$key`/`$action` are pulled above; sweep the rest\n // (e.g. a stray `$status`) so it never leaks to converters or components.\n const props = stripReservedProps(rest);\n return {\n type,\n props,\n children: normalizeChildren(children, partialPath, depth),\n key: $key,\n action: $action,\n };\n }\n\n if (isLegacyNode(node)) {\n const props = stripReservedProps(\n (node.props ?? {}) as Record<string, unknown>,\n );\n return {\n type: node.component,\n props,\n children: normalizeChildren(node.children, partialPath, depth),\n key: node.key as string | undefined,\n };\n }\n\n return null;\n}\n\nfunction stripReservedProps(\n props: Record<string, unknown>,\n): Record<string, unknown> {\n let out: Record<string, unknown> | undefined;\n for (const key of Object.keys(props)) {\n if (key.startsWith(\"$\")) {\n out ??= { ...props };\n delete out[key];\n }\n }\n return out ?? props;\n}\n\nfunction normalizeChildren(\n children: unknown,\n partialPath: readonly string[] | undefined,\n depth: number,\n): NormalizedUINode | undefined {\n if (children === undefined) return undefined;\n return normalizeUINode(children, descend(partialPath, \"children\"), depth + 1);\n}\n\n/**\n * Normalizes the root of a {@link UISpec}, preserving whether the root was a\n * single node or a list.\n */\nexport function normalizeSpec(spec: UISpec): {\n readonly root: NormalizedUINode | readonly NormalizedUINode[];\n} {\n if (Array.isArray(spec)) {\n return {\n root: (spec as readonly UINode[]).map((node) => normalizeUINode(node)),\n };\n }\n return { root: normalizeUINode(spec) };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,MAAa,aAAa;CAAC;CAAM;CAAM;CAAM;CAAM;CAAO;AAAK;AAG/D,MAAa,oBAAoB;CAAC;CAAM;CAAM;AAAI;AAGlD,MAAa,UAAU;CAAC;CAAU;CAAU;CAAY;AAAM;AAG9D,MAAa,SAAS;CACpB;CACA;CACA;CACA;CACA;CACA;AACF;AAGA,MAAa,SAAS;CAAC;CAAS;CAAU;AAAK;AAG/C,MAAa,YAAY;CAAC;CAAS;CAAU;CAAO;AAAS;AAG7D,MAAa,gBAAgB;CAC3B;CACA;CACA;CACA;CACA;AACF;AAGA,MAAa,cAAc;CAAC;CAAQ;CAAW;CAAW;AAAQ;AAIlE,MAAa,aAAa;CACxB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAsEA,MAAM,YAAY,UAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAIrE,MAAM,gBACJ,SAC6B,OAAO,KAAK,iBAAiB;AAE5D,MAAM,cAAc,SAClB,OAAO,KAAK,cAAc;;;AAI5B,MAAM,YAAY;;;;;;AAOlB,SAAS,QACP,aACA,KAC+B;CAC/B,OAAO,cAAc,OAAO,MAAM,YAAY,MAAM,CAAC,IAAI,KAAA;AAC3D;;;;;;;;;;;;;;;AAgBA,SAAgB,gBACd,MACA,aACA,QAAQ,GACU;CAClB,IAAI,QAAQ,WAAW,OAAO;CAC9B,IAAI,QAAQ,QAAQ,OAAO,SAAS,WAAW,OAAO;CACtD,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU,OAAO;CACjE,IAAI,MAAM,QAAQ,IAAI,GACpB,OAAO,KAAK,KAAK,OAAO,UACtB,gBAAgB,OAAO,QAAQ,aAAa,OAAO,KAAK,CAAC,GAAG,QAAQ,CAAC,CACvE;CACF,IAAI,CAAC,SAAS,IAAI,GAAG,OAAO;CAK5B,IAAI,WAAW,IAAI,GAAG;EACpB,IAAI,aAAa,WAAW,KAAK,YAAY,OAAA,SAAiB,OAAO;EACrE,MAAM,GAAG,WAAW,MAAM,MAAM,SAAS,UAAU,GAAG,SAAS;EAM/D,OAAO;GACL;GACA,OAHY,mBAAmB,IAG3B;GACJ,UAAU,kBAAkB,UAAU,aAAa,KAAK;GACxD,KAAK;GACL,QAAQ;EACV;CACF;CAEA,IAAI,aAAa,IAAI,GAAG;EACtB,MAAM,QAAQ,mBACX,KAAK,SAAS,CAAC,CAClB;EACA,OAAO;GACL,MAAM,KAAK;GACX;GACA,UAAU,kBAAkB,KAAK,UAAU,aAAa,KAAK;GAC7D,KAAK,KAAK;EACZ;CACF;CAEA,OAAO;AACT;AAEA,SAAS,mBACP,OACyB;CACzB,IAAI;CACJ,KAAK,MAAM,OAAO,OAAO,KAAK,KAAK,GACjC,IAAI,IAAI,WAAW,GAAG,GAAG;EACvB,QAAQ,EAAE,GAAG,MAAM;EACnB,OAAO,IAAI;CACb;CAEF,OAAO,OAAO;AAChB;AAEA,SAAS,kBACP,UACA,aACA,OAC8B;CAC9B,IAAI,aAAa,KAAA,GAAW,OAAO,KAAA;CACnC,OAAO,gBAAgB,UAAU,QAAQ,aAAa,UAAU,GAAG,QAAQ,CAAC;AAC9E;;;;;AAMA,SAAgB,cAAc,MAE5B;CACA,IAAI,MAAM,QAAQ,IAAI,GACpB,OAAO,EACL,MAAO,KAA2B,KAAK,SAAS,gBAAgB,IAAI,CAAC,EACvE;CAEF,OAAO,EAAE,MAAM,gBAAgB,IAAI,EAAE;AACvC"}
@@ -1 +1 @@
1
- {"version":3,"file":"renderGenerativeUI.d.ts","sourceRoot":"","sources":["../src/renderGenerativeUI.tsx"],"names":[],"mappings":"AACA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAMjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,mBAAgB;AAO9E;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,mBAAmB,EAC5B,OAAO,GAAE,yBAA2C,GACnD,SAAS,CAOX"}
1
+ {"version":3,"file":"renderGenerativeUI.d.ts","sourceRoot":"","sources":["../src/renderGenerativeUI.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAY,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAQjD,OAAO,KAAK,EAAE,mBAAmB,EAAE,yBAAyB,EAAE,mBAAgB;AAO9E;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,mBAAmB,EAC5B,OAAO,GAAE,yBAA2C,GACnD,SAAS,CAgBX"}
@@ -1,3 +1,5 @@
1
+ import { RadioGroupScope } from "./RadioGroupScope.js";
2
+ import { hasFieldReference, resolveFieldReferences } from "./fieldReferences.js";
1
3
  import { normalizeUINode } from "./ir.js";
2
4
  import { getPartialJsonObjectMeta } from "assistant-stream/utils";
3
5
  import { Fragment } from "react";
@@ -18,8 +20,17 @@ const isElement = (node) => typeof node === "object" && node !== null && !Array.
18
20
  function renderGenerativeUI(node, library, context = DEFAULT_CONTEXT) {
19
21
  const meta = getPartialJsonObjectMeta(node);
20
22
  const partialPath = meta?.state === "partial" ? meta.partialPath : void 0;
21
- return renderNode(normalizeUINode(node, partialPath), library, context);
23
+ const rendered = renderNode(normalizeUINode(node, partialPath), library, withFieldFallbacks(context));
24
+ return rendered !== null && typeof rendered === "object" ? /* @__PURE__ */ jsx(RadioGroupScope, { children: rendered }) : rendered;
22
25
  }
26
+ const withFieldFallbacks = (context) => {
27
+ const { dispatch } = context;
28
+ if (dispatch === void 0) return context;
29
+ return {
30
+ ...context,
31
+ dispatch: (action) => dispatch(hasFieldReference(action) ? resolveFieldReferences(action, {}) : action)
32
+ };
33
+ };
23
34
  function renderNode(node, library, context) {
24
35
  if (node == null || typeof node === "boolean") return null;
25
36
  if (typeof node === "string" || typeof node === "number") return node;
@@ -1 +1 @@
1
- {"version":3,"file":"renderGenerativeUI.js","names":[],"sources":["../src/renderGenerativeUI.tsx"],"sourcesContent":["import { getPartialJsonObjectMeta } from \"assistant-stream/utils\";\nimport { Fragment, type ReactNode } from \"react\";\nimport {\n normalizeUINode,\n type NormalizedUIElement,\n type NormalizedUINode,\n} from \"./ir\";\nimport type { GenerativeUILibrary, GenerativeUIRenderContext } from \"./types\";\n\nconst DEFAULT_CONTEXT: GenerativeUIRenderContext = { status: \"done\" };\n\nconst isElement = (node: NormalizedUINode): node is NormalizedUIElement =>\n typeof node === \"object\" && node !== null && !Array.isArray(node);\n\n/**\n * Renders a generative-ui tree against a {@link GenerativeUILibrary}.\n *\n * The model emits each node as a flat object `{ $type, ...props }`. We first\n * normalize that wire form into the canonical {@link NormalizedUINode} (with\n * `children` lifted to a reserved top-level key), then render: each `type` is\n * looked up in the library and its `props` are passed to the component's\n * `render(props, context)`, with `children` rendered recursively so components\n * can nest.\n */\nexport function renderGenerativeUI(\n node: unknown,\n library: GenerativeUILibrary,\n context: GenerativeUIRenderContext = DEFAULT_CONTEXT,\n): ReactNode {\n // Tool args are parsed incrementally, and the parse meta records which path\n // is still mid-arrival, so normalization can hold back a node whose `$type`\n // string has not finished streaming.\n const meta = getPartialJsonObjectMeta(node as Record<symbol, unknown>);\n const partialPath = meta?.state === \"partial\" ? meta.partialPath : undefined;\n return renderNode(normalizeUINode(node, partialPath), library, context);\n}\n\nfunction renderNode(\n node: NormalizedUINode,\n library: GenerativeUILibrary,\n context: GenerativeUIRenderContext,\n): ReactNode {\n if (node == null || typeof node === \"boolean\") return null;\n if (typeof node === \"string\" || typeof node === \"number\") return node;\n if (Array.isArray(node)) {\n // Use a model-provided stable key when present. Otherwise, keep the\n // positional fallback: pairing the index with the node's kind means that\n // when the kind at an index changes, React remounts instead of handing a\n // streaming node's hook state to a different component.\n return node.map((child, index) => (\n <Fragment key={nodeKey(child, index)}>\n {renderNode(child, library, context)}\n </Fragment>\n ));\n }\n return isElement(node) ? renderElement(node, library, context) : null;\n}\n\nfunction renderElement(\n element: NormalizedUIElement,\n library: GenerativeUILibrary,\n context: GenerativeUIRenderContext,\n): ReactNode {\n const entry = Object.hasOwn(library, element.type)\n ? library[element.type]\n : undefined;\n if (!entry) {\n reportUnknownComponent(element.type, Object.keys(library));\n return null;\n }\n\n // Components that opt out of prop streaming wait until their props are\n // complete rather than rendering from a partial parse.\n if (!entry.streamProperties && context.status === \"streaming\") return null;\n\n // `children` is a reserved top-level key on the normalized element, not a\n // prop, so it is not in `props`. Inject the framework props last so the model\n // can never override them. `$action` is likewise reserved and stripped from\n // the prop bag during normalization, so it is re-injected here for components\n // that carry behavior (e.g. `Button`).\n const props: Record<string, unknown> = {\n ...element.props,\n $status: context.status,\n };\n if (context.dispatch !== undefined) {\n props[\"$dispatch\"] = context.dispatch;\n }\n if (element.action !== undefined) {\n props[\"$action\"] = element.action;\n }\n if (element.children !== undefined) {\n props[\"children\"] = renderNode(element.children, library, context);\n }\n\n return <GenerativeUIComponentRenderer render={entry.render} props={props} />;\n}\n\n/**\n * Mounts a single node's `render` on its own fiber so the function may use\n * hooks and hold state independently of its siblings and parent.\n */\nfunction GenerativeUIComponentRenderer({\n render,\n props,\n}: {\n render: (props: any) => ReactNode;\n props: Record<string, unknown>;\n}): ReactNode {\n return render(props);\n}\n\n/** A coarse kind tag for a child, used in its list key so a node changing kind\n * at a given index forces a remount rather than a wrong-fiber reuse. */\nfunction nodeKind(node: NormalizedUINode): string {\n if (node == null || typeof node === \"boolean\") return \"\";\n if (typeof node === \"string\" || typeof node === \"number\") return \"#text\";\n if (Array.isArray(node)) return \"#array\";\n return isElement(node) ? node.type : \"\";\n}\n\nfunction nodeKey(node: NormalizedUINode, index: number): string {\n return isElement(node) &&\n (typeof node.key === \"string\" || typeof node.key === \"number\")\n ? `model:${node.key}`\n : `${index}:${nodeKind(node)}`;\n}\n\nfunction reportUnknownComponent(type: string, available: string[]): void {\n if (process.env[\"NODE_ENV\"] !== \"production\") {\n // eslint-disable-next-line no-console\n console.error(\n `[@assistant-ui/react-generative-ui] Unknown component \"${type}\". ` +\n `Available components: ${available.join(\", \") || \"(none)\"}.`,\n );\n }\n}\n"],"mappings":";;;;;AASA,MAAM,kBAA6C,EAAE,QAAQ,OAAO;AAEpE,MAAM,aAAa,SACjB,OAAO,SAAS,YAAY,SAAS,QAAQ,CAAC,MAAM,QAAQ,IAAI;;;;;;;;;;;AAYlE,SAAgB,mBACd,MACA,SACA,UAAqC,iBAC1B;CAIX,MAAM,OAAO,yBAAyB,IAA+B;CACrE,MAAM,cAAc,MAAM,UAAU,YAAY,KAAK,cAAc,KAAA;CACnE,OAAO,WAAW,gBAAgB,MAAM,WAAW,GAAG,SAAS,OAAO;AACxE;AAEA,SAAS,WACP,MACA,SACA,SACW;CACX,IAAI,QAAQ,QAAQ,OAAO,SAAS,WAAW,OAAO;CACtD,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU,OAAO;CACjE,IAAI,MAAM,QAAQ,IAAI,GAKpB,OAAO,KAAK,KAAK,OAAO,UACtB,oBAAC,UAAD,EAAA,UACG,WAAW,OAAO,SAAS,OAAO,EAC3B,GAFK,QAAQ,OAAO,KAAK,CAEzB,CACX;CAEH,OAAO,UAAU,IAAI,IAAI,cAAc,MAAM,SAAS,OAAO,IAAI;AACnE;AAEA,SAAS,cACP,SACA,SACA,SACW;CACX,MAAM,QAAQ,OAAO,OAAO,SAAS,QAAQ,IAAI,IAC7C,QAAQ,QAAQ,QAChB,KAAA;CACJ,IAAI,CAAC,OAAO;EACV,uBAAuB,QAAQ,MAAM,OAAO,KAAK,OAAO,CAAC;EACzD,OAAO;CACT;CAIA,IAAI,CAAC,MAAM,oBAAoB,QAAQ,WAAW,aAAa,OAAO;CAOtE,MAAM,QAAiC;EACrC,GAAG,QAAQ;EACX,SAAS,QAAQ;CACnB;CACA,IAAI,QAAQ,aAAa,KAAA,GACvB,MAAM,eAAe,QAAQ;CAE/B,IAAI,QAAQ,WAAW,KAAA,GACrB,MAAM,aAAa,QAAQ;CAE7B,IAAI,QAAQ,aAAa,KAAA,GACvB,MAAM,cAAc,WAAW,QAAQ,UAAU,SAAS,OAAO;CAGnE,OAAO,oBAAC,+BAAD;EAA+B,QAAQ,MAAM;EAAe;CAAQ,CAAA;AAC7E;;;;;AAMA,SAAS,8BAA8B,EACrC,QACA,SAIY;CACZ,OAAO,OAAO,KAAK;AACrB;;;AAIA,SAAS,SAAS,MAAgC;CAChD,IAAI,QAAQ,QAAQ,OAAO,SAAS,WAAW,OAAO;CACtD,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU,OAAO;CACjE,IAAI,MAAM,QAAQ,IAAI,GAAG,OAAO;CAChC,OAAO,UAAU,IAAI,IAAI,KAAK,OAAO;AACvC;AAEA,SAAS,QAAQ,MAAwB,OAAuB;CAC9D,OAAO,UAAU,IAAI,MAClB,OAAO,KAAK,QAAQ,YAAY,OAAO,KAAK,QAAQ,YACnD,SAAS,KAAK,QACd,GAAG,MAAM,GAAG,SAAS,IAAI;AAC/B;AAEA,SAAS,uBAAuB,MAAc,WAA2B;CACvE,IAAI,QAAQ,IAAI,gBAAgB,cAE9B,QAAQ,MACN,0DAA0D,KAAK,2BACpC,UAAU,KAAK,IAAI,KAAK,SAAS,EAC9D;AAEJ"}
1
+ {"version":3,"file":"renderGenerativeUI.js","names":[],"sources":["../src/renderGenerativeUI.tsx"],"sourcesContent":["import { RadioGroupScope } from \"./RadioGroupScope\";\nimport { getPartialJsonObjectMeta } from \"assistant-stream/utils\";\nimport { Fragment, type ReactNode } from \"react\";\nimport { hasFieldReference, resolveFieldReferences } from \"./fieldReferences\";\nimport {\n normalizeUINode,\n type Action,\n type NormalizedUIElement,\n type NormalizedUINode,\n} from \"./ir\";\nimport type { GenerativeUILibrary, GenerativeUIRenderContext } from \"./types\";\n\nconst DEFAULT_CONTEXT: GenerativeUIRenderContext = { status: \"done\" };\n\nconst isElement = (node: NormalizedUINode): node is NormalizedUIElement =>\n typeof node === \"object\" && node !== null && !Array.isArray(node);\n\n/**\n * Renders a generative-ui tree against a {@link GenerativeUILibrary}.\n *\n * The model emits each node as a flat object `{ $type, ...props }`. We first\n * normalize that wire form into the canonical {@link NormalizedUINode} (with\n * `children` lifted to a reserved top-level key), then render: each `type` is\n * looked up in the library and its `props` are passed to the component's\n * `render(props, context)`, with `children` rendered recursively so components\n * can nest.\n */\nexport function renderGenerativeUI(\n node: unknown,\n library: GenerativeUILibrary,\n context: GenerativeUIRenderContext = DEFAULT_CONTEXT,\n): ReactNode {\n // Tool args are parsed incrementally, and the parse meta records which path\n // is still mid-arrival, so normalization can hold back a node whose `$type`\n // string has not finished streaming.\n const meta = getPartialJsonObjectMeta(node as Record<symbol, unknown>);\n const partialPath = meta?.state === \"partial\" ? meta.partialPath : undefined;\n const rendered = renderNode(\n normalizeUINode(node, partialPath),\n library,\n withFieldFallbacks(context),\n );\n return rendered !== null && typeof rendered === \"object\" ? (\n <RadioGroupScope>{rendered}</RadioGroupScope>\n ) : (\n rendered\n );\n}\n\n// A component that dispatches `$action` itself reads no control, so each `$field` reference left in it becomes its fallback.\nconst withFieldFallbacks = (\n context: GenerativeUIRenderContext,\n): GenerativeUIRenderContext => {\n const { dispatch } = context;\n if (dispatch === undefined) return context;\n return {\n ...context,\n dispatch: (action) =>\n dispatch(\n hasFieldReference(action)\n ? (resolveFieldReferences(action, {}) as Action)\n : action,\n ),\n };\n};\n\nfunction renderNode(\n node: NormalizedUINode,\n library: GenerativeUILibrary,\n context: GenerativeUIRenderContext,\n): ReactNode {\n if (node == null || typeof node === \"boolean\") return null;\n if (typeof node === \"string\" || typeof node === \"number\") return node;\n if (Array.isArray(node)) {\n // Use a model-provided stable key when present. Otherwise, keep the\n // positional fallback: pairing the index with the node's kind means that\n // when the kind at an index changes, React remounts instead of handing a\n // streaming node's hook state to a different component.\n return node.map((child, index) => (\n <Fragment key={nodeKey(child, index)}>\n {renderNode(child, library, context)}\n </Fragment>\n ));\n }\n return isElement(node) ? renderElement(node, library, context) : null;\n}\n\nfunction renderElement(\n element: NormalizedUIElement,\n library: GenerativeUILibrary,\n context: GenerativeUIRenderContext,\n): ReactNode {\n const entry = Object.hasOwn(library, element.type)\n ? library[element.type]\n : undefined;\n if (!entry) {\n reportUnknownComponent(element.type, Object.keys(library));\n return null;\n }\n\n // Components that opt out of prop streaming wait until their props are\n // complete rather than rendering from a partial parse.\n if (!entry.streamProperties && context.status === \"streaming\") return null;\n\n // `children` is a reserved top-level key on the normalized element, not a\n // prop, so it is not in `props`. Inject the framework props last so the model\n // can never override them. `$action` is likewise reserved and stripped from\n // the prop bag during normalization, so it is re-injected here for components\n // that carry behavior (e.g. `Button`).\n const props: Record<string, unknown> = {\n ...element.props,\n $status: context.status,\n };\n if (context.dispatch !== undefined) {\n props[\"$dispatch\"] = context.dispatch;\n }\n if (element.action !== undefined) {\n props[\"$action\"] = element.action;\n }\n if (element.children !== undefined) {\n props[\"children\"] = renderNode(element.children, library, context);\n }\n\n return <GenerativeUIComponentRenderer render={entry.render} props={props} />;\n}\n\n/**\n * Mounts a single node's `render` on its own fiber so the function may use\n * hooks and hold state independently of its siblings and parent.\n */\nfunction GenerativeUIComponentRenderer({\n render,\n props,\n}: {\n render: (props: any) => ReactNode;\n props: Record<string, unknown>;\n}): ReactNode {\n return render(props);\n}\n\n/** A coarse kind tag for a child, used in its list key so a node changing kind\n * at a given index forces a remount rather than a wrong-fiber reuse. */\nfunction nodeKind(node: NormalizedUINode): string {\n if (node == null || typeof node === \"boolean\") return \"\";\n if (typeof node === \"string\" || typeof node === \"number\") return \"#text\";\n if (Array.isArray(node)) return \"#array\";\n return isElement(node) ? node.type : \"\";\n}\n\nfunction nodeKey(node: NormalizedUINode, index: number): string {\n return isElement(node) &&\n (typeof node.key === \"string\" || typeof node.key === \"number\")\n ? `model:${node.key}`\n : `${index}:${nodeKind(node)}`;\n}\n\nfunction reportUnknownComponent(type: string, available: string[]): void {\n if (process.env[\"NODE_ENV\"] !== \"production\") {\n // eslint-disable-next-line no-console\n console.error(\n `[@assistant-ui/react-generative-ui] Unknown component \"${type}\". ` +\n `Available components: ${available.join(\", \") || \"(none)\"}.`,\n );\n }\n}\n"],"mappings":";;;;;;;AAYA,MAAM,kBAA6C,EAAE,QAAQ,OAAO;AAEpE,MAAM,aAAa,SACjB,OAAO,SAAS,YAAY,SAAS,QAAQ,CAAC,MAAM,QAAQ,IAAI;;;;;;;;;;;AAYlE,SAAgB,mBACd,MACA,SACA,UAAqC,iBAC1B;CAIX,MAAM,OAAO,yBAAyB,IAA+B;CACrE,MAAM,cAAc,MAAM,UAAU,YAAY,KAAK,cAAc,KAAA;CACnE,MAAM,WAAW,WACf,gBAAgB,MAAM,WAAW,GACjC,SACA,mBAAmB,OAAO,CAC5B;CACA,OAAO,aAAa,QAAQ,OAAO,aAAa,WAC9C,oBAAC,iBAAD,EAAA,UAAkB,SAA0B,CAAA,IAE5C;AAEJ;AAGA,MAAM,sBACJ,YAC8B;CAC9B,MAAM,EAAE,aAAa;CACrB,IAAI,aAAa,KAAA,GAAW,OAAO;CACnC,OAAO;EACL,GAAG;EACH,WAAW,WACT,SACE,kBAAkB,MAAM,IACnB,uBAAuB,QAAQ,CAAC,CAAC,IAClC,MACN;CACJ;AACF;AAEA,SAAS,WACP,MACA,SACA,SACW;CACX,IAAI,QAAQ,QAAQ,OAAO,SAAS,WAAW,OAAO;CACtD,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU,OAAO;CACjE,IAAI,MAAM,QAAQ,IAAI,GAKpB,OAAO,KAAK,KAAK,OAAO,UACtB,oBAAC,UAAD,EAAA,UACG,WAAW,OAAO,SAAS,OAAO,EAC3B,GAFK,QAAQ,OAAO,KAAK,CAEzB,CACX;CAEH,OAAO,UAAU,IAAI,IAAI,cAAc,MAAM,SAAS,OAAO,IAAI;AACnE;AAEA,SAAS,cACP,SACA,SACA,SACW;CACX,MAAM,QAAQ,OAAO,OAAO,SAAS,QAAQ,IAAI,IAC7C,QAAQ,QAAQ,QAChB,KAAA;CACJ,IAAI,CAAC,OAAO;EACV,uBAAuB,QAAQ,MAAM,OAAO,KAAK,OAAO,CAAC;EACzD,OAAO;CACT;CAIA,IAAI,CAAC,MAAM,oBAAoB,QAAQ,WAAW,aAAa,OAAO;CAOtE,MAAM,QAAiC;EACrC,GAAG,QAAQ;EACX,SAAS,QAAQ;CACnB;CACA,IAAI,QAAQ,aAAa,KAAA,GACvB,MAAM,eAAe,QAAQ;CAE/B,IAAI,QAAQ,WAAW,KAAA,GACrB,MAAM,aAAa,QAAQ;CAE7B,IAAI,QAAQ,aAAa,KAAA,GACvB,MAAM,cAAc,WAAW,QAAQ,UAAU,SAAS,OAAO;CAGnE,OAAO,oBAAC,+BAAD;EAA+B,QAAQ,MAAM;EAAe;CAAQ,CAAA;AAC7E;;;;;AAMA,SAAS,8BAA8B,EACrC,QACA,SAIY;CACZ,OAAO,OAAO,KAAK;AACrB;;;AAIA,SAAS,SAAS,MAAgC;CAChD,IAAI,QAAQ,QAAQ,OAAO,SAAS,WAAW,OAAO;CACtD,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,UAAU,OAAO;CACjE,IAAI,MAAM,QAAQ,IAAI,GAAG,OAAO;CAChC,OAAO,UAAU,IAAI,IAAI,KAAK,OAAO;AACvC;AAEA,SAAS,QAAQ,MAAwB,OAAuB;CAC9D,OAAO,UAAU,IAAI,MAClB,OAAO,KAAK,QAAQ,YAAY,OAAO,KAAK,QAAQ,YACnD,SAAS,KAAK,QACd,GAAG,MAAM,GAAG,SAAS,IAAI;AAC/B;AAEA,SAAS,uBAAuB,MAAc,WAA2B;CACvE,IAAI,QAAQ,IAAI,gBAAgB,cAE9B,QAAQ,MACN,0DAA0D,KAAK,2BACpC,UAAU,KAAK,IAAI,KAAK,SAAS,EAC9D;AAEJ"}
@@ -29,6 +29,8 @@ export declare const ACTIONS_ELEMENT_CAP = 25;
29
29
  export declare const SELECT_OPTION_CAP = 100;
30
30
  /** The maximum number of options in a radio-button group. */
31
31
  export declare const RADIO_OPTION_CAP = 10;
32
+ /** The maximum number of options in a checkbox group. */
33
+ export declare const CHECKBOX_OPTION_CAP = 10;
32
34
  /** The character limit for a select or input placeholder. */
33
35
  export declare const PLACEHOLDER_TEXT_CAP = 150;
34
36
  /** The character limit for an input's label. */
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/slack/constants.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/slack/constants.ts"],"names":[],"mappings":"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"}
@@ -29,6 +29,8 @@ const ACTIONS_ELEMENT_CAP = 25;
29
29
  const SELECT_OPTION_CAP = 100;
30
30
  /** The maximum number of options in a radio-button group. */
31
31
  const RADIO_OPTION_CAP = 10;
32
+ /** The maximum number of options in a checkbox group. */
33
+ const CHECKBOX_OPTION_CAP = 10;
32
34
  /** The character limit for a select or input placeholder. */
33
35
  const PLACEHOLDER_TEXT_CAP = 150;
34
36
  /** The character limit for an input's label. */
@@ -139,6 +141,6 @@ function buildBlockBudgetNote(omitted) {
139
141
  };
140
142
  }
141
143
  //#endregion
142
- export { ACTIONS_ELEMENT_CAP, ACTION_ID_CAP, ALERT_TEXT_CAP, BUTTON_VALUE_CAP, CARD_ACTIONS_CAP, CARD_BODY_CAP, CARD_SUBTEXT_CAP, CARD_TITLE_CAP, CAROUSEL_CARD_CAP, CAROUSEL_CARD_MIN, CONTEXT_ELEMENT_CAP, CONTEXT_TEXT_CAP, DATA_TABLE_CHAR_BUDGET, DATA_TABLE_COLUMN_CAP, DATA_TABLE_ROW_CAP, FACT_FIELD_CAP, FACT_FIELD_TEXT_CAP, HEADER_TEXT_CAP, INBOUND_BLOCK_CAP, INPUT_LABEL_CAP, INTERACTIVE_TEXT_CAP, MARKDOWN_TEXT_BUDGET, MESSAGE_BLOCK_CAP, MODAL_BLOCK_CAP, PLACEHOLDER_TEXT_CAP, RADIO_OPTION_CAP, SECTION_TEXT_CAP, SELECT_OPTION_CAP, TABLE_CAPTION, buildAlertBlock, buildAlertFallback, buildBlockBudgetNote, buildCardBlock, buildCarouselBlock, buildDataTableBlock };
144
+ export { ACTIONS_ELEMENT_CAP, ACTION_ID_CAP, ALERT_TEXT_CAP, BUTTON_VALUE_CAP, CARD_ACTIONS_CAP, CARD_BODY_CAP, CARD_SUBTEXT_CAP, CARD_TITLE_CAP, CAROUSEL_CARD_CAP, CAROUSEL_CARD_MIN, CHECKBOX_OPTION_CAP, CONTEXT_ELEMENT_CAP, CONTEXT_TEXT_CAP, DATA_TABLE_CHAR_BUDGET, DATA_TABLE_COLUMN_CAP, DATA_TABLE_ROW_CAP, FACT_FIELD_CAP, FACT_FIELD_TEXT_CAP, HEADER_TEXT_CAP, INBOUND_BLOCK_CAP, INPUT_LABEL_CAP, INTERACTIVE_TEXT_CAP, MARKDOWN_TEXT_BUDGET, MESSAGE_BLOCK_CAP, MODAL_BLOCK_CAP, PLACEHOLDER_TEXT_CAP, RADIO_OPTION_CAP, SECTION_TEXT_CAP, SELECT_OPTION_CAP, TABLE_CAPTION, buildAlertBlock, buildAlertFallback, buildBlockBudgetNote, buildCardBlock, buildCarouselBlock, buildDataTableBlock };
143
145
 
144
146
  //# sourceMappingURL=constants.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","names":[],"sources":["../../src/slack/constants.ts"],"sourcesContent":["import type {\n SlackAlertBlock,\n SlackButtonElement,\n SlackCardBlock,\n SlackCarouselBlock,\n SlackContextBlock,\n SlackDataTableBlock,\n SlackDataTableCell,\n SlackImageBlock,\n SlackSectionBlock,\n SlackTextObject,\n} from \"./types\";\n\n/** The block limit for a Slack message. */\nexport const MESSAGE_BLOCK_CAP = 50;\n\n/** The block limit for a Slack modal. */\nexport const MODAL_BLOCK_CAP = 100;\n\n/**\n * The maximum number of top-level blocks accepted when parsing Slack Block\n * Kit JSON back into generative UI, guarding against a hostile or malformed\n * payload.\n */\nexport const INBOUND_BLOCK_CAP = 200;\n\n/** The character limit for header text. */\nexport const HEADER_TEXT_CAP = 150;\n\n/** The character limit for section text. */\nexport const SECTION_TEXT_CAP = 3000;\n\n/** The maximum number of fields in one section. */\nexport const FACT_FIELD_CAP = 10;\n\n/** The character limit for one section field. */\nexport const FACT_FIELD_TEXT_CAP = 2000;\n\n/** The character limit for button labels and option labels. */\nexport const INTERACTIVE_TEXT_CAP = 75;\n\n/** The character limit for an action_id. */\nexport const ACTION_ID_CAP = 255;\n\n/** The character limit for a button's serialized value payload. */\nexport const BUTTON_VALUE_CAP = 2000;\n\n/** The maximum number of elements in one actions block. */\nexport const ACTIONS_ELEMENT_CAP = 25;\n\n/** The maximum number of options in a static select. */\nexport const SELECT_OPTION_CAP = 100;\n\n/** The maximum number of options in a radio-button group. */\nexport const RADIO_OPTION_CAP = 10;\n\n/** The character limit for a select or input placeholder. */\nexport const PLACEHOLDER_TEXT_CAP = 150;\n\n/** The character limit for an input's label. */\nexport const INPUT_LABEL_CAP = 2000;\n\n/** The maximum number of elements in one context block. */\nexport const CONTEXT_ELEMENT_CAP = 10;\n\n/** The character limit for one context element's text. */\nexport const CONTEXT_TEXT_CAP = 3000;\n\n/** The character limit for card titles and subtitles. */\nexport const CARD_TITLE_CAP = 150;\n\n/** The character limit for a card's body text. */\nexport const CARD_BODY_CAP = 200;\n\n/** The character limit for a card's subtext. */\nexport const CARD_SUBTEXT_CAP = 200;\n\n/** The maximum number of buttons in a card's actions. */\nexport const CARD_ACTIONS_CAP = 3;\n\n/** The minimum number of elements in a renderable carousel. */\nexport const CAROUSEL_CARD_MIN = 1;\n\n/** The maximum number of elements in a carousel. */\nexport const CAROUSEL_CARD_CAP = 10;\n\n/** The character limit for an alert's text. */\nexport const ALERT_TEXT_CAP = 200;\n\n/**\n * The maximum number of data rows in a data-table block; the emitted `rows`\n * array additionally carries one header row.\n */\nexport const DATA_TABLE_ROW_CAP = 200;\n\n/** The maximum number of columns in a data-table block. */\nexport const DATA_TABLE_COLUMN_CAP = 20;\n\n/**\n * The cumulative character limit (summed across every cell's text) shared by\n * every data-table block in one payload.\n */\nexport const DATA_TABLE_CHAR_BUDGET = 20000;\n\n/** The caption emitted on every native data-table block. */\nexport const TABLE_CAPTION = \"Table\";\n\n/** The cumulative character limit for native markdown blocks. */\nexport const MARKDOWN_TEXT_BUDGET = 12000;\n\n/** Builds the message-surface fallback for an alert. */\nexport function buildAlertFallback(\n title: string,\n description: string,\n tone: \"info\" | \"success\" | \"warning\" | \"danger\",\n): readonly [SlackContextBlock, SlackSectionBlock] {\n const emoji = {\n info: \"ℹ️\",\n success: \"✅\",\n warning: \"⚠️\",\n danger: \"🛑\",\n }[tone];\n return [\n {\n type: \"context\",\n elements: [\n { type: \"mrkdwn\", text: title ? `${emoji} *${title}*` : emoji },\n ],\n },\n { type: \"section\", text: { type: \"mrkdwn\", text: description } },\n ];\n}\n\n/** Builds a 2026 Slack modal-only alert block, mapping `tone` to its `level`. */\nexport function buildAlertBlock(\n text: string,\n tone: \"info\" | \"success\" | \"warning\" | \"danger\",\n): SlackAlertBlock {\n const level = tone === \"danger\" ? \"error\" : tone;\n return { type: \"alert\", text: { type: \"mrkdwn\", text }, level };\n}\n\ntype CardBlockFields = {\n readonly heroImage?: SlackImageBlock;\n readonly title?: SlackTextObject;\n readonly subtitle?: SlackTextObject;\n readonly body?: SlackTextObject;\n readonly subtext?: SlackTextObject;\n readonly actions?: readonly SlackButtonElement[];\n};\n\n/** Builds a 2026 Slack card block from its already-clamped fields. */\nexport function buildCardBlock(fields: CardBlockFields): SlackCardBlock {\n const { heroImage, title, subtitle, body, subtext, actions } = fields;\n return {\n type: \"card\",\n ...(heroImage !== undefined ? { hero_image: heroImage } : {}),\n ...(title !== undefined ? { title } : {}),\n ...(subtitle !== undefined ? { subtitle } : {}),\n ...(body !== undefined ? { body } : {}),\n ...(subtext !== undefined ? { subtext } : {}),\n ...(actions !== undefined ? { actions } : {}),\n };\n}\n\n/** Builds a 2026 Slack carousel block. */\nexport function buildCarouselBlock(\n elements: readonly SlackCardBlock[],\n): SlackCarouselBlock {\n return { type: \"carousel\", elements };\n}\n\n/** Builds a 2026 Slack data-table block; `rows[0]` must be the header row. */\nexport function buildDataTableBlock(\n rows: readonly (readonly SlackDataTableCell[])[],\n): SlackDataTableBlock {\n return { type: \"data_table\", caption: TABLE_CAPTION, rows };\n}\n\n/** Builds the final note used when a surface block budget is exceeded. */\nexport function buildBlockBudgetNote(omitted: number): SlackContextBlock {\n return {\n type: \"context\",\n elements: [\n {\n type: \"mrkdwn\",\n text: `${omitted} ${omitted === 1 ? \"block was\" : \"blocks were\"} omitted.`,\n },\n ],\n };\n}\n"],"mappings":";;AAcA,MAAa,oBAAoB;;AAGjC,MAAa,kBAAkB;;;;;;AAO/B,MAAa,oBAAoB;;AAGjC,MAAa,kBAAkB;;AAG/B,MAAa,mBAAmB;;AAGhC,MAAa,iBAAiB;;AAG9B,MAAa,sBAAsB;;AAGnC,MAAa,uBAAuB;;AAGpC,MAAa,gBAAgB;;AAG7B,MAAa,mBAAmB;;AAGhC,MAAa,sBAAsB;;AAGnC,MAAa,oBAAoB;;AAGjC,MAAa,mBAAmB;;AAGhC,MAAa,uBAAuB;;AAGpC,MAAa,kBAAkB;;AAG/B,MAAa,sBAAsB;;AAGnC,MAAa,mBAAmB;;AAGhC,MAAa,iBAAiB;;AAG9B,MAAa,gBAAgB;;AAG7B,MAAa,mBAAmB;;AAGhC,MAAa,mBAAmB;;AAGhC,MAAa,oBAAoB;;AAGjC,MAAa,oBAAoB;;AAGjC,MAAa,iBAAiB;;;;;AAM9B,MAAa,qBAAqB;;AAGlC,MAAa,wBAAwB;;;;;AAMrC,MAAa,yBAAyB;;AAGtC,MAAa,gBAAgB;;AAG7B,MAAa,uBAAuB;;AAGpC,SAAgB,mBACd,OACA,aACA,MACiD;CACjD,MAAM,QAAQ;EACZ,MAAM;EACN,SAAS;EACT,SAAS;EACT,QAAQ;CACV,EAAE;CACF,OAAO,CACL;EACE,MAAM;EACN,UAAU,CACR;GAAE,MAAM;GAAU,MAAM,QAAQ,GAAG,MAAM,IAAI,MAAM,KAAK;EAAM,CAChE;CACF,GACA;EAAE,MAAM;EAAW,MAAM;GAAE,MAAM;GAAU,MAAM;EAAY;CAAE,CACjE;AACF;;AAGA,SAAgB,gBACd,MACA,MACiB;CAEjB,OAAO;EAAE,MAAM;EAAS,MAAM;GAAE,MAAM;GAAU;EAAK;EAAG,OAD1C,SAAS,WAAW,UAAU;CACkB;AAChE;;AAYA,SAAgB,eAAe,QAAyC;CACtE,MAAM,EAAE,WAAW,OAAO,UAAU,MAAM,SAAS,YAAY;CAC/D,OAAO;EACL,MAAM;EACN,GAAI,cAAc,KAAA,IAAY,EAAE,YAAY,UAAU,IAAI,CAAC;EAC3D,GAAI,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;EACvC,GAAI,aAAa,KAAA,IAAY,EAAE,SAAS,IAAI,CAAC;EAC7C,GAAI,SAAS,KAAA,IAAY,EAAE,KAAK,IAAI,CAAC;EACrC,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;EAC3C,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;CAC7C;AACF;;AAGA,SAAgB,mBACd,UACoB;CACpB,OAAO;EAAE,MAAM;EAAY;CAAS;AACtC;;AAGA,SAAgB,oBACd,MACqB;CACrB,OAAO;EAAE,MAAM;EAAc,SAAS;EAAe;CAAK;AAC5D;;AAGA,SAAgB,qBAAqB,SAAoC;CACvE,OAAO;EACL,MAAM;EACN,UAAU,CACR;GACE,MAAM;GACN,MAAM,GAAG,QAAQ,GAAG,YAAY,IAAI,cAAc,cAAc;EAClE,CACF;CACF;AACF"}
1
+ {"version":3,"file":"constants.js","names":[],"sources":["../../src/slack/constants.ts"],"sourcesContent":["import type {\n SlackAlertBlock,\n SlackButtonElement,\n SlackCardBlock,\n SlackCarouselBlock,\n SlackContextBlock,\n SlackDataTableBlock,\n SlackDataTableCell,\n SlackImageBlock,\n SlackSectionBlock,\n SlackTextObject,\n} from \"./types\";\n\n/** The block limit for a Slack message. */\nexport const MESSAGE_BLOCK_CAP = 50;\n\n/** The block limit for a Slack modal. */\nexport const MODAL_BLOCK_CAP = 100;\n\n/**\n * The maximum number of top-level blocks accepted when parsing Slack Block\n * Kit JSON back into generative UI, guarding against a hostile or malformed\n * payload.\n */\nexport const INBOUND_BLOCK_CAP = 200;\n\n/** The character limit for header text. */\nexport const HEADER_TEXT_CAP = 150;\n\n/** The character limit for section text. */\nexport const SECTION_TEXT_CAP = 3000;\n\n/** The maximum number of fields in one section. */\nexport const FACT_FIELD_CAP = 10;\n\n/** The character limit for one section field. */\nexport const FACT_FIELD_TEXT_CAP = 2000;\n\n/** The character limit for button labels and option labels. */\nexport const INTERACTIVE_TEXT_CAP = 75;\n\n/** The character limit for an action_id. */\nexport const ACTION_ID_CAP = 255;\n\n/** The character limit for a button's serialized value payload. */\nexport const BUTTON_VALUE_CAP = 2000;\n\n/** The maximum number of elements in one actions block. */\nexport const ACTIONS_ELEMENT_CAP = 25;\n\n/** The maximum number of options in a static select. */\nexport const SELECT_OPTION_CAP = 100;\n\n/** The maximum number of options in a radio-button group. */\nexport const RADIO_OPTION_CAP = 10;\n\n/** The maximum number of options in a checkbox group. */\nexport const CHECKBOX_OPTION_CAP = 10;\n\n/** The character limit for a select or input placeholder. */\nexport const PLACEHOLDER_TEXT_CAP = 150;\n\n/** The character limit for an input's label. */\nexport const INPUT_LABEL_CAP = 2000;\n\n/** The maximum number of elements in one context block. */\nexport const CONTEXT_ELEMENT_CAP = 10;\n\n/** The character limit for one context element's text. */\nexport const CONTEXT_TEXT_CAP = 3000;\n\n/** The character limit for card titles and subtitles. */\nexport const CARD_TITLE_CAP = 150;\n\n/** The character limit for a card's body text. */\nexport const CARD_BODY_CAP = 200;\n\n/** The character limit for a card's subtext. */\nexport const CARD_SUBTEXT_CAP = 200;\n\n/** The maximum number of buttons in a card's actions. */\nexport const CARD_ACTIONS_CAP = 3;\n\n/** The minimum number of elements in a renderable carousel. */\nexport const CAROUSEL_CARD_MIN = 1;\n\n/** The maximum number of elements in a carousel. */\nexport const CAROUSEL_CARD_CAP = 10;\n\n/** The character limit for an alert's text. */\nexport const ALERT_TEXT_CAP = 200;\n\n/**\n * The maximum number of data rows in a data-table block; the emitted `rows`\n * array additionally carries one header row.\n */\nexport const DATA_TABLE_ROW_CAP = 200;\n\n/** The maximum number of columns in a data-table block. */\nexport const DATA_TABLE_COLUMN_CAP = 20;\n\n/**\n * The cumulative character limit (summed across every cell's text) shared by\n * every data-table block in one payload.\n */\nexport const DATA_TABLE_CHAR_BUDGET = 20000;\n\n/** The caption emitted on every native data-table block. */\nexport const TABLE_CAPTION = \"Table\";\n\n/** The cumulative character limit for native markdown blocks. */\nexport const MARKDOWN_TEXT_BUDGET = 12000;\n\n/** Builds the message-surface fallback for an alert. */\nexport function buildAlertFallback(\n title: string,\n description: string,\n tone: \"info\" | \"success\" | \"warning\" | \"danger\",\n): readonly [SlackContextBlock, SlackSectionBlock] {\n const emoji = {\n info: \"ℹ️\",\n success: \"✅\",\n warning: \"⚠️\",\n danger: \"🛑\",\n }[tone];\n return [\n {\n type: \"context\",\n elements: [\n { type: \"mrkdwn\", text: title ? `${emoji} *${title}*` : emoji },\n ],\n },\n { type: \"section\", text: { type: \"mrkdwn\", text: description } },\n ];\n}\n\n/** Builds a 2026 Slack modal-only alert block, mapping `tone` to its `level`. */\nexport function buildAlertBlock(\n text: string,\n tone: \"info\" | \"success\" | \"warning\" | \"danger\",\n): SlackAlertBlock {\n const level = tone === \"danger\" ? \"error\" : tone;\n return { type: \"alert\", text: { type: \"mrkdwn\", text }, level };\n}\n\ntype CardBlockFields = {\n readonly heroImage?: SlackImageBlock;\n readonly title?: SlackTextObject;\n readonly subtitle?: SlackTextObject;\n readonly body?: SlackTextObject;\n readonly subtext?: SlackTextObject;\n readonly actions?: readonly SlackButtonElement[];\n};\n\n/** Builds a 2026 Slack card block from its already-clamped fields. */\nexport function buildCardBlock(fields: CardBlockFields): SlackCardBlock {\n const { heroImage, title, subtitle, body, subtext, actions } = fields;\n return {\n type: \"card\",\n ...(heroImage !== undefined ? { hero_image: heroImage } : {}),\n ...(title !== undefined ? { title } : {}),\n ...(subtitle !== undefined ? { subtitle } : {}),\n ...(body !== undefined ? { body } : {}),\n ...(subtext !== undefined ? { subtext } : {}),\n ...(actions !== undefined ? { actions } : {}),\n };\n}\n\n/** Builds a 2026 Slack carousel block. */\nexport function buildCarouselBlock(\n elements: readonly SlackCardBlock[],\n): SlackCarouselBlock {\n return { type: \"carousel\", elements };\n}\n\n/** Builds a 2026 Slack data-table block; `rows[0]` must be the header row. */\nexport function buildDataTableBlock(\n rows: readonly (readonly SlackDataTableCell[])[],\n): SlackDataTableBlock {\n return { type: \"data_table\", caption: TABLE_CAPTION, rows };\n}\n\n/** Builds the final note used when a surface block budget is exceeded. */\nexport function buildBlockBudgetNote(omitted: number): SlackContextBlock {\n return {\n type: \"context\",\n elements: [\n {\n type: \"mrkdwn\",\n text: `${omitted} ${omitted === 1 ? \"block was\" : \"blocks were\"} omitted.`,\n },\n ],\n };\n}\n"],"mappings":";;AAcA,MAAa,oBAAoB;;AAGjC,MAAa,kBAAkB;;;;;;AAO/B,MAAa,oBAAoB;;AAGjC,MAAa,kBAAkB;;AAG/B,MAAa,mBAAmB;;AAGhC,MAAa,iBAAiB;;AAG9B,MAAa,sBAAsB;;AAGnC,MAAa,uBAAuB;;AAGpC,MAAa,gBAAgB;;AAG7B,MAAa,mBAAmB;;AAGhC,MAAa,sBAAsB;;AAGnC,MAAa,oBAAoB;;AAGjC,MAAa,mBAAmB;;AAGhC,MAAa,sBAAsB;;AAGnC,MAAa,uBAAuB;;AAGpC,MAAa,kBAAkB;;AAG/B,MAAa,sBAAsB;;AAGnC,MAAa,mBAAmB;;AAGhC,MAAa,iBAAiB;;AAG9B,MAAa,gBAAgB;;AAG7B,MAAa,mBAAmB;;AAGhC,MAAa,mBAAmB;;AAGhC,MAAa,oBAAoB;;AAGjC,MAAa,oBAAoB;;AAGjC,MAAa,iBAAiB;;;;;AAM9B,MAAa,qBAAqB;;AAGlC,MAAa,wBAAwB;;;;;AAMrC,MAAa,yBAAyB;;AAGtC,MAAa,gBAAgB;;AAG7B,MAAa,uBAAuB;;AAGpC,SAAgB,mBACd,OACA,aACA,MACiD;CACjD,MAAM,QAAQ;EACZ,MAAM;EACN,SAAS;EACT,SAAS;EACT,QAAQ;CACV,EAAE;CACF,OAAO,CACL;EACE,MAAM;EACN,UAAU,CACR;GAAE,MAAM;GAAU,MAAM,QAAQ,GAAG,MAAM,IAAI,MAAM,KAAK;EAAM,CAChE;CACF,GACA;EAAE,MAAM;EAAW,MAAM;GAAE,MAAM;GAAU,MAAM;EAAY;CAAE,CACjE;AACF;;AAGA,SAAgB,gBACd,MACA,MACiB;CAEjB,OAAO;EAAE,MAAM;EAAS,MAAM;GAAE,MAAM;GAAU;EAAK;EAAG,OAD1C,SAAS,WAAW,UAAU;CACkB;AAChE;;AAYA,SAAgB,eAAe,QAAyC;CACtE,MAAM,EAAE,WAAW,OAAO,UAAU,MAAM,SAAS,YAAY;CAC/D,OAAO;EACL,MAAM;EACN,GAAI,cAAc,KAAA,IAAY,EAAE,YAAY,UAAU,IAAI,CAAC;EAC3D,GAAI,UAAU,KAAA,IAAY,EAAE,MAAM,IAAI,CAAC;EACvC,GAAI,aAAa,KAAA,IAAY,EAAE,SAAS,IAAI,CAAC;EAC7C,GAAI,SAAS,KAAA,IAAY,EAAE,KAAK,IAAI,CAAC;EACrC,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;EAC3C,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;CAC7C;AACF;;AAGA,SAAgB,mBACd,UACoB;CACpB,OAAO;EAAE,MAAM;EAAY;CAAS;AACtC;;AAGA,SAAgB,oBACd,MACqB;CACrB,OAAO;EAAE,MAAM;EAAc,SAAS;EAAe;CAAK;AAC5D;;AAGA,SAAgB,qBAAqB,SAAoC;CACvE,OAAO;EACL,MAAM;EACN,UAAU,CACR;GACE,MAAM;GACN,MAAM,GAAG,QAAQ,GAAG,YAAY,IAAI,cAAc,cAAc;EAClE,CACF;CACF;AACF"}
@@ -3,6 +3,8 @@ import type { Action } from "../ir.js";
3
3
  * Decodes one structural entry from a Slack `block_actions` payload.
4
4
  * `$input` is reserved for the runtime selection: a `$input` key inside the
5
5
  * button's JSON `value` payload is dropped rather than spread into the result.
6
+ * `stateValues` is the companion `state.values` object used to resolve named
7
+ * control values referenced by `{ "$field": name }` in the action payload.
6
8
  */
7
- export declare function decodeBlockAction(action: unknown): Action | undefined;
9
+ export declare function decodeBlockAction(action: unknown, stateValues?: unknown): Action | undefined;
8
10
  //# sourceMappingURL=decodeBlockAction.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"decodeBlockAction.d.ts","sourceRoot":"","sources":["../../src/slack/decodeBlockAction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAc;AAUpC;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAkDrE"}
1
+ {"version":3,"file":"decodeBlockAction.d.ts","sourceRoot":"","sources":["../../src/slack/decodeBlockAction.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,iBAAc;AA8JpC;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,OAAO,EACf,WAAW,CAAC,EAAE,OAAO,GACpB,MAAM,GAAG,SAAS,CA+DpB"}
@@ -1,12 +1,94 @@
1
+ import { resolveFieldReferences } from "../fieldReferences.js";
2
+ import { formatTemporalUnixSeconds, mergeTemporalMinutes, splitTemporalMinutes } from "../temporal.js";
1
3
  //#region src/slack/decodeBlockAction.ts
2
4
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3
5
  const optionValue = (value) => isRecord(value) && typeof value["value"] === "string" ? value["value"] : void 0;
6
+ const selectedValue = (action, field) => {
7
+ if (action["type"] === "datetimepicker" || "selected_date_time" in action) {
8
+ const seconds = action["selected_date_time"];
9
+ return seconds === null ? "" : typeof seconds === "number" ? formatTemporalUnixSeconds(seconds, field?.offset) : void 0;
10
+ }
11
+ if (action["type"] === "timepicker" || "selected_time" in action) {
12
+ const time = action["selected_time"];
13
+ if (time === null) return "";
14
+ const parts = typeof time === "string" ? splitTemporalMinutes(time) : void 0;
15
+ return parts?.date === void 0 ? parts?.time : void 0;
16
+ }
17
+ if (field?.part === "date" && action["selected_date"] === null) return "";
18
+ const selectedOption = optionValue(action["selected_option"]);
19
+ const selectedDate = typeof action["selected_date"] === "string" ? action["selected_date"] : void 0;
20
+ const selectedOptions = Array.isArray(action["selected_options"]) ? action["selected_options"].map(optionValue).filter((value) => value !== void 0) : void 0;
21
+ const numberInput = action["type"] === "number_input" && typeof action["value"] === "string" ? Number(action["value"]) : void 0;
22
+ return selectedOption ?? selectedDate ?? (selectedOptions !== void 0 ? selectedOptions : Number.isFinite(numberInput) ? numberInput : void 0);
23
+ };
24
+ const fieldMappingsFromBlockId = (blockId) => {
25
+ if (!/^aui:\d+:/.test(blockId)) return [];
26
+ const mappingStart = blockId.indexOf(":", 4);
27
+ if (mappingStart === -1) return [];
28
+ try {
29
+ const parsed = JSON.parse(blockId.slice(mappingStart + 1));
30
+ if (!Array.isArray(parsed)) return [];
31
+ return parsed.flatMap((entry) => {
32
+ if (!Array.isArray(entry) || typeof entry[0] !== "string" || typeof entry[1] !== "string") return [];
33
+ return [{
34
+ actionId: entry[0],
35
+ name: entry[1],
36
+ component: typeof entry[2] === "string" ? entry[2] : "",
37
+ ...entry[2] === "DatePicker" && (entry[3] === "time" || entry[3] === "datetime") ? {
38
+ inputType: entry[3],
39
+ ...typeof entry[4] === "string" ? { offset: entry[4] } : {},
40
+ ...(entry[5] === "date" || entry[5] === "time") && Number.isSafeInteger(entry[6]) && entry[6] >= 0 ? {
41
+ part: entry[5],
42
+ pairId: entry[6]
43
+ } : {}
44
+ } : {}
45
+ }];
46
+ });
47
+ } catch {
48
+ return [];
49
+ }
50
+ };
51
+ const checkboxValue = (value, name) => Array.isArray(value["selected_options"]) ? value["selected_options"].some((option) => optionValue(option) === name) : void 0;
52
+ const stateFieldValue = (value, field) => {
53
+ if (!isRecord(value)) return void 0;
54
+ if (field.component === "Checkbox" && value["type"] === "checkboxes") return checkboxValue(value, field.name);
55
+ if (value["type"] === "plain_text_input") return typeof value["value"] === "string" ? value["value"] : void 0;
56
+ return selectedValue(value, field);
57
+ };
58
+ const fieldValuesFromState = (stateValues, action, actionField) => {
59
+ const fields = Object.create(null);
60
+ const pairs = /* @__PURE__ */ new Map();
61
+ const collect = (raw, field) => {
62
+ const value = stateFieldValue(raw, field);
63
+ if (field.part !== void 0 && field.pairId !== void 0) {
64
+ const pair = pairs.get(field.pairId) ?? {
65
+ name: field.name,
66
+ date: "",
67
+ time: ""
68
+ };
69
+ pair[field.part] = typeof value === "string" ? value : "";
70
+ pairs.set(field.pairId, pair);
71
+ } else if (field.name && value !== void 0) fields[field.name] = value;
72
+ };
73
+ if (isRecord(stateValues)) for (const [blockId, rawBlockValues] of Object.entries(stateValues)) {
74
+ if (!isRecord(rawBlockValues)) continue;
75
+ for (const field of fieldMappingsFromBlockId(blockId)) if (Object.hasOwn(rawBlockValues, field.actionId)) collect(rawBlockValues[field.actionId], field);
76
+ }
77
+ if (actionField?.inputType !== void 0) collect(action, actionField);
78
+ for (const pair of pairs.values()) if (pair.name) fields[pair.name] = mergeTemporalMinutes(pair.date, pair.time);
79
+ return {
80
+ fields,
81
+ pairs
82
+ };
83
+ };
4
84
  /**
5
85
  * Decodes one structural entry from a Slack `block_actions` payload.
6
86
  * `$input` is reserved for the runtime selection: a `$input` key inside the
7
87
  * button's JSON `value` payload is dropped rather than spread into the result.
88
+ * `stateValues` is the companion `state.values` object used to resolve named
89
+ * control values referenced by `{ "$field": name }` in the action payload.
8
90
  */
9
- function decodeBlockAction(action) {
91
+ function decodeBlockAction(action, stateValues) {
10
92
  try {
11
93
  if (!isRecord(action) || typeof action["action_id"] !== "string") return;
12
94
  const actionId = action["action_id"];
@@ -14,22 +96,29 @@ function decodeBlockAction(action) {
14
96
  const rawValue = action["value"];
15
97
  let payload = {};
16
98
  let plainValue;
17
- if (typeof rawValue === "string") try {
99
+ if (action["type"] === "plain_text_input" && typeof rawValue === "string") plainValue = rawValue;
100
+ else if (typeof rawValue === "string") try {
18
101
  const parsed = JSON.parse(rawValue);
19
102
  if (isRecord(parsed)) payload = parsed;
20
103
  else plainValue = rawValue;
21
104
  } catch {
22
105
  plainValue = rawValue;
23
106
  }
24
- const selectedOption = optionValue(action["selected_option"]);
25
- const selectedDate = typeof action["selected_date"] === "string" ? action["selected_date"] : void 0;
26
- const selectedOptions = Array.isArray(action["selected_options"]) ? action["selected_options"].map(optionValue).filter((value) => value !== void 0) : void 0;
27
- const input = selectedOption ?? selectedDate ?? (selectedOptions !== void 0 ? selectedOptions : plainValue);
28
- return {
107
+ const actionField = typeof action["block_id"] === "string" ? fieldMappingsFromBlockId(action["block_id"]).find((field) => field.actionId === actionId) : void 0;
108
+ const { fields, pairs } = fieldValuesFromState(stateValues, action, actionField);
109
+ let input = selectedValue(action, actionField) ?? plainValue;
110
+ if (actionField?.pairId !== void 0) {
111
+ const pair = pairs.get(actionField.pairId);
112
+ input = pair === void 0 ? "" : mergeTemporalMinutes(pair.date, pair.time);
113
+ if (!input) return void 0;
114
+ }
115
+ if ((action["type"] === "timepicker" || action["type"] === "datetimepicker") && input === void 0) return void 0;
116
+ const decoded = {
29
117
  ...Object.fromEntries(Object.entries(payload).filter(([key]) => key !== "$input")),
30
118
  type: actionId,
31
119
  ...input !== void 0 ? { $input: input } : {}
32
120
  };
121
+ return resolveFieldReferences(decoded, fields);
33
122
  } catch {
34
123
  return;
35
124
  }
@@ -1 +1 @@
1
- {"version":3,"file":"decodeBlockAction.js","names":[],"sources":["../../src/slack/decodeBlockAction.ts"],"sourcesContent":["import type { Action } from \"../ir\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst optionValue = (value: unknown): string | undefined =>\n isRecord(value) && typeof value[\"value\"] === \"string\"\n ? value[\"value\"]\n : undefined;\n\n/**\n * Decodes one structural entry from a Slack `block_actions` payload.\n * `$input` is reserved for the runtime selection: a `$input` key inside the\n * button's JSON `value` payload is dropped rather than spread into the result.\n */\nexport function decodeBlockAction(action: unknown): Action | undefined {\n try {\n if (!isRecord(action) || typeof action[\"action_id\"] !== \"string\") {\n return undefined;\n }\n\n const actionId = action[\"action_id\"];\n if (!actionId) return undefined;\n\n const rawValue = action[\"value\"];\n let payload: Record<string, unknown> = {};\n let plainValue: string | undefined;\n if (typeof rawValue === \"string\") {\n try {\n const parsed: unknown = JSON.parse(rawValue);\n if (isRecord(parsed)) {\n payload = parsed;\n } else {\n plainValue = rawValue;\n }\n } catch {\n plainValue = rawValue;\n }\n }\n\n const selectedOption = optionValue(action[\"selected_option\"]);\n const selectedDate =\n typeof action[\"selected_date\"] === \"string\"\n ? action[\"selected_date\"]\n : undefined;\n const selectedOptions = Array.isArray(action[\"selected_options\"])\n ? action[\"selected_options\"]\n .map(optionValue)\n .filter((value): value is string => value !== undefined)\n : undefined;\n const input =\n selectedOption ??\n selectedDate ??\n (selectedOptions !== undefined ? selectedOptions : plainValue);\n\n return {\n ...Object.fromEntries(\n Object.entries(payload).filter(([key]) => key !== \"$input\"),\n ),\n type: actionId,\n ...(input !== undefined ? { $input: input } : {}),\n };\n } catch {\n return undefined;\n }\n}\n"],"mappings":";AAEA,MAAM,YAAY,UAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAErE,MAAM,eAAe,UACnB,SAAS,KAAK,KAAK,OAAO,MAAM,aAAa,WACzC,MAAM,WACN,KAAA;;;;;;AAON,SAAgB,kBAAkB,QAAqC;CACrE,IAAI;EACF,IAAI,CAAC,SAAS,MAAM,KAAK,OAAO,OAAO,iBAAiB,UACtD;EAGF,MAAM,WAAW,OAAO;EACxB,IAAI,CAAC,UAAU,OAAO,KAAA;EAEtB,MAAM,WAAW,OAAO;EACxB,IAAI,UAAmC,CAAC;EACxC,IAAI;EACJ,IAAI,OAAO,aAAa,UACtB,IAAI;GACF,MAAM,SAAkB,KAAK,MAAM,QAAQ;GAC3C,IAAI,SAAS,MAAM,GACjB,UAAU;QAEV,aAAa;EAEjB,QAAQ;GACN,aAAa;EACf;EAGF,MAAM,iBAAiB,YAAY,OAAO,kBAAkB;EAC5D,MAAM,eACJ,OAAO,OAAO,qBAAqB,WAC/B,OAAO,mBACP,KAAA;EACN,MAAM,kBAAkB,MAAM,QAAQ,OAAO,mBAAmB,IAC5D,OAAO,mBAAmB,CACvB,IAAI,WAAW,CAAC,CAChB,QAAQ,UAA2B,UAAU,KAAA,CAAS,IACzD,KAAA;EACJ,MAAM,QACJ,kBACA,iBACC,oBAAoB,KAAA,IAAY,kBAAkB;EAErD,OAAO;GACL,GAAG,OAAO,YACR,OAAO,QAAQ,OAAO,CAAC,CAAC,QAAQ,CAAC,SAAS,QAAQ,QAAQ,CAC5D;GACA,MAAM;GACN,GAAI,UAAU,KAAA,IAAY,EAAE,QAAQ,MAAM,IAAI,CAAC;EACjD;CACF,QAAQ;EACN;CACF;AACF"}
1
+ {"version":3,"file":"decodeBlockAction.js","names":[],"sources":["../../src/slack/decodeBlockAction.ts"],"sourcesContent":["import type { Action } from \"../ir\";\nimport { resolveFieldReferences } from \"../fieldReferences\";\nimport {\n formatTemporalUnixSeconds,\n mergeTemporalMinutes,\n splitTemporalMinutes,\n} from \"../temporal\";\nimport type { FieldMapping } from \"./types\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst optionValue = (value: unknown): string | undefined =>\n isRecord(value) && typeof value[\"value\"] === \"string\"\n ? value[\"value\"]\n : undefined;\n\nconst selectedValue = (\n action: Record<string, unknown>,\n field?: FieldMapping,\n): unknown => {\n if (action[\"type\"] === \"datetimepicker\" || \"selected_date_time\" in action) {\n const seconds = action[\"selected_date_time\"];\n return seconds === null\n ? \"\"\n : typeof seconds === \"number\"\n ? formatTemporalUnixSeconds(seconds, field?.offset)\n : undefined;\n }\n if (action[\"type\"] === \"timepicker\" || \"selected_time\" in action) {\n const time = action[\"selected_time\"];\n if (time === null) return \"\";\n const parts =\n typeof time === \"string\" ? splitTemporalMinutes(time) : undefined;\n return parts?.date === undefined ? parts?.time : undefined;\n }\n if (field?.part === \"date\" && action[\"selected_date\"] === null) return \"\";\n const selectedOption = optionValue(action[\"selected_option\"]);\n const selectedDate =\n typeof action[\"selected_date\"] === \"string\"\n ? action[\"selected_date\"]\n : undefined;\n const selectedOptions = Array.isArray(action[\"selected_options\"])\n ? action[\"selected_options\"]\n .map(optionValue)\n .filter((value): value is string => value !== undefined)\n : undefined;\n const numberInput =\n action[\"type\"] === \"number_input\" && typeof action[\"value\"] === \"string\"\n ? Number(action[\"value\"])\n : undefined;\n return (\n selectedOption ??\n selectedDate ??\n (selectedOptions !== undefined\n ? selectedOptions\n : Number.isFinite(numberInput)\n ? numberInput\n : undefined)\n );\n};\n\nconst fieldMappingsFromBlockId = (blockId: string): readonly FieldMapping[] => {\n if (!/^aui:\\d+:/.test(blockId)) return [];\n const mappingStart = blockId.indexOf(\":\", 4);\n if (mappingStart === -1) return [];\n try {\n const parsed: unknown = JSON.parse(blockId.slice(mappingStart + 1));\n if (!Array.isArray(parsed)) return [];\n return parsed.flatMap((entry): FieldMapping[] => {\n if (\n !Array.isArray(entry) ||\n typeof entry[0] !== \"string\" ||\n typeof entry[1] !== \"string\"\n )\n return [];\n return [\n {\n actionId: entry[0],\n name: entry[1],\n component: typeof entry[2] === \"string\" ? entry[2] : \"\",\n ...(entry[2] === \"DatePicker\" &&\n (entry[3] === \"time\" || entry[3] === \"datetime\")\n ? {\n inputType: entry[3],\n ...(typeof entry[4] === \"string\" ? { offset: entry[4] } : {}),\n ...((entry[5] === \"date\" || entry[5] === \"time\") &&\n Number.isSafeInteger(entry[6]) &&\n entry[6] >= 0\n ? { part: entry[5], pairId: entry[6] }\n : {}),\n }\n : {}),\n },\n ];\n });\n } catch {\n return [];\n }\n};\n\nconst checkboxValue = (\n value: Record<string, unknown>,\n name: string,\n): boolean | undefined =>\n Array.isArray(value[\"selected_options\"])\n ? value[\"selected_options\"].some((option) => optionValue(option) === name)\n : undefined;\n\nconst stateFieldValue = (value: unknown, field: FieldMapping): unknown => {\n if (!isRecord(value)) return undefined;\n if (field.component === \"Checkbox\" && value[\"type\"] === \"checkboxes\") {\n return checkboxValue(value, field.name);\n }\n if (value[\"type\"] === \"plain_text_input\") {\n return typeof value[\"value\"] === \"string\" ? value[\"value\"] : undefined;\n }\n return selectedValue(value, field);\n};\n\nconst fieldValuesFromState = (\n stateValues: unknown,\n action: Record<string, unknown>,\n actionField: FieldMapping | undefined,\n) => {\n const fields: Record<string, unknown> = Object.create(null);\n const pairs = new Map<number, { name: string; date: string; time: string }>();\n const collect = (raw: unknown, field: FieldMapping) => {\n const value = stateFieldValue(raw, field);\n if (field.part !== undefined && field.pairId !== undefined) {\n const pair = pairs.get(field.pairId) ?? {\n name: field.name,\n date: \"\",\n time: \"\",\n };\n pair[field.part] = typeof value === \"string\" ? value : \"\";\n pairs.set(field.pairId, pair);\n } else if (field.name && value !== undefined) {\n fields[field.name] = value;\n }\n };\n if (isRecord(stateValues)) {\n for (const [blockId, rawBlockValues] of Object.entries(stateValues)) {\n if (!isRecord(rawBlockValues)) continue;\n for (const field of fieldMappingsFromBlockId(blockId)) {\n if (Object.hasOwn(rawBlockValues, field.actionId))\n collect(rawBlockValues[field.actionId], field);\n }\n }\n }\n if (actionField?.inputType !== undefined) collect(action, actionField);\n for (const pair of pairs.values()) {\n if (pair.name)\n fields[pair.name] = mergeTemporalMinutes(pair.date, pair.time);\n }\n return { fields, pairs };\n};\n\n/**\n * Decodes one structural entry from a Slack `block_actions` payload.\n * `$input` is reserved for the runtime selection: a `$input` key inside the\n * button's JSON `value` payload is dropped rather than spread into the result.\n * `stateValues` is the companion `state.values` object used to resolve named\n * control values referenced by `{ \"$field\": name }` in the action payload.\n */\nexport function decodeBlockAction(\n action: unknown,\n stateValues?: unknown,\n): Action | undefined {\n try {\n if (!isRecord(action) || typeof action[\"action_id\"] !== \"string\") {\n return undefined;\n }\n\n const actionId = action[\"action_id\"];\n if (!actionId) return undefined;\n\n const rawValue = action[\"value\"];\n let payload: Record<string, unknown> = {};\n let plainValue: string | undefined;\n if (action[\"type\"] === \"plain_text_input\" && typeof rawValue === \"string\") {\n plainValue = rawValue;\n } else if (typeof rawValue === \"string\") {\n try {\n const parsed: unknown = JSON.parse(rawValue);\n if (isRecord(parsed)) {\n payload = parsed;\n } else {\n plainValue = rawValue;\n }\n } catch {\n plainValue = rawValue;\n }\n }\n\n const actionField =\n typeof action[\"block_id\"] === \"string\"\n ? fieldMappingsFromBlockId(action[\"block_id\"]).find(\n (field) => field.actionId === actionId,\n )\n : undefined;\n const { fields, pairs } = fieldValuesFromState(\n stateValues,\n action,\n actionField,\n );\n let input = selectedValue(action, actionField) ?? plainValue;\n if (actionField?.pairId !== undefined) {\n const pair = pairs.get(actionField.pairId);\n input =\n pair === undefined ? \"\" : mergeTemporalMinutes(pair.date, pair.time);\n if (!input) return undefined;\n }\n if (\n (action[\"type\"] === \"timepicker\" ||\n action[\"type\"] === \"datetimepicker\") &&\n input === undefined\n )\n return undefined;\n\n const decoded = {\n ...Object.fromEntries(\n Object.entries(payload).filter(([key]) => key !== \"$input\"),\n ),\n type: actionId,\n ...(input !== undefined ? { $input: input } : {}),\n };\n return resolveFieldReferences(decoded, fields) as Action;\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"fromSlackBlocks.d.ts","sourceRoot":"","sources":["../../src/slack/fromSlackBlocks.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,qBAAqB,EAA0B,mBAAgB;AAynB7E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,qBAAqB,CAqCtE"}
1
+ {"version":3,"file":"fromSlackBlocks.d.ts","sourceRoot":"","sources":["../../src/slack/fromSlackBlocks.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,qBAAqB,EAA0B,mBAAgB;AAusB7E;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,OAAO,GAAG,qBAAqB,CAqCtE"}
@@ -1,3 +1,4 @@
1
+ import { formatTemporalUnixSeconds } from "../temporal.js";
1
2
  import { copyBounded } from "../convert/copyBounded.js";
2
3
  import "./constants.js";
3
4
  //#region src/slack/fromSlackBlocks.ts
@@ -135,11 +136,14 @@ const buttonFrom = (element) => {
135
136
  };
136
137
  };
137
138
  const selectFrom = (element, warnings) => {
138
- const options = boundedArray(element["options"], 100, warnings, "Select", `options were clamped to 100 entries.`);
139
+ const convertedOptions = boundedArray(element["options"], 100, warnings, "Select", `options were clamped to 100 entries.`).map(optionFrom).filter(isDefined);
140
+ const initialOption = element["initial_option"];
141
+ const defaultValue = isRecord(initialOption) && typeof initialOption["value"] === "string" ? initialOption["value"] : void 0;
139
142
  const placeholder = element["placeholder"];
140
143
  return {
141
144
  $type: "Select",
142
- options: options.map(optionFrom).filter(isDefined),
145
+ options: convertedOptions,
146
+ ...defaultValue !== void 0 && convertedOptions.some((option) => option.value === defaultValue) ? { defaultValue } : {},
143
147
  ...isRecord(placeholder) ? { placeholder: textOf(placeholder) } : {},
144
148
  $action: decodeAction(element["action_id"], element["value"])
145
149
  };
@@ -162,6 +166,19 @@ const checkboxFrom = (element) => {
162
166
  $action: decodeAction(element["action_id"], element["value"])
163
167
  };
164
168
  };
169
+ const checkboxGroupFrom = (element, warnings) => {
170
+ const options = boundedArray(element["options"], 10, warnings, "CheckboxGroup", `options were clamped to 10 entries.`).map(optionFrom).filter(isDefined);
171
+ const defaultValue = clampArray(element["initial_options"], 10).items.flatMap((initialOption) => {
172
+ const value = isRecord(initialOption) && typeof initialOption["value"] === "string" ? initialOption["value"] : void 0;
173
+ return value !== void 0 && options.some((option) => option.value === value) ? [value] : [];
174
+ });
175
+ return {
176
+ $type: "CheckboxGroup",
177
+ options,
178
+ ...defaultValue.length > 0 ? { defaultValue } : {},
179
+ $action: decodeAction(element["action_id"], element["value"])
180
+ };
181
+ };
165
182
  const radioGroupFrom = (element, warnings) => {
166
183
  const options = boundedArray(element["options"], 10, warnings, "RadioGroup", `options were clamped to 10 entries.`);
167
184
  const initialOption = element["initial_option"];
@@ -178,7 +195,22 @@ const actionElementFrom = (element, warnings) => {
178
195
  case "button": return buttonFrom(element);
179
196
  case "static_select": return selectFrom(element, warnings);
180
197
  case "datepicker": return datePickerFrom(element);
181
- case "checkboxes": return checkboxFrom(element);
198
+ case "timepicker": return {
199
+ $type: "DatePicker",
200
+ inputType: "time",
201
+ ...typeof element["initial_time"] === "string" ? { value: element["initial_time"] } : {},
202
+ $action: decodeAction(element["action_id"], element["value"])
203
+ };
204
+ case "datetimepicker": {
205
+ const value = typeof element["initial_date_time"] === "number" ? formatTemporalUnixSeconds(element["initial_date_time"]) : void 0;
206
+ return {
207
+ $type: "DatePicker",
208
+ inputType: "datetime",
209
+ ...value !== void 0 ? { value } : {},
210
+ $action: decodeAction(element["action_id"], element["value"])
211
+ };
212
+ }
213
+ case "checkboxes": return Array.isArray(element["options"]) && element["options"].length > 1 ? checkboxGroupFrom(element, warnings) : checkboxFrom(element);
182
214
  case "radio_buttons": return radioGroupFrom(element, warnings);
183
215
  }
184
216
  warn(warnings, "dropped", isRecord(element) && typeof element["type"] === "string" ? element["type"] : "unknown", "Unknown action element type was dropped.");
@@ -188,6 +220,13 @@ const actionsFrom = (block, warnings) => {
188
220
  };
189
221
  const inputFrom = (block, warnings) => {
190
222
  const element = block["element"];
223
+ if (isRecord(element) && (element["type"] === "timepicker" || element["type"] === "datetimepicker")) {
224
+ const picker = actionElementFrom(element, warnings);
225
+ return picker ? [{
226
+ ...picker,
227
+ label: textOf(block["label"])
228
+ }] : [];
229
+ }
191
230
  if (!isRecord(element) || element["type"] !== "plain_text_input") {
192
231
  warn(warnings, "dropped", "Input", "Unknown input element type was dropped.");
193
232
  return [];
@@ -198,6 +237,7 @@ const inputFrom = (block, warnings) => {
198
237
  label: textOf(block["label"]),
199
238
  ...isRecord(placeholder) ? { placeholder: textOf(placeholder) } : {},
200
239
  ...element["multiline"] === true ? { multiline: true } : {},
240
+ ...typeof element["initial_value"] === "string" ? { defaultValue: element["initial_value"] } : {},
201
241
  $action: decodeAction(element["action_id"], void 0)
202
242
  }];
203
243
  };