@assistant-ui/react-generative-ui 0.0.20 → 0.0.22

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 (189) 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 +369 -87
  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 +2 -0
  43. package/dist/constants.d.ts.map +1 -1
  44. package/dist/constants.js +3 -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 +58 -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 +20 -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 +187 -19
  73. package/dist/slack/toSlackBlocks.js.map +1 -1
  74. package/dist/slack/types.d.ts +4 -0
  75. package/dist/slack/types.d.ts.map +1 -1
  76. package/dist/teams/constants.d.ts +1 -1
  77. package/dist/teams/constants.d.ts.map +1 -1
  78. package/dist/teams/constants.js +1 -1
  79. package/dist/teams/constants.js.map +1 -1
  80. package/dist/teams/decodeSubmitData.d.ts +4 -0
  81. package/dist/teams/decodeSubmitData.d.ts.map +1 -1
  82. package/dist/teams/decodeSubmitData.js +6 -1
  83. package/dist/teams/decodeSubmitData.js.map +1 -1
  84. package/dist/teams/toAdaptiveCard.d.ts +5 -4
  85. package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
  86. package/dist/teams/toAdaptiveCard.js +56 -17
  87. package/dist/teams/toAdaptiveCard.js.map +1 -1
  88. package/dist/teams/types.d.ts +4 -1
  89. package/dist/teams/types.d.ts.map +1 -1
  90. package/dist/vocabulary/alert.d.ts +9 -2
  91. package/dist/vocabulary/alert.d.ts.map +1 -1
  92. package/dist/vocabulary/alert.js +116 -19
  93. package/dist/vocabulary/alert.js.map +1 -1
  94. package/dist/vocabulary/collectFormValues.d.ts +2 -1
  95. package/dist/vocabulary/collectFormValues.d.ts.map +1 -1
  96. package/dist/vocabulary/collectFormValues.js +10 -3
  97. package/dist/vocabulary/collectFormValues.js.map +1 -1
  98. package/dist/vocabulary/data.d.ts +22 -5
  99. package/dist/vocabulary/data.d.ts.map +1 -1
  100. package/dist/vocabulary/data.js +278 -83
  101. package/dist/vocabulary/data.js.map +1 -1
  102. package/dist/vocabulary/dispatch.d.ts +3 -2
  103. package/dist/vocabulary/dispatch.d.ts.map +1 -1
  104. package/dist/vocabulary/dispatch.js +33 -4
  105. package/dist/vocabulary/dispatch.js.map +1 -1
  106. package/dist/vocabulary/fact.d.ts +8 -1
  107. package/dist/vocabulary/fact.d.ts.map +1 -1
  108. package/dist/vocabulary/fact.js +37 -11
  109. package/dist/vocabulary/fact.js.map +1 -1
  110. package/dist/vocabulary/form.d.ts.map +1 -1
  111. package/dist/vocabulary/form.js +3 -3
  112. package/dist/vocabulary/form.js.map +1 -1
  113. package/dist/vocabulary/formatValue.d.ts +7 -0
  114. package/dist/vocabulary/formatValue.d.ts.map +1 -0
  115. package/dist/vocabulary/formatValue.js +76 -0
  116. package/dist/vocabulary/formatValue.js.map +1 -0
  117. package/dist/vocabulary/icon.d.ts +1 -1
  118. package/dist/vocabulary/interactive.d.ts +137 -8
  119. package/dist/vocabulary/interactive.d.ts.map +1 -1
  120. package/dist/vocabulary/interactive.js +404 -103
  121. package/dist/vocabulary/interactive.js.map +1 -1
  122. package/dist/vocabulary/layout.d.ts.map +1 -1
  123. package/dist/vocabulary/layout.js +4 -4
  124. package/dist/vocabulary/layout.js.map +1 -1
  125. package/dist/vocabulary/list.js +2 -2
  126. package/dist/vocabulary/list.js.map +1 -1
  127. package/dist/vocabulary/text.d.ts +1 -1
  128. package/package.json +6 -3
  129. package/src/JSONGenerativeUI.client.tsx +84 -40
  130. package/src/JSONGenerativeUI.test.tsx +964 -68
  131. package/src/RadioGroupScope.server.test.tsx +24 -0
  132. package/src/RadioGroupScope.tsx +34 -0
  133. package/src/a2ui/BindingContext.ts +26 -0
  134. package/src/a2ui/PresentRenderer.tsx +83 -0
  135. package/src/a2ui/convert.test.ts +1982 -145
  136. package/src/a2ui/convert.ts +677 -137
  137. package/src/a2ui/dataModel.ts +69 -0
  138. package/src/a2ui/reducer.test.ts +35 -0
  139. package/src/a2ui/reducer.ts +5 -4
  140. package/src/a2ui/types.ts +9 -2
  141. package/src/a2ui/valueFunctions.test.ts +271 -0
  142. package/src/a2ui/valueFunctions.ts +523 -0
  143. package/src/actionRegistry.ts +1 -1
  144. package/src/answeredValues.test.ts +33 -0
  145. package/src/answeredValues.tsx +70 -0
  146. package/src/constants.ts +4 -0
  147. package/src/fieldReferences.test.ts +66 -0
  148. package/src/fieldReferences.ts +83 -0
  149. package/src/generativeUIToJSX.test.ts +18 -0
  150. package/src/generativeUIToJSX.ts +4 -6
  151. package/src/index.server.test.ts +106 -0
  152. package/src/ir.ts +1 -1
  153. package/src/renderGenerativeUI.test.tsx +14 -5
  154. package/src/renderGenerativeUI.tsx +30 -1
  155. package/src/slack/a2uiSlack.test.ts +198 -0
  156. package/src/slack/constants.ts +3 -0
  157. package/src/slack/decodeBlockAction.test.ts +98 -0
  158. package/src/slack/decodeBlockAction.ts +110 -17
  159. package/src/slack/fromSlackBlocks.test.ts +222 -3
  160. package/src/slack/fromSlackBlocks.ts +53 -2
  161. package/src/slack/toSlackBlocks.test.ts +682 -3
  162. package/src/slack/toSlackBlocks.ts +374 -23
  163. package/src/slack/types.ts +4 -0
  164. package/src/teams/constants.ts +1 -1
  165. package/src/teams/decodeSubmitData.test.ts +33 -0
  166. package/src/teams/decodeSubmitData.ts +9 -1
  167. package/src/teams/toAdaptiveCard.test.ts +219 -0
  168. package/src/teams/toAdaptiveCard.ts +93 -19
  169. package/src/teams/types.ts +4 -1
  170. package/src/vocabulary/alert.test.tsx +288 -7
  171. package/src/vocabulary/alert.tsx +168 -26
  172. package/src/vocabulary/collectFormValues.test.ts +28 -1
  173. package/src/vocabulary/collectFormValues.ts +25 -5
  174. package/src/vocabulary/data.test.tsx +248 -6
  175. package/src/vocabulary/data.tsx +398 -100
  176. package/src/vocabulary/dispatch.ts +63 -2
  177. package/src/vocabulary/fact.test.tsx +67 -0
  178. package/src/vocabulary/fact.tsx +40 -9
  179. package/src/vocabulary/form.tsx +7 -2
  180. package/src/vocabulary/formatValue.test.ts +72 -0
  181. package/src/vocabulary/formatValue.ts +127 -0
  182. package/src/vocabulary/index.test.ts +2 -0
  183. package/src/vocabulary/interactive.dispatch.test.tsx +167 -79
  184. package/src/vocabulary/interactive.dom.test.tsx +1699 -0
  185. package/src/vocabulary/interactive.test.tsx +60 -4
  186. package/src/vocabulary/interactive.tsx +770 -151
  187. package/src/vocabulary/layout.test.tsx +6 -4
  188. package/src/vocabulary/layout.tsx +13 -2
  189. package/src/vocabulary/list.tsx +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"fromSlackBlocks.js","names":[],"sources":["../../src/slack/fromSlackBlocks.ts"],"sourcesContent":["import { copyBounded } from \"../convert/copyBounded\";\nimport type { Action, UIElement } from \"../ir\";\nimport {\n ACTIONS_ELEMENT_CAP,\n CARD_ACTIONS_CAP,\n CAROUSEL_CARD_CAP,\n CONTEXT_ELEMENT_CAP,\n DATA_TABLE_COLUMN_CAP,\n DATA_TABLE_ROW_CAP,\n FACT_FIELD_CAP,\n INBOUND_BLOCK_CAP,\n RADIO_OPTION_CAP,\n SELECT_OPTION_CAP,\n} from \"./constants\";\nimport type { FromSlackBlocksResult, SlackConversionWarning } from \"./types\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isDefined = <T>(value: T | undefined): value is T => value !== undefined;\n\nconst asString = (value: unknown): string =>\n typeof value === \"string\" ? value : \"\";\n\nconst textOf = (value: unknown): string =>\n isRecord(value) && typeof value[\"text\"] === \"string\" ? value[\"text\"] : \"\";\n\nconst warn = (\n warnings: SlackConversionWarning[],\n code: SlackConversionWarning[\"code\"],\n component: string,\n detail: string,\n) => {\n warnings.push({ code, component, detail });\n};\n\n/**\n * The shared bounded-iteration primitive: copies `value` to at most `cap`\n * entries without ever reading past that many indices, so a hostile array\n * (sparse or proxied with a fabricated `length`) cannot stall the event loop.\n */\nconst clampArray = (\n value: unknown,\n cap: number,\n): { readonly items: unknown[]; readonly truncated: boolean } => {\n if (!Array.isArray(value)) return { items: [], truncated: false };\n return copyBounded(value, cap);\n};\n\nconst boundedArray = (\n value: unknown,\n cap: number,\n warnings: SlackConversionWarning[],\n component: string,\n detail: string,\n): unknown[] => {\n const { items, truncated } = clampArray(value, cap);\n if (truncated) warn(warnings, \"clamped\", component, detail);\n return items;\n};\n\nconst parseValuePayload = (value: unknown): Record<string, unknown> => {\n if (typeof value !== \"string\") return {};\n try {\n const parsed: unknown = JSON.parse(value);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n};\n\nconst decodeAction = (actionId: unknown, value: unknown): Action => ({\n ...parseValuePayload(value),\n type: typeof actionId === \"string\" ? actionId : \"action\",\n});\n\nconst headerFrom = (block: Record<string, unknown>): UIElement => ({\n $type: \"Header\",\n text: textOf(block[\"text\"]),\n});\n\nconst imageFrom = (block: Record<string, unknown>): UIElement => ({\n $type: \"Image\",\n src: asString(block[\"image_url\"]),\n alt: asString(block[\"alt_text\"]),\n});\n\nconst markdownFrom = (block: Record<string, unknown>): UIElement => ({\n $type: \"Markdown\",\n value: asString(block[\"text\"]),\n});\n\nconst FACT_FIELD_PATTERN = /^\\*([\\s\\S]*?)\\*\\n([\\s\\S]*)$/;\n\nconst factFrom = (\n field: unknown,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const text = textOf(field);\n const match = FACT_FIELD_PATTERN.exec(text);\n if (match) {\n const [, label, value] = match;\n return { $type: \"Fact\", label, value };\n }\n warn(\n warnings,\n \"fallback\",\n \"Fact\",\n \"A section field that did not match the label/value format was kept as a value-only fact.\",\n );\n return { $type: \"Fact\", label: \"\", value: text };\n};\n\nconst listViewItemFrom = (\n text: string,\n button: Record<string, unknown>,\n): UIElement => ({\n $type: \"ListViewItem\",\n $action: decodeAction(button[\"action_id\"], button[\"value\"]),\n children: { $type: \"Text\", value: text },\n});\n\n/**\n * Recognizes a section block that stands for a `ListViewItem`: mrkdwn text\n * paired with a button accessory, and no `fields` (a fields section is\n * always a Facts section, never a list row).\n */\nconst listViewItemFromSection = (block: unknown): UIElement | undefined => {\n if (!isRecord(block) || block[\"type\"] !== \"section\") return undefined;\n if (Array.isArray(block[\"fields\"])) return undefined;\n const accessory = block[\"accessory\"];\n if (!isRecord(accessory) || accessory[\"type\"] !== \"button\") {\n return undefined;\n }\n return listViewItemFrom(textOf(block[\"text\"]), accessory);\n};\n\nconst isDividerBlock = (block: unknown): boolean =>\n isRecord(block) && block[\"type\"] === \"divider\";\n\nconst sectionFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement[] => {\n const fields = block[\"fields\"];\n if (Array.isArray(fields)) {\n return boundedArray(\n fields,\n FACT_FIELD_CAP,\n warnings,\n \"Fact\",\n `fields were clamped to ${FACT_FIELD_CAP} entries.`,\n ).map((field) => factFrom(field, warnings));\n }\n return [{ $type: \"Text\", value: textOf(block[\"text\"]) }];\n};\n\nconst captionFrom = (element: unknown): UIElement => ({\n $type: \"Caption\",\n value: textOf(element),\n});\n\nconst contextFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const elements = boundedArray(\n block[\"elements\"],\n CONTEXT_ELEMENT_CAP,\n warnings,\n \"Row\",\n `elements were clamped to ${CONTEXT_ELEMENT_CAP} entries.`,\n );\n if (elements.length > 1) {\n return { $type: \"Row\", children: elements.map(captionFrom) };\n }\n return {\n $type: \"Caption\",\n value: elements.length === 1 ? textOf(elements[0]) : \"\",\n };\n};\n\nconst optionFrom = (\n option: unknown,\n): { label: string; value: string } | undefined => {\n if (!isRecord(option) || typeof option[\"value\"] !== \"string\") {\n return undefined;\n }\n return { label: textOf(option[\"text\"]), value: option[\"value\"] };\n};\n\nconst buttonFrom = (element: Record<string, unknown>): UIElement => {\n const style = element[\"style\"];\n return {\n $type: \"Button\",\n label: textOf(element[\"text\"]),\n ...(style === \"primary\" || style === \"danger\"\n ? { buttonStyle: style }\n : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst selectFrom = (\n element: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const options = boundedArray(\n element[\"options\"],\n SELECT_OPTION_CAP,\n warnings,\n \"Select\",\n `options were clamped to ${SELECT_OPTION_CAP} entries.`,\n );\n const placeholder = element[\"placeholder\"];\n return {\n $type: \"Select\",\n options: options.map(optionFrom).filter(isDefined),\n ...(isRecord(placeholder) ? { placeholder: textOf(placeholder) } : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst datePickerFrom = (element: Record<string, unknown>): UIElement => ({\n $type: \"DatePicker\",\n ...(typeof element[\"initial_date\"] === \"string\"\n ? { value: element[\"initial_date\"] }\n : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n});\n\nconst checkboxFrom = (element: Record<string, unknown>): UIElement => {\n const options = Array.isArray(element[\"options\"]) ? element[\"options\"] : [];\n const first = options[0];\n const initialOptions = Array.isArray(element[\"initial_options\"])\n ? element[\"initial_options\"]\n : [];\n const firstValue =\n isRecord(first) && typeof first[\"value\"] === \"string\" ? first[\"value\"] : \"\";\n const firstChecked = copyBounded(\n initialOptions,\n SELECT_OPTION_CAP,\n ).items.some((option) => isRecord(option) && option[\"value\"] === firstValue);\n return {\n $type: \"Checkbox\",\n label: isRecord(first) ? textOf(first[\"text\"]) : \"\",\n name: firstValue,\n ...(firstChecked ? { defaultChecked: true } : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst radioGroupFrom = (\n element: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const options = boundedArray(\n element[\"options\"],\n RADIO_OPTION_CAP,\n warnings,\n \"RadioGroup\",\n `options were clamped to ${RADIO_OPTION_CAP} entries.`,\n );\n const initialOption = element[\"initial_option\"];\n const initialValue =\n isRecord(initialOption) && typeof initialOption[\"value\"] === \"string\"\n ? initialOption[\"value\"]\n : undefined;\n return {\n $type: \"RadioGroup\",\n options: options.map(optionFrom).filter(isDefined),\n ...(initialValue !== undefined ? { defaultValue: initialValue } : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst actionElementFrom = (\n element: unknown,\n warnings: SlackConversionWarning[],\n): UIElement | undefined => {\n if (isRecord(element)) {\n switch (element[\"type\"]) {\n case \"button\":\n return buttonFrom(element);\n case \"static_select\":\n return selectFrom(element, warnings);\n case \"datepicker\":\n return datePickerFrom(element);\n case \"checkboxes\":\n return checkboxFrom(element);\n case \"radio_buttons\":\n return radioGroupFrom(element, warnings);\n }\n }\n warn(\n warnings,\n \"dropped\",\n isRecord(element) && typeof element[\"type\"] === \"string\"\n ? element[\"type\"]\n : \"unknown\",\n \"Unknown action element type was dropped.\",\n );\n return undefined;\n};\n\nconst actionsFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement[] => {\n const elements = boundedArray(\n block[\"elements\"],\n ACTIONS_ELEMENT_CAP,\n warnings,\n \"Actions\",\n `elements were clamped to ${ACTIONS_ELEMENT_CAP} entries.`,\n );\n return elements\n .map((element) => actionElementFrom(element, warnings))\n .filter(isDefined);\n};\n\nconst inputFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement[] => {\n const element = block[\"element\"];\n if (!isRecord(element) || element[\"type\"] !== \"plain_text_input\") {\n warn(\n warnings,\n \"dropped\",\n \"Input\",\n \"Unknown input element type was dropped.\",\n );\n return [];\n }\n const placeholder = element[\"placeholder\"];\n return [\n {\n $type: \"Input\",\n label: textOf(block[\"label\"]),\n ...(isRecord(placeholder) ? { placeholder: textOf(placeholder) } : {}),\n ...(element[\"multiline\"] === true ? { multiline: true } : {}),\n $action: decodeAction(element[\"action_id\"], undefined),\n },\n ];\n};\n\ntype CardFooterButton = { readonly label: string; readonly $action: Action };\n\nconst cardFooterButtonFrom = (button: unknown): CardFooterButton | undefined =>\n isRecord(button)\n ? {\n label: textOf(button[\"text\"]),\n $action: decodeAction(button[\"action_id\"], button[\"value\"]),\n }\n : undefined;\n\n/**\n * Assigns a card's footer actions by style rather than position: the\n * primary-styled button is the confirm action and a non-primary button is\n * cancel, since that is the pairing {@link toSlackBlocks} itself emits.\n * Position is only a fallback for input that does not carry that pairing\n * (zero or multiple primary-styled buttons among two or more actions).\n */\nconst assignCardFooterActions = (\n actions: unknown[],\n warnings: SlackConversionWarning[],\n): {\n readonly confirm: CardFooterButton | undefined;\n readonly cancel: CardFooterButton | undefined;\n readonly leftover: unknown[];\n} => {\n const isPrimaryButton = (action: unknown): boolean =>\n isRecord(action) && action[\"style\"] === \"primary\";\n const primaryButtons = actions.filter(isPrimaryButton);\n\n let confirmSource: unknown = undefined;\n let cancelSource: unknown = undefined;\n let leftover: unknown[] = [];\n\n if (primaryButtons.length === 1) {\n const [confirmButton] = primaryButtons;\n const [cancelButton, ...rest] = actions.filter(\n (action) => !isPrimaryButton(action),\n );\n confirmSource = confirmButton;\n cancelSource = cancelButton;\n leftover = rest;\n } else if (primaryButtons.length === 0 && actions.length <= 1) {\n const [onlyButton] = actions;\n cancelSource = onlyButton;\n } else if (actions.length > 0) {\n const [first, second, ...rest] = actions;\n confirmSource = first;\n cancelSource = second;\n leftover = rest;\n warn(\n warnings,\n \"fallback\",\n \"Card\",\n \"Confirm and cancel buttons could not be assigned by style and fell back to position.\",\n );\n }\n\n return {\n confirm: cardFooterButtonFrom(confirmSource),\n cancel: cardFooterButtonFrom(cancelSource),\n leftover,\n };\n};\n\nconst cardFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const titleText = textOf(block[\"title\"]);\n const children: UIElement[] = [];\n const heroImage = block[\"hero_image\"];\n if (isRecord(heroImage)) {\n children.push({\n $type: \"Image\",\n src: asString(heroImage[\"image_url\"]),\n alt: asString(heroImage[\"alt_text\"]),\n });\n }\n const body = block[\"body\"];\n if (isRecord(body)) {\n children.push({ $type: \"Text\", value: textOf(body) });\n }\n const subtext = block[\"subtext\"];\n if (isRecord(subtext)) {\n children.push({ $type: \"Caption\", value: textOf(subtext) });\n }\n const actions = boundedArray(\n block[\"actions\"],\n CARD_ACTIONS_CAP,\n warnings,\n \"Card\",\n `actions were clamped to ${CARD_ACTIONS_CAP} entries.`,\n );\n const { confirm, cancel, leftover } = assignCardFooterActions(\n actions,\n warnings,\n );\n if (leftover.length > 0) {\n for (const extra of leftover) {\n if (isRecord(extra)) children.push(buttonFrom(extra));\n }\n warn(\n warnings,\n \"fallback\",\n \"Card\",\n \"Card actions beyond confirm and cancel were appended as Button children.\",\n );\n }\n return {\n $type: \"Card\",\n ...(titleText ? { title: titleText } : {}),\n ...(children.length > 0 ? { children } : {}),\n ...(confirm !== undefined ? { confirm } : {}),\n ...(cancel !== undefined ? { cancel } : {}),\n };\n};\n\nconst carouselFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const elements = boundedArray(\n block[\"elements\"],\n CAROUSEL_CARD_CAP,\n warnings,\n \"Carousel\",\n `cards were clamped to ${CAROUSEL_CARD_CAP} entries.`,\n );\n const cards = elements\n .filter(isRecord)\n .map((card) => cardFrom(card, warnings));\n return { $type: \"Carousel\", children: cards };\n};\n\nconst ALERT_LEVEL_TONES: Record<string, string> = {\n error: \"danger\",\n success: \"success\",\n warning: \"warning\",\n info: \"info\",\n};\n\nconst alertFrom = (block: Record<string, unknown>): UIElement => {\n const level = block[\"level\"];\n const tone = typeof level === \"string\" ? ALERT_LEVEL_TONES[level] : undefined;\n return {\n $type: \"Alert\",\n description: textOf(block[\"text\"]),\n ...(tone !== undefined ? { tone } : {}),\n };\n};\n\nconst dataTableCellValue = (cell: unknown): string | number => {\n if (!isRecord(cell)) return \"\";\n const text = asString(cell[\"text\"]);\n if (cell[\"type\"] === \"raw_number\") {\n const parsed = Number(text);\n if (Number.isFinite(parsed)) return parsed;\n }\n return text;\n};\n\nconst dataTableFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const { items: rows, truncated: rowsTruncated } = clampArray(\n block[\"rows\"],\n DATA_TABLE_ROW_CAP + 1,\n );\n if (rowsTruncated) {\n warn(\n warnings,\n \"clamped\",\n \"Table\",\n `rows were clamped to ${DATA_TABLE_ROW_CAP} entries.`,\n );\n }\n const [headerRow, ...dataRows] = rows;\n const { items: headerCells, truncated: headerTruncated } = clampArray(\n headerRow,\n DATA_TABLE_COLUMN_CAP,\n );\n let columnsTruncated = headerTruncated;\n const normalizedRows = dataRows.map((row) => {\n const { items: cells, truncated } = clampArray(row, DATA_TABLE_COLUMN_CAP);\n if (truncated) columnsTruncated = true;\n return cells;\n });\n if (columnsTruncated) {\n warn(\n warnings,\n \"clamped\",\n \"Table\",\n `columns were clamped to ${DATA_TABLE_COLUMN_CAP} entries.`,\n );\n }\n const headerAllEmpty = headerCells.every(\n (cell) => !isRecord(cell) || asString(cell[\"text\"]) === \"\",\n );\n const columns = headerCells.map((cell) => ({\n label: isRecord(cell) ? asString(cell[\"text\"]) : \"\",\n }));\n return {\n $type: \"Table\",\n ...(headerAllEmpty ? {} : { columns }),\n rows: normalizedRows.map((row) => row.map(dataTableCellValue)),\n };\n};\n\nfunction convertBlock(\n block: unknown,\n warnings: SlackConversionWarning[],\n): UIElement[] {\n if (isRecord(block)) {\n switch (block[\"type\"]) {\n case \"header\":\n return [headerFrom(block)];\n case \"section\":\n return sectionFrom(block, warnings);\n case \"context\":\n return [contextFrom(block, warnings)];\n case \"image\":\n return [imageFrom(block)];\n case \"divider\":\n return [{ $type: \"Divider\" }];\n case \"actions\":\n return actionsFrom(block, warnings);\n case \"input\":\n return inputFrom(block, warnings);\n case \"markdown\":\n return [markdownFrom(block)];\n case \"card\":\n return [cardFrom(block, warnings)];\n case \"carousel\":\n return [carouselFrom(block, warnings)];\n case \"alert\":\n return [alertFrom(block)];\n case \"data_table\":\n return [dataTableFrom(block, warnings)];\n }\n }\n warn(\n warnings,\n \"dropped\",\n isRecord(block) && typeof block[\"type\"] === \"string\"\n ? block[\"type\"]\n : \"unknown\",\n \"Unknown block type was dropped.\",\n );\n return [];\n}\n\nconst extractBlockList = (input: unknown): unknown[] | undefined => {\n if (Array.isArray(input)) return input;\n if (isRecord(input) && Array.isArray(input[\"blocks\"])) return input[\"blocks\"];\n return undefined;\n};\n\n/**\n * Walks the (already bounded) block list, folding a run of one or more\n * consecutive `ListViewItem`-shaped section blocks, optionally separated by\n * single divider blocks, into one `ListView` container instead of emitting\n * root-level `ListViewItem` nodes or the interleaved dividers. A divider that\n * is not directly between two such items is left for {@link convertBlock} to\n * decode as a literal `Divider`.\n */\nfunction convertBlockList(\n blocks: unknown[],\n warnings: SlackConversionWarning[],\n): UIElement[] {\n const nodes: UIElement[] = [];\n let index = 0;\n while (index < blocks.length) {\n const firstItem = listViewItemFromSection(blocks[index]);\n if (firstItem === undefined) {\n nodes.push(...convertBlock(blocks[index], warnings));\n index += 1;\n continue;\n }\n const items: UIElement[] = [firstItem];\n let cursor = index + 1;\n while (true) {\n if (isDividerBlock(blocks[cursor])) {\n const candidate = listViewItemFromSection(blocks[cursor + 1]);\n if (candidate === undefined) break;\n items.push(candidate);\n cursor += 2;\n continue;\n }\n const candidate = listViewItemFromSection(blocks[cursor]);\n if (candidate === undefined) break;\n items.push(candidate);\n cursor += 1;\n }\n nodes.push({ $type: \"ListView\", children: items });\n index = cursor;\n }\n return nodes;\n}\n\n/**\n * Converts Slack Block Kit JSON back into a generative-ui IR tree, inverting\n * {@link toSlackBlocks}. Accepts a blocks array or a `{ blocks }` wrapper;\n * malformed input resolves to an empty node list plus a warning, and an\n * unrecognized block or action element is skipped with a \"dropped\" warning.\n * Every array read along the way (blocks, fields, options, elements, cards,\n * rows, cells) is bounded to its site's cap before it is mapped over, so a\n * hostile array (sparse or proxied with a fabricated `length`) is clamped\n * with a \"clamped\" warning instead of stalling the event loop. Never throws.\n *\n * The Slack shape is lossier than the IR, so some information cannot be\n * recovered: a context block's elements always decode as `Caption` (the\n * original `Badge` vs `Caption` distinction does not survive the round trip),\n * a button's `buttonStyle` is only recovered for the `primary`/`danger` Slack\n * styles, a native alert block's `title` and `description` merge into a\n * single `description` string, and a card's layout collapses to its `title`,\n * a flat `children` list (image, then text, then caption, in that order),\n * and `confirm`/`cancel` footer buttons. A section field's label and value\n * are split on the first `*`+newline delimiter, so a label or value that\n * itself contains that exact delimiter cannot be split back unambiguously.\n * A data-table cell that Slack carries as `raw_text` always decodes as a\n * string, even when its text is the stringified form of a boolean, since the\n * Slack shape does not distinguish a boolean from ordinary text.\n */\nexport function fromSlackBlocks(blocks: unknown): FromSlackBlocksResult {\n const warnings: SlackConversionWarning[] = [];\n try {\n const list = extractBlockList(blocks);\n if (list === undefined) {\n return {\n nodes: [],\n warnings: [\n {\n code: \"dropped\",\n component: \"Root\",\n detail: \"Input could not be converted.\",\n },\n ],\n };\n }\n const boundedList = boundedArray(\n list,\n INBOUND_BLOCK_CAP,\n warnings,\n \"Root\",\n `blocks were clamped to ${INBOUND_BLOCK_CAP} entries.`,\n );\n const nodes = convertBlockList(boundedList, warnings);\n return { nodes, warnings };\n } catch {\n return {\n nodes: [],\n warnings: [\n {\n code: \"dropped\",\n component: \"Root\",\n detail: \"Input could not be converted.\",\n },\n ],\n };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"fromSlackBlocks.js","names":[],"sources":["../../src/slack/fromSlackBlocks.ts"],"sourcesContent":["import { copyBounded } from \"../convert/copyBounded\";\nimport type { Action, UIElement } from \"../ir\";\nimport {\n ACTIONS_ELEMENT_CAP,\n CARD_ACTIONS_CAP,\n CAROUSEL_CARD_CAP,\n CHECKBOX_OPTION_CAP,\n CONTEXT_ELEMENT_CAP,\n DATA_TABLE_COLUMN_CAP,\n DATA_TABLE_ROW_CAP,\n FACT_FIELD_CAP,\n INBOUND_BLOCK_CAP,\n RADIO_OPTION_CAP,\n SELECT_OPTION_CAP,\n} from \"./constants\";\nimport type { FromSlackBlocksResult, SlackConversionWarning } from \"./types\";\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nconst isDefined = <T>(value: T | undefined): value is T => value !== undefined;\n\nconst asString = (value: unknown): string =>\n typeof value === \"string\" ? value : \"\";\n\nconst textOf = (value: unknown): string =>\n isRecord(value) && typeof value[\"text\"] === \"string\" ? value[\"text\"] : \"\";\n\nconst warn = (\n warnings: SlackConversionWarning[],\n code: SlackConversionWarning[\"code\"],\n component: string,\n detail: string,\n) => {\n warnings.push({ code, component, detail });\n};\n\n/**\n * The shared bounded-iteration primitive: copies `value` to at most `cap`\n * entries without ever reading past that many indices, so a hostile array\n * (sparse or proxied with a fabricated `length`) cannot stall the event loop.\n */\nconst clampArray = (\n value: unknown,\n cap: number,\n): { readonly items: unknown[]; readonly truncated: boolean } => {\n if (!Array.isArray(value)) return { items: [], truncated: false };\n return copyBounded(value, cap);\n};\n\nconst boundedArray = (\n value: unknown,\n cap: number,\n warnings: SlackConversionWarning[],\n component: string,\n detail: string,\n): unknown[] => {\n const { items, truncated } = clampArray(value, cap);\n if (truncated) warn(warnings, \"clamped\", component, detail);\n return items;\n};\n\nconst parseValuePayload = (value: unknown): Record<string, unknown> => {\n if (typeof value !== \"string\") return {};\n try {\n const parsed: unknown = JSON.parse(value);\n return isRecord(parsed) ? parsed : {};\n } catch {\n return {};\n }\n};\n\nconst decodeAction = (actionId: unknown, value: unknown): Action => ({\n ...parseValuePayload(value),\n type: typeof actionId === \"string\" ? actionId : \"action\",\n});\n\nconst headerFrom = (block: Record<string, unknown>): UIElement => ({\n $type: \"Header\",\n text: textOf(block[\"text\"]),\n});\n\nconst imageFrom = (block: Record<string, unknown>): UIElement => ({\n $type: \"Image\",\n src: asString(block[\"image_url\"]),\n alt: asString(block[\"alt_text\"]),\n});\n\nconst markdownFrom = (block: Record<string, unknown>): UIElement => ({\n $type: \"Markdown\",\n value: asString(block[\"text\"]),\n});\n\nconst FACT_FIELD_PATTERN = /^\\*([\\s\\S]*?)\\*\\n([\\s\\S]*)$/;\n\nconst factFrom = (\n field: unknown,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const text = textOf(field);\n const match = FACT_FIELD_PATTERN.exec(text);\n if (match) {\n const [, label, value] = match;\n return { $type: \"Fact\", label, value };\n }\n warn(\n warnings,\n \"fallback\",\n \"Fact\",\n \"A section field that did not match the label/value format was kept as a value-only fact.\",\n );\n return { $type: \"Fact\", label: \"\", value: text };\n};\n\nconst listViewItemFrom = (\n text: string,\n button: Record<string, unknown>,\n): UIElement => ({\n $type: \"ListViewItem\",\n $action: decodeAction(button[\"action_id\"], button[\"value\"]),\n children: { $type: \"Text\", value: text },\n});\n\n/**\n * Recognizes a section block that stands for a `ListViewItem`: mrkdwn text\n * paired with a button accessory, and no `fields` (a fields section is\n * always a Facts section, never a list row).\n */\nconst listViewItemFromSection = (block: unknown): UIElement | undefined => {\n if (!isRecord(block) || block[\"type\"] !== \"section\") return undefined;\n if (Array.isArray(block[\"fields\"])) return undefined;\n const accessory = block[\"accessory\"];\n if (!isRecord(accessory) || accessory[\"type\"] !== \"button\") {\n return undefined;\n }\n return listViewItemFrom(textOf(block[\"text\"]), accessory);\n};\n\nconst isDividerBlock = (block: unknown): boolean =>\n isRecord(block) && block[\"type\"] === \"divider\";\n\nconst sectionFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement[] => {\n const fields = block[\"fields\"];\n if (Array.isArray(fields)) {\n return boundedArray(\n fields,\n FACT_FIELD_CAP,\n warnings,\n \"Fact\",\n `fields were clamped to ${FACT_FIELD_CAP} entries.`,\n ).map((field) => factFrom(field, warnings));\n }\n return [{ $type: \"Text\", value: textOf(block[\"text\"]) }];\n};\n\nconst captionFrom = (element: unknown): UIElement => ({\n $type: \"Caption\",\n value: textOf(element),\n});\n\nconst contextFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const elements = boundedArray(\n block[\"elements\"],\n CONTEXT_ELEMENT_CAP,\n warnings,\n \"Row\",\n `elements were clamped to ${CONTEXT_ELEMENT_CAP} entries.`,\n );\n if (elements.length > 1) {\n return { $type: \"Row\", children: elements.map(captionFrom) };\n }\n return {\n $type: \"Caption\",\n value: elements.length === 1 ? textOf(elements[0]) : \"\",\n };\n};\n\nconst optionFrom = (\n option: unknown,\n): { label: string; value: string } | undefined => {\n if (!isRecord(option) || typeof option[\"value\"] !== \"string\") {\n return undefined;\n }\n return { label: textOf(option[\"text\"]), value: option[\"value\"] };\n};\n\nconst buttonFrom = (element: Record<string, unknown>): UIElement => {\n const style = element[\"style\"];\n return {\n $type: \"Button\",\n label: textOf(element[\"text\"]),\n ...(style === \"primary\" || style === \"danger\"\n ? { buttonStyle: style }\n : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst selectFrom = (\n element: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const options = boundedArray(\n element[\"options\"],\n SELECT_OPTION_CAP,\n warnings,\n \"Select\",\n `options were clamped to ${SELECT_OPTION_CAP} entries.`,\n );\n const convertedOptions = options.map(optionFrom).filter(isDefined);\n const initialOption = element[\"initial_option\"];\n const defaultValue =\n isRecord(initialOption) && typeof initialOption[\"value\"] === \"string\"\n ? initialOption[\"value\"]\n : undefined;\n const placeholder = element[\"placeholder\"];\n return {\n $type: \"Select\",\n options: convertedOptions,\n ...(defaultValue !== undefined &&\n convertedOptions.some((option) => option.value === defaultValue)\n ? { defaultValue }\n : {}),\n ...(isRecord(placeholder) ? { placeholder: textOf(placeholder) } : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst datePickerFrom = (element: Record<string, unknown>): UIElement => ({\n $type: \"DatePicker\",\n ...(typeof element[\"initial_date\"] === \"string\"\n ? { value: element[\"initial_date\"] }\n : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n});\n\nconst checkboxFrom = (element: Record<string, unknown>): UIElement => {\n const options = Array.isArray(element[\"options\"]) ? element[\"options\"] : [];\n const first = options[0];\n const initialOptions = Array.isArray(element[\"initial_options\"])\n ? element[\"initial_options\"]\n : [];\n const firstValue =\n isRecord(first) && typeof first[\"value\"] === \"string\" ? first[\"value\"] : \"\";\n const firstChecked = copyBounded(\n initialOptions,\n SELECT_OPTION_CAP,\n ).items.some((option) => isRecord(option) && option[\"value\"] === firstValue);\n return {\n $type: \"Checkbox\",\n label: isRecord(first) ? textOf(first[\"text\"]) : \"\",\n name: firstValue,\n ...(firstChecked ? { defaultChecked: true } : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst checkboxGroupFrom = (\n element: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const options = boundedArray(\n element[\"options\"],\n CHECKBOX_OPTION_CAP,\n warnings,\n \"CheckboxGroup\",\n `options were clamped to ${CHECKBOX_OPTION_CAP} entries.`,\n )\n .map(optionFrom)\n .filter(isDefined);\n const defaultValue = clampArray(\n element[\"initial_options\"],\n CHECKBOX_OPTION_CAP,\n ).items.flatMap((initialOption) => {\n const value =\n isRecord(initialOption) && typeof initialOption[\"value\"] === \"string\"\n ? initialOption[\"value\"]\n : undefined;\n return value !== undefined &&\n options.some((option) => option.value === value)\n ? [value]\n : [];\n });\n return {\n $type: \"CheckboxGroup\",\n options,\n ...(defaultValue.length > 0 ? { defaultValue } : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst radioGroupFrom = (\n element: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const options = boundedArray(\n element[\"options\"],\n RADIO_OPTION_CAP,\n warnings,\n \"RadioGroup\",\n `options were clamped to ${RADIO_OPTION_CAP} entries.`,\n );\n const initialOption = element[\"initial_option\"];\n const initialValue =\n isRecord(initialOption) && typeof initialOption[\"value\"] === \"string\"\n ? initialOption[\"value\"]\n : undefined;\n return {\n $type: \"RadioGroup\",\n options: options.map(optionFrom).filter(isDefined),\n ...(initialValue !== undefined ? { defaultValue: initialValue } : {}),\n $action: decodeAction(element[\"action_id\"], element[\"value\"]),\n };\n};\n\nconst actionElementFrom = (\n element: unknown,\n warnings: SlackConversionWarning[],\n): UIElement | undefined => {\n if (isRecord(element)) {\n switch (element[\"type\"]) {\n case \"button\":\n return buttonFrom(element);\n case \"static_select\":\n return selectFrom(element, warnings);\n case \"datepicker\":\n return datePickerFrom(element);\n case \"checkboxes\":\n return Array.isArray(element[\"options\"]) &&\n element[\"options\"].length > 1\n ? checkboxGroupFrom(element, warnings)\n : checkboxFrom(element);\n case \"radio_buttons\":\n return radioGroupFrom(element, warnings);\n }\n }\n warn(\n warnings,\n \"dropped\",\n isRecord(element) && typeof element[\"type\"] === \"string\"\n ? element[\"type\"]\n : \"unknown\",\n \"Unknown action element type was dropped.\",\n );\n return undefined;\n};\n\nconst actionsFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement[] => {\n const elements = boundedArray(\n block[\"elements\"],\n ACTIONS_ELEMENT_CAP,\n warnings,\n \"Actions\",\n `elements were clamped to ${ACTIONS_ELEMENT_CAP} entries.`,\n );\n return elements\n .map((element) => actionElementFrom(element, warnings))\n .filter(isDefined);\n};\n\nconst inputFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement[] => {\n const element = block[\"element\"];\n if (!isRecord(element) || element[\"type\"] !== \"plain_text_input\") {\n warn(\n warnings,\n \"dropped\",\n \"Input\",\n \"Unknown input element type was dropped.\",\n );\n return [];\n }\n const placeholder = element[\"placeholder\"];\n return [\n {\n $type: \"Input\",\n label: textOf(block[\"label\"]),\n ...(isRecord(placeholder) ? { placeholder: textOf(placeholder) } : {}),\n ...(element[\"multiline\"] === true ? { multiline: true } : {}),\n ...(typeof element[\"initial_value\"] === \"string\"\n ? { defaultValue: element[\"initial_value\"] }\n : {}),\n $action: decodeAction(element[\"action_id\"], undefined),\n },\n ];\n};\n\ntype CardFooterButton = { readonly label: string; readonly $action: Action };\n\nconst cardFooterButtonFrom = (button: unknown): CardFooterButton | undefined =>\n isRecord(button)\n ? {\n label: textOf(button[\"text\"]),\n $action: decodeAction(button[\"action_id\"], button[\"value\"]),\n }\n : undefined;\n\n/**\n * Assigns a card's footer actions by style rather than position: the\n * primary-styled button is the confirm action and a non-primary button is\n * cancel, since that is the pairing {@link toSlackBlocks} itself emits.\n * Position is only a fallback for input that does not carry that pairing\n * (zero or multiple primary-styled buttons among two or more actions).\n */\nconst assignCardFooterActions = (\n actions: unknown[],\n warnings: SlackConversionWarning[],\n): {\n readonly confirm: CardFooterButton | undefined;\n readonly cancel: CardFooterButton | undefined;\n readonly leftover: unknown[];\n} => {\n const isPrimaryButton = (action: unknown): boolean =>\n isRecord(action) && action[\"style\"] === \"primary\";\n const primaryButtons = actions.filter(isPrimaryButton);\n\n let confirmSource: unknown = undefined;\n let cancelSource: unknown = undefined;\n let leftover: unknown[] = [];\n\n if (primaryButtons.length === 1) {\n const [confirmButton] = primaryButtons;\n const [cancelButton, ...rest] = actions.filter(\n (action) => !isPrimaryButton(action),\n );\n confirmSource = confirmButton;\n cancelSource = cancelButton;\n leftover = rest;\n } else if (primaryButtons.length === 0 && actions.length <= 1) {\n const [onlyButton] = actions;\n cancelSource = onlyButton;\n } else if (actions.length > 0) {\n const [first, second, ...rest] = actions;\n confirmSource = first;\n cancelSource = second;\n leftover = rest;\n warn(\n warnings,\n \"fallback\",\n \"Card\",\n \"Confirm and cancel buttons could not be assigned by style and fell back to position.\",\n );\n }\n\n return {\n confirm: cardFooterButtonFrom(confirmSource),\n cancel: cardFooterButtonFrom(cancelSource),\n leftover,\n };\n};\n\nconst cardFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const titleText = textOf(block[\"title\"]);\n const children: UIElement[] = [];\n const heroImage = block[\"hero_image\"];\n if (isRecord(heroImage)) {\n children.push({\n $type: \"Image\",\n src: asString(heroImage[\"image_url\"]),\n alt: asString(heroImage[\"alt_text\"]),\n });\n }\n const body = block[\"body\"];\n if (isRecord(body)) {\n children.push({ $type: \"Text\", value: textOf(body) });\n }\n const subtext = block[\"subtext\"];\n if (isRecord(subtext)) {\n children.push({ $type: \"Caption\", value: textOf(subtext) });\n }\n const actions = boundedArray(\n block[\"actions\"],\n CARD_ACTIONS_CAP,\n warnings,\n \"Card\",\n `actions were clamped to ${CARD_ACTIONS_CAP} entries.`,\n );\n const { confirm, cancel, leftover } = assignCardFooterActions(\n actions,\n warnings,\n );\n if (leftover.length > 0) {\n for (const extra of leftover) {\n if (isRecord(extra)) children.push(buttonFrom(extra));\n }\n warn(\n warnings,\n \"fallback\",\n \"Card\",\n \"Card actions beyond confirm and cancel were appended as Button children.\",\n );\n }\n return {\n $type: \"Card\",\n ...(titleText ? { title: titleText } : {}),\n ...(children.length > 0 ? { children } : {}),\n ...(confirm !== undefined ? { confirm } : {}),\n ...(cancel !== undefined ? { cancel } : {}),\n };\n};\n\nconst carouselFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const elements = boundedArray(\n block[\"elements\"],\n CAROUSEL_CARD_CAP,\n warnings,\n \"Carousel\",\n `cards were clamped to ${CAROUSEL_CARD_CAP} entries.`,\n );\n const cards = elements\n .filter(isRecord)\n .map((card) => cardFrom(card, warnings));\n return { $type: \"Carousel\", children: cards };\n};\n\nconst ALERT_LEVEL_TONES: Record<string, string> = {\n error: \"danger\",\n success: \"success\",\n warning: \"warning\",\n info: \"info\",\n};\n\nconst alertFrom = (block: Record<string, unknown>): UIElement => {\n const level = block[\"level\"];\n const tone = typeof level === \"string\" ? ALERT_LEVEL_TONES[level] : undefined;\n return {\n $type: \"Alert\",\n description: textOf(block[\"text\"]),\n ...(tone !== undefined ? { tone } : {}),\n };\n};\n\nconst dataTableCellValue = (cell: unknown): string | number => {\n if (!isRecord(cell)) return \"\";\n const text = asString(cell[\"text\"]);\n if (cell[\"type\"] === \"raw_number\") {\n const parsed = Number(text);\n if (Number.isFinite(parsed)) return parsed;\n }\n return text;\n};\n\nconst dataTableFrom = (\n block: Record<string, unknown>,\n warnings: SlackConversionWarning[],\n): UIElement => {\n const { items: rows, truncated: rowsTruncated } = clampArray(\n block[\"rows\"],\n DATA_TABLE_ROW_CAP + 1,\n );\n if (rowsTruncated) {\n warn(\n warnings,\n \"clamped\",\n \"Table\",\n `rows were clamped to ${DATA_TABLE_ROW_CAP} entries.`,\n );\n }\n const [headerRow, ...dataRows] = rows;\n const { items: headerCells, truncated: headerTruncated } = clampArray(\n headerRow,\n DATA_TABLE_COLUMN_CAP,\n );\n let columnsTruncated = headerTruncated;\n const normalizedRows = dataRows.map((row) => {\n const { items: cells, truncated } = clampArray(row, DATA_TABLE_COLUMN_CAP);\n if (truncated) columnsTruncated = true;\n return cells;\n });\n if (columnsTruncated) {\n warn(\n warnings,\n \"clamped\",\n \"Table\",\n `columns were clamped to ${DATA_TABLE_COLUMN_CAP} entries.`,\n );\n }\n const headerAllEmpty = headerCells.every(\n (cell) => !isRecord(cell) || asString(cell[\"text\"]) === \"\",\n );\n const columns = headerCells.map((cell) => ({\n label: isRecord(cell) ? asString(cell[\"text\"]) : \"\",\n }));\n return {\n $type: \"Table\",\n ...(headerAllEmpty ? {} : { columns }),\n rows: normalizedRows.map((row) => row.map(dataTableCellValue)),\n };\n};\n\nfunction convertBlock(\n block: unknown,\n warnings: SlackConversionWarning[],\n): UIElement[] {\n if (isRecord(block)) {\n switch (block[\"type\"]) {\n case \"header\":\n return [headerFrom(block)];\n case \"section\":\n return sectionFrom(block, warnings);\n case \"context\":\n return [contextFrom(block, warnings)];\n case \"image\":\n return [imageFrom(block)];\n case \"divider\":\n return [{ $type: \"Divider\" }];\n case \"actions\":\n return actionsFrom(block, warnings);\n case \"input\":\n return inputFrom(block, warnings);\n case \"markdown\":\n return [markdownFrom(block)];\n case \"card\":\n return [cardFrom(block, warnings)];\n case \"carousel\":\n return [carouselFrom(block, warnings)];\n case \"alert\":\n return [alertFrom(block)];\n case \"data_table\":\n return [dataTableFrom(block, warnings)];\n }\n }\n warn(\n warnings,\n \"dropped\",\n isRecord(block) && typeof block[\"type\"] === \"string\"\n ? block[\"type\"]\n : \"unknown\",\n \"Unknown block type was dropped.\",\n );\n return [];\n}\n\nconst extractBlockList = (input: unknown): unknown[] | undefined => {\n if (Array.isArray(input)) return input;\n if (isRecord(input) && Array.isArray(input[\"blocks\"])) return input[\"blocks\"];\n return undefined;\n};\n\n/**\n * Walks the (already bounded) block list, folding a run of one or more\n * consecutive `ListViewItem`-shaped section blocks, optionally separated by\n * single divider blocks, into one `ListView` container instead of emitting\n * root-level `ListViewItem` nodes or the interleaved dividers. A divider that\n * is not directly between two such items is left for {@link convertBlock} to\n * decode as a literal `Divider`.\n */\nfunction convertBlockList(\n blocks: unknown[],\n warnings: SlackConversionWarning[],\n): UIElement[] {\n const nodes: UIElement[] = [];\n let index = 0;\n while (index < blocks.length) {\n const firstItem = listViewItemFromSection(blocks[index]);\n if (firstItem === undefined) {\n nodes.push(...convertBlock(blocks[index], warnings));\n index += 1;\n continue;\n }\n const items: UIElement[] = [firstItem];\n let cursor = index + 1;\n while (true) {\n if (isDividerBlock(blocks[cursor])) {\n const candidate = listViewItemFromSection(blocks[cursor + 1]);\n if (candidate === undefined) break;\n items.push(candidate);\n cursor += 2;\n continue;\n }\n const candidate = listViewItemFromSection(blocks[cursor]);\n if (candidate === undefined) break;\n items.push(candidate);\n cursor += 1;\n }\n nodes.push({ $type: \"ListView\", children: items });\n index = cursor;\n }\n return nodes;\n}\n\n/**\n * Converts Slack Block Kit JSON back into a generative-ui IR tree, inverting\n * {@link toSlackBlocks}. Accepts a blocks array or a `{ blocks }` wrapper;\n * malformed input resolves to an empty node list plus a warning, and an\n * unrecognized block or action element is skipped with a \"dropped\" warning.\n * Every array read along the way (blocks, fields, options, elements, cards,\n * rows, cells) is bounded to its site's cap before it is mapped over, so a\n * hostile array (sparse or proxied with a fabricated `length`) is clamped\n * with a \"clamped\" warning instead of stalling the event loop. Never throws.\n *\n * The Slack shape is lossier than the IR, so some information cannot be\n * recovered: a context block's elements always decode as `Caption` (the\n * original `Badge` vs `Caption` distinction does not survive the round trip),\n * a button's `buttonStyle` is only recovered for the `primary`/`danger` Slack\n * styles, a native alert block's `title` and `description` merge into a\n * single `description` string, and a card's layout collapses to its `title`,\n * a flat `children` list (image, then text, then caption, in that order),\n * and `confirm`/`cancel` footer buttons. A section field's label and value\n * are split on the first `*`+newline delimiter, so a label or value that\n * itself contains that exact delimiter cannot be split back unambiguously.\n * A data-table cell that Slack carries as `raw_text` always decodes as a\n * string, even when its text is the stringified form of a boolean, since the\n * Slack shape does not distinguish a boolean from ordinary text.\n */\nexport function fromSlackBlocks(blocks: unknown): FromSlackBlocksResult {\n const warnings: SlackConversionWarning[] = [];\n try {\n const list = extractBlockList(blocks);\n if (list === undefined) {\n return {\n nodes: [],\n warnings: [\n {\n code: \"dropped\",\n component: \"Root\",\n detail: \"Input could not be converted.\",\n },\n ],\n };\n }\n const boundedList = boundedArray(\n list,\n INBOUND_BLOCK_CAP,\n warnings,\n \"Root\",\n `blocks were clamped to ${INBOUND_BLOCK_CAP} entries.`,\n );\n const nodes = convertBlockList(boundedList, warnings);\n return { nodes, warnings };\n } catch {\n return {\n nodes: [],\n warnings: [\n {\n code: \"dropped\",\n component: \"Root\",\n detail: \"Input could not be converted.\",\n },\n ],\n };\n }\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"toSlackBlocks.d.ts","sourceRoot":"","sources":["../../src/slack/toSlackBlocks.ts"],"names":[],"mappings":"AAgDA,OAAO,KAAK,EAWV,iBAAiB,EACjB,oBAAoB,EACrB,mBAAgB;AAuvCjB,sFAAsF;AACtF,wBAAgB,aAAa,CAC3B,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,oBAAoB,GAC7B,iBAAiB,CA2CnB"}
1
+ {"version":3,"file":"toSlackBlocks.d.ts","sourceRoot":"","sources":["../../src/slack/toSlackBlocks.ts"],"names":[],"mappings":"AAsDA,OAAO,KAAK,EAWV,iBAAiB,EACjB,oBAAoB,EACrB,mBAAgB;AA6kDjB,sFAAsF;AACtF,wBAAgB,aAAa,CAC3B,IAAI,EAAE,OAAO,EACb,OAAO,CAAC,EAAE,oBAAoB,GAC7B,iBAAiB,CA8CnB"}
@@ -3,13 +3,16 @@ import { copyBounded } from "../convert/copyBounded.js";
3
3
  import { boundSpec, clampReasonDetail } from "../convert/boundSpec.js";
4
4
  import { isElement } from "../convert/isElement.js";
5
5
  import { takeRun } from "../convert/takeRun.js";
6
+ import { factTrend, formatFactDelta, formatValue } from "../vocabulary/formatValue.js";
6
7
  import { CONTEXT_TEXT_CAP, DATA_TABLE_CHAR_BUDGET, FACT_FIELD_TEXT_CAP, INPUT_LABEL_CAP, MARKDOWN_TEXT_BUDGET, SECTION_TEXT_CAP, buildAlertBlock, buildAlertFallback, buildBlockBudgetNote, buildCardBlock, buildCarouselBlock, buildDataTableBlock } from "./constants.js";
7
8
  //#region src/slack/toSlackBlocks.ts
9
+ const SLACK_BLOCK_ID_CAP = 255;
8
10
  const INTERACTIVE_TYPES = /* @__PURE__ */ new Set([
9
11
  "Button",
10
12
  "Select",
11
13
  "DatePicker",
12
14
  "Checkbox",
15
+ "CheckboxGroup",
13
16
  "RadioGroup"
14
17
  ]);
15
18
  /**
@@ -21,11 +24,13 @@ const INTERACTIVE_TYPES = /* @__PURE__ */ new Set([
21
24
  const CONTROL_TYPES = /* @__PURE__ */ new Set([
22
25
  ...INTERACTIVE_TYPES,
23
26
  "Input",
27
+ "Slider",
24
28
  "Form"
25
29
  ]);
26
30
  const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
27
31
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
28
32
  const asString = (value) => typeof value === "string" ? value : "";
33
+ const asFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
29
34
  const warn = (context, code, component, detail) => {
30
35
  context.warnings.push({
31
36
  code,
@@ -43,19 +48,47 @@ const plainText = (text) => ({
43
48
  type: "plain_text",
44
49
  text
45
50
  });
46
- const actionValue = (action) => {
51
+ const serializeFieldBlockId = (sequence, fields) => `aui:${sequence}:${JSON.stringify(fields.map(({ actionId, name, component }) => component === "Checkbox" ? [
52
+ actionId,
53
+ name,
54
+ "Checkbox"
55
+ ] : [actionId, name]))}`;
56
+ const fieldBlockId = (fields, context) => {
57
+ if (fields.length === 0) return void 0;
58
+ const sequence = context.fieldBlockIdSequence++;
59
+ const blockId = serializeFieldBlockId(sequence, fields);
60
+ if (blockId.length <= SLACK_BLOCK_ID_CAP) return blockId;
61
+ for (const field of fields) warn(context, "dropped", field.component, "name could not be mapped because its Slack block_id exceeded 255 characters.");
62
+ };
63
+ const collectOmittedPasswordNames = (node, names, depth = 0) => {
64
+ if (depth > 64) return;
65
+ if (Array.isArray(node)) for (const child of node) collectOmittedPasswordNames(child, names, depth + 1);
66
+ else if (isElement(node)) {
67
+ if (node.type === "Input" && node.props["inputType"] === "password" && typeof node.props["name"] === "string") names.add(node.props["name"]);
68
+ if (node.children !== void 0) collectOmittedPasswordNames(node.children, names, depth + 1);
69
+ }
70
+ };
71
+ const withoutOmittedPasswordFallbacks = (value, names, depth = 0) => {
72
+ if (depth > 64) throw new RangeError("Action payload nests too deeply to sanitize.");
73
+ if (Array.isArray(value)) return value.map((item) => withoutOmittedPasswordFallbacks(item, names, depth + 1));
74
+ if (!isRecord(value)) return value;
75
+ if (typeof value["$field"] === "string" && names.has(value["$field"]) && Object.keys(value).every((key) => key === "$field" || key === "fallback")) return { $field: value["$field"] };
76
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, withoutOmittedPasswordFallbacks(item, names, depth + 1)]));
77
+ };
78
+ const actionValue = (action, context) => {
47
79
  if (!isRecord(action)) return void 0;
48
80
  const { type: _type, ...payload } = action;
49
81
  if (Object.keys(payload).length === 0) return void 0;
82
+ const serialized = JSON.stringify(context.omittedPasswordNames.size === 0 ? payload : withoutOmittedPasswordFallbacks(payload, context.omittedPasswordNames));
83
+ return serialized === "{}" ? void 0 : serialized;
84
+ };
85
+ const buttonElement = (label, buttonStyle, action, component, context) => {
86
+ let value;
50
87
  try {
51
- const serialized = JSON.stringify(payload);
52
- return serialized === "{}" ? void 0 : serialized;
88
+ value = actionValue(action, context);
53
89
  } catch {
54
- return;
90
+ warn(context, "dropped", component, "value was dropped because the action payload could not be serialized.");
55
91
  }
56
- };
57
- const buttonElement = (label, buttonStyle, action, component, context) => {
58
- let value = actionValue(action);
59
92
  if (value !== void 0 && value.length > 2e3) {
60
93
  warn(context, "dropped", component, "value was dropped because the action payload was too large to round-trip.");
61
94
  value = void 0;
@@ -71,9 +104,11 @@ const buttonElement = (label, buttonStyle, action, component, context) => {
71
104
  };
72
105
  const optionFrom = (value, component, context) => {
73
106
  if (!isRecord(value) || typeof value["label"] !== "string" || typeof value["value"] !== "string") return;
107
+ const description = asString(value["description"]);
74
108
  return {
75
109
  text: plainText(clampText(value["label"], 75, component, "option label", context)),
76
- value: value["value"]
110
+ value: value["value"],
111
+ ...description ? { description: plainText(clampText(description, 75, component, "option description", context)) } : {}
77
112
  };
78
113
  };
79
114
  const warnDroppedOptions = (kept, taken, component, context) => {
@@ -92,10 +127,13 @@ const toActionElement = (element, context) => {
92
127
  const options = takenOptions.map((option) => optionFrom(option, "Select", context)).filter((option) => option !== void 0);
93
128
  warnDroppedOptions(options.length, takenOptions.length, "Select", context);
94
129
  const placeholder = clampText(asString(props["placeholder"]), 150, "Select", "placeholder", context);
130
+ const defaultValue = props["defaultValue"];
131
+ const initialOption = typeof defaultValue === "string" ? options.find((option) => option.value === defaultValue) : void 0;
95
132
  return {
96
133
  type: "static_select",
97
134
  action_id: asActionId(action, "Select", context),
98
135
  options,
136
+ ...initialOption !== void 0 ? { initial_option: initialOption } : {},
99
137
  ...placeholder ? { placeholder: plainText(placeholder) } : {}
100
138
  };
101
139
  }
@@ -123,6 +161,21 @@ const toActionElement = (element, context) => {
123
161
  ...props["defaultChecked"] === true ? { initial_options: [option] } : {}
124
162
  };
125
163
  }
164
+ case "CheckboxGroup": {
165
+ const rawOptions = Array.isArray(props["options"]) ? props["options"] : [];
166
+ const { items: takenOptions, truncated } = copyBounded(rawOptions, 10);
167
+ if (truncated) warn(context, "clamped", "CheckboxGroup", `options were clamped to 10 entries.`);
168
+ const options = takenOptions.map((option) => optionFrom(option, "CheckboxGroup", context)).filter((option) => option !== void 0);
169
+ warnDroppedOptions(options.length, takenOptions.length, "CheckboxGroup", context);
170
+ const defaultValue = Array.isArray(props["defaultValue"]) ? props["defaultValue"] : [];
171
+ const initialOptions = options.filter((option) => defaultValue.includes(option.value));
172
+ return {
173
+ type: "checkboxes",
174
+ action_id: asActionId(action, "CheckboxGroup", context),
175
+ options,
176
+ ...initialOptions.length > 0 ? { initial_options: initialOptions } : {}
177
+ };
178
+ }
126
179
  case "RadioGroup": {
127
180
  const rawOptions = Array.isArray(props["options"]) ? props["options"] : [];
128
181
  const { items: takenOptions, truncated } = copyBounded(rawOptions, 10);
@@ -150,9 +203,11 @@ const convertFacts = (facts, context) => {
150
203
  const fields = facts.map((fact) => {
151
204
  const label = asString(fact.props["label"]);
152
205
  const value = asString(fact.props["value"]);
206
+ const delta = fact.props["delta"];
207
+ const deltaText = typeof delta === "string" ? formatFactDelta(delta, factTrend(delta, fact.props["trend"])) : void 0;
153
208
  return {
154
209
  type: "mrkdwn",
155
- text: clampText(`*${label}*\n${value}`, FACT_FIELD_TEXT_CAP, "Fact", "field", context)
210
+ text: clampText(`*${label}*\n${deltaText === void 0 ? value : `${value} (${deltaText})`}`, FACT_FIELD_TEXT_CAP, "Fact", "field", context)
156
211
  };
157
212
  });
158
213
  const blocks = [];
@@ -163,12 +218,65 @@ const convertFacts = (facts, context) => {
163
218
  return blocks;
164
219
  };
165
220
  const convertActions = (elements, context) => {
166
- const converted = elements.map((element) => toActionElement(element, context)).filter((element) => element !== void 0);
221
+ const converted = elements.map((source) => ({
222
+ source,
223
+ element: toActionElement(source, context)
224
+ })).filter((entry) => entry.element !== void 0);
167
225
  const blocks = [];
168
- for (let index = 0; index < converted.length; index += 25) blocks.push({
169
- type: "actions",
170
- elements: converted.slice(index, index + 25)
226
+ const groups = [];
227
+ let group = [];
228
+ let actionIds = /* @__PURE__ */ new Set();
229
+ const hasFieldName = (entry) => {
230
+ const name = entry.source.props["name"];
231
+ return typeof name === "string" && name.length > 0;
232
+ };
233
+ const fieldsFor = (entries) => entries.flatMap(({ source, element }) => {
234
+ const name = source.props["name"];
235
+ return typeof name === "string" && name.length > 0 ? [{
236
+ actionId: element.action_id,
237
+ name,
238
+ component: source.type
239
+ }] : [];
171
240
  });
241
+ for (const entry of converted) {
242
+ const repeatedMappedActionId = actionIds.has(entry.element.action_id) && (hasFieldName(entry) || group.some((candidate) => candidate.element.action_id === entry.element.action_id && hasFieldName(candidate)));
243
+ if (group.length === 25 || repeatedMappedActionId) {
244
+ groups.push(group);
245
+ group = [];
246
+ actionIds = /* @__PURE__ */ new Set();
247
+ }
248
+ group.push(entry);
249
+ actionIds.add(entry.element.action_id);
250
+ }
251
+ if (group.length > 0) groups.push(group);
252
+ const mappedGroups = [];
253
+ let nextSequence = context.fieldBlockIdSequence;
254
+ for (const entries of groups) {
255
+ let current = [];
256
+ for (const entry of entries) {
257
+ const candidate = [...current, entry];
258
+ const fields = fieldsFor(candidate);
259
+ const blockId = fields.length > 0 ? serializeFieldBlockId(nextSequence, fields) : void 0;
260
+ if (current.length > 0 && fields.length > fieldsFor(current).length && blockId !== void 0 && blockId.length > SLACK_BLOCK_ID_CAP) {
261
+ mappedGroups.push(current);
262
+ if (fieldsFor(current).length > 0) nextSequence += 1;
263
+ current = [entry];
264
+ } else current = candidate;
265
+ }
266
+ if (current.length > 0) {
267
+ mappedGroups.push(current);
268
+ if (fieldsFor(current).length > 0) nextSequence += 1;
269
+ }
270
+ }
271
+ for (const group of mappedGroups) {
272
+ const fields = fieldsFor(group);
273
+ const blockId = fieldBlockId(fields, context);
274
+ blocks.push({
275
+ type: "actions",
276
+ ...blockId !== void 0 ? { block_id: blockId } : {},
277
+ elements: group.map(({ element }) => element)
278
+ });
279
+ }
172
280
  return blocks;
173
281
  };
174
282
  const cardActionButtons = (props, context) => {
@@ -438,14 +546,18 @@ const convertCarousel = (element, context, depth) => {
438
546
  }
439
547
  return [buildCarouselBlock(cards)];
440
548
  };
441
- const toDataTableCell = (value) => {
442
- if (typeof value === "number") return {
549
+ const toDataTableCell = (value, format) => {
550
+ if (typeof value === "number" && format === void 0) return {
443
551
  type: "raw_number",
444
552
  text: String(value)
445
553
  };
446
554
  if (typeof value === "string" || typeof value === "boolean") return {
447
555
  type: "raw_text",
448
- text: String(value)
556
+ text: formatValue(value, format)
557
+ };
558
+ if (typeof value === "number") return {
559
+ type: "raw_text",
560
+ text: formatValue(value, format)
449
561
  };
450
562
  };
451
563
  const rowCharacters = (row) => row.reduce((sum, cell) => sum + cell.text.length, 0);
@@ -462,7 +574,7 @@ const convertTable = (element, context) => {
462
574
  type: "raw_text",
463
575
  text: isRecord(column) ? asString(column["label"]) : ""
464
576
  }));
465
- const dataRows = takenRows.map((row) => (Array.isArray(row) ? copyBounded(row, 20).items : []).map((value) => toDataTableCell(value) ?? {
577
+ const dataRows = takenRows.map((row) => (Array.isArray(row) ? copyBounded(row, 20).items : []).map((value, index) => toDataTableCell(value, isRecord(takenColumns[index]) ? takenColumns[index]["format"] : void 0) ?? {
466
578
  type: "raw_text",
467
579
  text: ""
468
580
  }));
@@ -526,17 +638,70 @@ const convertElement = (element, context, depth) => {
526
638
  case "Select":
527
639
  case "DatePicker":
528
640
  case "Checkbox":
641
+ case "CheckboxGroup":
529
642
  case "RadioGroup": return convertActions([element], context);
643
+ case "Slider": {
644
+ const label = clampText(asString(props["label"]), INPUT_LABEL_CAP, "Slider", "label", context);
645
+ const min = asFiniteNumber(props["min"]);
646
+ const max = asFiniteNumber(props["max"]);
647
+ const step = asFiniteNumber(props["step"]);
648
+ const defaultValue = asFiniteNumber(props["defaultValue"]);
649
+ const actionId = asActionId(element.action, "Slider", context);
650
+ const name = props["name"];
651
+ const blockId = fieldBlockId(typeof name === "string" && name.length > 0 ? [{
652
+ actionId,
653
+ name,
654
+ component: "Slider"
655
+ }] : [], context);
656
+ return [{
657
+ type: "input",
658
+ ...blockId !== void 0 ? { block_id: blockId } : {},
659
+ label: plainText(label),
660
+ element: {
661
+ type: "number_input",
662
+ action_id: actionId,
663
+ ...min !== void 0 ? { min_value: min } : {},
664
+ ...max !== void 0 ? { max_value: max } : {},
665
+ ...defaultValue !== void 0 ? { initial_value: defaultValue } : {},
666
+ ...[
667
+ step,
668
+ min,
669
+ max,
670
+ defaultValue
671
+ ].some((value) => value !== void 0 && !Number.isInteger(value)) ? { is_decimal_allowed: true } : {}
672
+ }
673
+ }];
674
+ }
530
675
  case "Input": {
676
+ if (props["inputType"] === "password") {
677
+ warn(context, "dropped", "Input", "Password input was replaced by a Slack omission note.");
678
+ return [{
679
+ type: "context",
680
+ elements: [{
681
+ type: "mrkdwn",
682
+ text: "Password input omitted on Slack."
683
+ }]
684
+ }];
685
+ }
531
686
  const label = clampText(asString(props["label"]), INPUT_LABEL_CAP, "Input", "label", context);
532
687
  const placeholder = clampText(asString(props["placeholder"]), 150, "Input", "placeholder", context);
688
+ const actionId = asActionId(element.action, "Input", context);
689
+ const name = props["name"];
690
+ const blockId = fieldBlockId(typeof name === "string" && name.length > 0 ? [{
691
+ actionId,
692
+ name,
693
+ component: "Input"
694
+ }] : [], context);
695
+ const defaultValue = props["defaultValue"];
533
696
  return [{
534
697
  type: "input",
698
+ ...blockId !== void 0 ? { block_id: blockId } : {},
535
699
  label: plainText(label),
536
700
  element: {
537
701
  type: "plain_text_input",
538
- action_id: asActionId(element.action, "Input", context),
539
- ...props["multiline"] === true ? { multiline: true } : {},
702
+ action_id: actionId,
703
+ ...props["multiline"] === true && (props["inputType"] === void 0 || props["inputType"] === "text") ? { multiline: true } : {},
704
+ ...typeof defaultValue === "string" && defaultValue ? { initial_value: defaultValue } : {},
540
705
  ...placeholder ? { placeholder: plainText(placeholder) } : {}
541
706
  }
542
707
  }];
@@ -641,6 +806,8 @@ function toSlackBlocks(node, options) {
641
806
  blockCap: surface === "modal" ? 100 : 50,
642
807
  surface,
643
808
  warnings: [],
809
+ omittedPasswordNames: /* @__PURE__ */ new Set(),
810
+ fieldBlockIdSequence: 0,
644
811
  markdownCharacters: 0,
645
812
  markdownExhausted: false,
646
813
  dataTableCharacters: 0
@@ -648,6 +815,7 @@ function toSlackBlocks(node, options) {
648
815
  try {
649
816
  const bounded = boundSpec(node, (reason) => warn(context, "clamped", "Root", clampReasonDetail(reason)));
650
817
  const { root } = normalizeSpec(bounded);
818
+ collectOmittedPasswordNames(root, context.omittedPasswordNames);
651
819
  const converted = convertSequence(root, context, 0);
652
820
  if (converted.length <= context.blockCap) return {
653
821
  blocks: converted,