@assistant-ui/react-generative-ui 0.0.7 → 0.0.8

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 (179) hide show
  1. package/dist/JSONGenerativeUI.client.d.ts +0 -1
  2. package/dist/JSONGenerativeUI.client.d.ts.map +1 -1
  3. package/dist/JSONGenerativeUI.server.d.ts +0 -1
  4. package/dist/JSONGenerativeUI.server.d.ts.map +1 -1
  5. package/dist/JSONGenerativeUI.shared.d.ts +0 -1
  6. package/dist/JSONGenerativeUI.shared.d.ts.map +1 -1
  7. package/dist/actionRegistry.d.ts +2 -11
  8. package/dist/actionRegistry.d.ts.map +1 -1
  9. package/dist/actionRegistry.js.map +1 -1
  10. package/dist/buildPresentParameters.d.ts +5 -5
  11. package/dist/buildPresentParameters.d.ts.map +1 -1
  12. package/dist/buildPresentParameters.js +10 -5
  13. package/dist/buildPresentParameters.js.map +1 -1
  14. package/dist/constants.d.ts.map +1 -1
  15. package/dist/defineGenerativeComponents.d.ts +0 -1
  16. package/dist/defineGenerativeComponents.d.ts.map +1 -1
  17. package/dist/generativeUIToJSX.d.ts +15 -10
  18. package/dist/generativeUIToJSX.d.ts.map +1 -1
  19. package/dist/generativeUIToJSX.js +47 -17
  20. package/dist/generativeUIToJSX.js.map +1 -1
  21. package/dist/index.d.ts +4 -3
  22. package/dist/index.js +2 -2
  23. package/dist/index.server.d.ts +4 -3
  24. package/dist/index.server.js +2 -2
  25. package/dist/index.shared.d.ts +3 -3
  26. package/dist/index.shared.js +2 -2
  27. package/dist/ir.d.ts +6 -6
  28. package/dist/ir.d.ts.map +1 -1
  29. package/dist/ir.js +27 -1
  30. package/dist/ir.js.map +1 -1
  31. package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.ts +3 -3
  32. package/dist/node_modules/.pnpm/@types_json-schema@7.0.15/node_modules/@types/json-schema/index.d.ts.map +1 -1
  33. package/dist/renderGenerativeUI.d.ts +0 -1
  34. package/dist/renderGenerativeUI.d.ts.map +1 -1
  35. package/dist/slack/constants.d.ts +111 -0
  36. package/dist/slack/constants.d.ts.map +1 -0
  37. package/dist/slack/constants.js +168 -0
  38. package/dist/slack/constants.js.map +1 -0
  39. package/dist/slack/decodeBlockAction.d.ts +11 -0
  40. package/dist/slack/decodeBlockAction.d.ts.map +1 -0
  41. package/dist/slack/decodeBlockAction.js +40 -0
  42. package/dist/slack/decodeBlockAction.js.map +1 -0
  43. package/dist/slack/fromSlackBlocks.d.ts +30 -0
  44. package/dist/slack/fromSlackBlocks.d.ts.map +1 -0
  45. package/dist/slack/fromSlackBlocks.js +447 -0
  46. package/dist/slack/fromSlackBlocks.js.map +1 -0
  47. package/dist/slack/toSlackBlocks.d.ts +7 -0
  48. package/dist/slack/toSlackBlocks.d.ts.map +1 -0
  49. package/dist/slack/toSlackBlocks.js +649 -0
  50. package/dist/slack/toSlackBlocks.js.map +1 -0
  51. package/dist/slack/types.d.ts +178 -0
  52. package/dist/slack/types.d.ts.map +1 -0
  53. package/dist/slack/types.js +0 -0
  54. package/dist/slack.d.ts +5 -0
  55. package/dist/slack.js +4 -0
  56. package/dist/teams/boundSpec.d.ts +20 -0
  57. package/dist/teams/boundSpec.d.ts.map +1 -0
  58. package/dist/teams/boundSpec.js +67 -0
  59. package/dist/teams/boundSpec.js.map +1 -0
  60. package/dist/teams/constants.d.ts +57 -0
  61. package/dist/teams/constants.d.ts.map +1 -0
  62. package/dist/teams/constants.js +92 -0
  63. package/dist/teams/constants.js.map +1 -0
  64. package/dist/teams/decodeSubmitData.d.ts +25 -0
  65. package/dist/teams/decodeSubmitData.d.ts.map +1 -0
  66. package/dist/teams/decodeSubmitData.js +44 -0
  67. package/dist/teams/decodeSubmitData.js.map +1 -0
  68. package/dist/teams/toAdaptiveCard.d.ts +39 -0
  69. package/dist/teams/toAdaptiveCard.d.ts.map +1 -0
  70. package/dist/teams/toAdaptiveCard.js +496 -0
  71. package/dist/teams/toAdaptiveCard.js.map +1 -0
  72. package/dist/teams/toTeamsAttachments.d.ts +13 -0
  73. package/dist/teams/toTeamsAttachments.d.ts.map +1 -0
  74. package/dist/teams/toTeamsAttachments.js +95 -0
  75. package/dist/teams/toTeamsAttachments.js.map +1 -0
  76. package/dist/teams/types.d.ts +196 -0
  77. package/dist/teams/types.d.ts.map +1 -0
  78. package/dist/teams/types.js +0 -0
  79. package/dist/teams.d.ts +5 -0
  80. package/dist/teams.js +4 -0
  81. package/dist/types.d.ts +7 -4
  82. package/dist/types.d.ts.map +1 -1
  83. package/dist/vocabulary/alert.d.ts +6 -12
  84. package/dist/vocabulary/alert.d.ts.map +1 -1
  85. package/dist/vocabulary/alert.js +13 -4
  86. package/dist/vocabulary/alert.js.map +1 -1
  87. package/dist/vocabulary/collectFormValues.d.ts +23 -0
  88. package/dist/vocabulary/collectFormValues.d.ts.map +1 -0
  89. package/dist/vocabulary/collectFormValues.js +32 -0
  90. package/dist/vocabulary/collectFormValues.js.map +1 -0
  91. package/dist/vocabulary/data.d.ts +16 -17
  92. package/dist/vocabulary/data.d.ts.map +1 -1
  93. package/dist/vocabulary/data.js +244 -13
  94. package/dist/vocabulary/data.js.map +1 -1
  95. package/dist/vocabulary/dispatch.d.ts +11 -0
  96. package/dist/vocabulary/dispatch.d.ts.map +1 -0
  97. package/dist/vocabulary/dispatch.js +21 -0
  98. package/dist/vocabulary/dispatch.js.map +1 -0
  99. package/dist/vocabulary/fact.d.ts +1 -6
  100. package/dist/vocabulary/fact.d.ts.map +1 -1
  101. package/dist/vocabulary/form.d.ts +14 -0
  102. package/dist/vocabulary/form.d.ts.map +1 -0
  103. package/dist/vocabulary/form.js +23 -0
  104. package/dist/vocabulary/form.js.map +1 -0
  105. package/dist/vocabulary/icon.d.ts +44 -0
  106. package/dist/vocabulary/icon.d.ts.map +1 -0
  107. package/dist/vocabulary/icon.js +272 -0
  108. package/dist/vocabulary/icon.js.map +1 -0
  109. package/dist/vocabulary/index.d.ts +0 -1
  110. package/dist/vocabulary/index.d.ts.map +1 -1
  111. package/dist/vocabulary/index.js +7 -1
  112. package/dist/vocabulary/index.js.map +1 -1
  113. package/dist/vocabulary/interactive.d.ts +45 -33
  114. package/dist/vocabulary/interactive.d.ts.map +1 -1
  115. package/dist/vocabulary/interactive.js +78 -29
  116. package/dist/vocabulary/interactive.js.map +1 -1
  117. package/dist/vocabulary/layout.d.ts +27 -23
  118. package/dist/vocabulary/layout.d.ts.map +1 -1
  119. package/dist/vocabulary/layout.js +78 -14
  120. package/dist/vocabulary/layout.js.map +1 -1
  121. package/dist/vocabulary/list.d.ts +17 -0
  122. package/dist/vocabulary/list.d.ts.map +1 -0
  123. package/dist/vocabulary/list.js +48 -0
  124. package/dist/vocabulary/list.js.map +1 -0
  125. package/dist/vocabulary/media.d.ts +3 -9
  126. package/dist/vocabulary/media.d.ts.map +1 -1
  127. package/dist/vocabulary/media.js +17 -7
  128. package/dist/vocabulary/media.js.map +1 -1
  129. package/dist/vocabulary/text.d.ts +3 -17
  130. package/dist/vocabulary/text.d.ts.map +1 -1
  131. package/package.json +13 -5
  132. package/src/actionRegistry.ts +2 -10
  133. package/src/buildPresentParameters.ts +15 -9
  134. package/src/generativeUIToJSX.test.ts +186 -0
  135. package/src/generativeUIToJSX.ts +112 -21
  136. package/src/index.shared.ts +3 -0
  137. package/src/ir.ts +31 -5
  138. package/src/renderGenerativeUI.test.tsx +16 -2
  139. package/src/slack/constants.ts +217 -0
  140. package/src/slack/decodeBlockAction.test.ts +135 -0
  141. package/src/slack/decodeBlockAction.ts +66 -0
  142. package/src/slack/fromSlackBlocks.test.ts +1145 -0
  143. package/src/slack/fromSlackBlocks.ts +709 -0
  144. package/src/slack/toSlackBlocks.test.ts +1936 -0
  145. package/src/slack/toSlackBlocks.ts +1319 -0
  146. package/src/slack/types.ts +230 -0
  147. package/src/slack.ts +37 -0
  148. package/src/teams/boundSpec.ts +102 -0
  149. package/src/teams/constants.ts +127 -0
  150. package/src/teams/decodeSubmitData.test.ts +148 -0
  151. package/src/teams/decodeSubmitData.ts +57 -0
  152. package/src/teams/toAdaptiveCard.test.ts +1410 -0
  153. package/src/teams/toAdaptiveCard.ts +762 -0
  154. package/src/teams/toTeamsAttachments.test.ts +29 -0
  155. package/src/teams/toTeamsAttachments.ts +137 -0
  156. package/src/teams/types.ts +237 -0
  157. package/src/teams.ts +34 -0
  158. package/src/vocabulary/alert.test.tsx +72 -2
  159. package/src/vocabulary/alert.tsx +24 -5
  160. package/src/vocabulary/collectFormValues.test.ts +127 -0
  161. package/src/vocabulary/collectFormValues.ts +58 -0
  162. package/src/vocabulary/data.test.tsx +503 -3
  163. package/src/vocabulary/data.tsx +426 -18
  164. package/src/vocabulary/dispatch.ts +22 -0
  165. package/src/vocabulary/form.test.tsx +112 -0
  166. package/src/vocabulary/form.tsx +34 -0
  167. package/src/vocabulary/icon.test.tsx +64 -0
  168. package/src/vocabulary/icon.tsx +174 -0
  169. package/src/vocabulary/index.test.ts +7 -0
  170. package/src/vocabulary/index.ts +6 -0
  171. package/src/vocabulary/interactive.dispatch.test.tsx +243 -1
  172. package/src/vocabulary/interactive.test.tsx +143 -3
  173. package/src/vocabulary/interactive.tsx +134 -30
  174. package/src/vocabulary/layout.test.tsx +231 -3
  175. package/src/vocabulary/layout.tsx +168 -15
  176. package/src/vocabulary/list.test.tsx +195 -0
  177. package/src/vocabulary/list.tsx +48 -0
  178. package/src/vocabulary/media.test.tsx +36 -4
  179. package/src/vocabulary/media.tsx +27 -8
@@ -0,0 +1,44 @@
1
+ //#region src/teams/decodeSubmitData.ts
2
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3
+ /**
4
+ * Decodes the merged `activity.value` object a Teams bot receives on
5
+ * Action.Submit, inverting the `aui` envelope {@link buildSubmitAction} nests
6
+ * the resume action under. Adaptive Cards fold every other same-card input's
7
+ * value into the same submit object keyed by its `id`, so every top-level key
8
+ * besides `aui` is collected into `$input` (omitted when there are none).
9
+ * `aui` is reserved for the envelope: `toAdaptiveCard` renames any input
10
+ * whose id would collide to an unused id derived from it before encoding, so
11
+ * a same-card input value can never land on this key. `$input` and `type`
12
+ * are reserved on the way out: a `$input` key inside the envelope payload is
13
+ * dropped so the slot only ever reflects same-card input values, and the
14
+ * envelope's own `type` wins over a payload key of the same name. Every
15
+ * collected key (in `payload` or `$input`) is kept as an own data property of
16
+ * the returned object, even a `__proto__` or `constructor` key, since the
17
+ * object is always built by spreading and `Object.fromEntries` rather than by
18
+ * keyed assignment. Envelope keys are read as own properties only, so
19
+ * prototype-inherited `aui`, `type`, or `payload` values never dispatch.
20
+ * Returns `undefined` for a missing or malformed `aui` envelope; never throws.
21
+ */
22
+ function decodeSubmitData(value) {
23
+ try {
24
+ if (!isRecord(value) || !Object.hasOwn(value, "aui")) return void 0;
25
+ const aui = value["aui"];
26
+ if (!isRecord(aui) || !Object.hasOwn(aui, "type") || typeof aui["type"] !== "string") return;
27
+ const type = aui["type"];
28
+ const payload = Object.hasOwn(aui, "payload") && isRecord(aui["payload"]) ? aui["payload"] : {};
29
+ const payloadEntries = Object.entries(payload).filter(([key]) => key !== "$input");
30
+ const inputEntries = Object.entries(value).filter(([key]) => key !== "aui");
31
+ const input = inputEntries.length > 0 ? Object.fromEntries(inputEntries) : void 0;
32
+ return {
33
+ ...Object.fromEntries(payloadEntries),
34
+ type,
35
+ ...input !== void 0 ? { $input: input } : {}
36
+ };
37
+ } catch {
38
+ return;
39
+ }
40
+ }
41
+ //#endregion
42
+ export { decodeSubmitData };
43
+
44
+ //# sourceMappingURL=decodeSubmitData.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"decodeSubmitData.js","names":[],"sources":["../../src/teams/decodeSubmitData.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\n/**\n * Decodes the merged `activity.value` object a Teams bot receives on\n * Action.Submit, inverting the `aui` envelope {@link buildSubmitAction} nests\n * the resume action under. Adaptive Cards fold every other same-card input's\n * value into the same submit object keyed by its `id`, so every top-level key\n * besides `aui` is collected into `$input` (omitted when there are none).\n * `aui` is reserved for the envelope: `toAdaptiveCard` renames any input\n * whose id would collide to an unused id derived from it before encoding, so\n * a same-card input value can never land on this key. `$input` and `type`\n * are reserved on the way out: a `$input` key inside the envelope payload is\n * dropped so the slot only ever reflects same-card input values, and the\n * envelope's own `type` wins over a payload key of the same name. Every\n * collected key (in `payload` or `$input`) is kept as an own data property of\n * the returned object, even a `__proto__` or `constructor` key, since the\n * object is always built by spreading and `Object.fromEntries` rather than by\n * keyed assignment. Envelope keys are read as own properties only, so\n * prototype-inherited `aui`, `type`, or `payload` values never dispatch.\n * Returns `undefined` for a missing or malformed `aui` envelope; never throws.\n */\nexport function decodeSubmitData(value: unknown): Action | undefined {\n try {\n if (!isRecord(value) || !Object.hasOwn(value, \"aui\")) return undefined;\n const aui = value[\"aui\"];\n if (\n !isRecord(aui) ||\n !Object.hasOwn(aui, \"type\") ||\n typeof aui[\"type\"] !== \"string\"\n ) {\n return undefined;\n }\n const type = aui[\"type\"];\n\n const payload =\n Object.hasOwn(aui, \"payload\") && isRecord(aui[\"payload\"])\n ? aui[\"payload\"]\n : {};\n const payloadEntries = Object.entries(payload).filter(\n ([key]) => key !== \"$input\",\n );\n const inputEntries = Object.entries(value).filter(([key]) => key !== \"aui\");\n const input =\n inputEntries.length > 0 ? Object.fromEntries(inputEntries) : undefined;\n\n return {\n ...Object.fromEntries(payloadEntries),\n type,\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;;;;;;;;;;;;;;;;;;;;AAqBrE,SAAgB,iBAAiB,OAAoC;CACnE,IAAI;EACF,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,OAAO,OAAO,OAAO,KAAK,GAAG,OAAO,KAAA;EAC7D,MAAM,MAAM,MAAM;EAClB,IACE,CAAC,SAAS,GAAG,KACb,CAAC,OAAO,OAAO,KAAK,MAAM,KAC1B,OAAO,IAAI,YAAY,UAEvB;EAEF,MAAM,OAAO,IAAI;EAEjB,MAAM,UACJ,OAAO,OAAO,KAAK,SAAS,KAAK,SAAS,IAAI,UAAU,IACpD,IAAI,aACJ,CAAC;EACP,MAAM,iBAAiB,OAAO,QAAQ,OAAO,CAAC,CAAC,QAC5C,CAAC,SAAS,QAAQ,QACrB;EACA,MAAM,eAAe,OAAO,QAAQ,KAAK,CAAC,CAAC,QAAQ,CAAC,SAAS,QAAQ,KAAK;EAC1E,MAAM,QACJ,aAAa,SAAS,IAAI,OAAO,YAAY,YAAY,IAAI,KAAA;EAE/D,OAAO;GACL,GAAG,OAAO,YAAY,cAAc;GACpC;GACA,GAAI,UAAU,KAAA,IAAY,EAAE,QAAQ,MAAM,IAAI,CAAC;EACjD;CACF,QAAQ;EACN;CACF;AACF"}
@@ -0,0 +1,39 @@
1
+ import { NormalizedUIElement, NormalizedUINode } from "../ir.js";
2
+ import { AdaptiveCardResult, TeamsAdaptiveCard, TeamsCardElement, TeamsConversionWarning, ToAdaptiveCardOptions } from "./types.js";
3
+ //#region src/teams/toAdaptiveCard.d.ts
4
+ /** Shared mutable conversion state threaded through one top-level conversion. */
5
+ interface ConversionContext {
6
+ readonly warnings: TeamsConversionWarning[];
7
+ usedInputIds: Set<string>;
8
+ }
9
+ declare function convertElement(element: NormalizedUIElement, context: ConversionContext, depth: number): TeamsCardElement[];
10
+ /**
11
+ * Walks a normalized sequence into card body elements. Nested arrays are
12
+ * flattened into one flat sibling sequence first (via {@link normalizedList}),
13
+ * so a `Divider`/`Fact`/`Button` run merges correctly even when the model
14
+ * wrapped some of its siblings in their own array. Consecutive `Fact`
15
+ * siblings merge into one FactSet and consecutive `Button` siblings merge
16
+ * into one ActionSet; a `Divider`/`Spacer` emits nothing itself but sets
17
+ * `separator`/`spacing` on the next emitted sibling (a trailing or orphan
18
+ * one is dropped silently, since there is no sibling left to carry it).
19
+ * `depth` advances by exactly one per element-nesting level: this call does
20
+ * not itself add a level, since flattening a sibling array or converting a
21
+ * sibling element is not a descent; only a container's own recursive
22
+ * `convertSequence(children, depth + 1)` call is.
23
+ */
24
+ declare function convertSequence(node: NormalizedUINode | undefined, context: ConversionContext, depth: number): TeamsCardElement[];
25
+ /** Builds a full card (schema envelope plus body) from an already-normalized root, checking the soft payload budget. */
26
+ declare function convertRootToCard(root: NormalizedUINode | readonly NormalizedUINode[], context: ConversionContext): TeamsAdaptiveCard;
27
+ /**
28
+ * Converts a generative-ui tree into a Microsoft Teams Adaptive Card and
29
+ * non-fatal downgrade warnings. Sizes, weights, and colors map to Adaptive
30
+ * Card's semantic enums rather than raw values. An Input/Select/RadioGroup/
31
+ * Checkbox/DatePicker whose id would be the reserved {@link RESERVED_INPUT_ID}
32
+ * is renamed with a warning (see `decodeSubmitData`). Never throws: an
33
+ * unknown `$type` is skipped with a "dropped" warning, and a malformed input
34
+ * resolves to an empty card plus a "dropped" warning instead of throwing.
35
+ */
36
+ declare function toAdaptiveCard(node: unknown, _options?: ToAdaptiveCardOptions): AdaptiveCardResult;
37
+ //#endregion
38
+ export { ConversionContext, convertElement, convertRootToCard, convertSequence, toAdaptiveCard };
39
+ //# sourceMappingURL=toAdaptiveCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toAdaptiveCard.d.ts","names":[],"sources":["../../src/teams/toAdaptiveCard.ts"],"mappings":";;;;UAsCiB;WACN,UAAU;EACnB,cAAc;;iBAuTA,eACd,SAAS,qBACT,SAAS,mBACT,gBACC;;;;;;;;;;;;;;;iBAkRa,gBACd,MAAM,8BACN,SAAS,mBACT,gBACC;;iBA0Ea,kBACd,MAAM,4BAA4B,oBAClC,SAAS,oBACR;;;;;;;;;;iBAyBa,eACd,eACA,WAAW,wBACV"}
@@ -0,0 +1,496 @@
1
+ import { normalizeSpec } from "../ir.js";
2
+ import { buildCard, buildSubmitAction, clampReasonDetail, utf8ByteLength } from "./constants.js";
3
+ import { boundSpec } from "./boundSpec.js";
4
+ //#region src/teams/toAdaptiveCard.ts
5
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
6
+ const isElement = (node) => isRecord(node);
7
+ const asString = (value) => typeof value === "string" ? value : "";
8
+ const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
9
+ const SUBTLE_COLORS = /* @__PURE__ */ new Set([
10
+ "secondary",
11
+ "alpha-70",
12
+ "white",
13
+ "white-70",
14
+ "white-50"
15
+ ]);
16
+ const isSubtleColor = (color) => typeof color === "string" && SUBTLE_COLORS.has(color);
17
+ const BOLDER_WEIGHTS = /* @__PURE__ */ new Set([
18
+ "medium",
19
+ "semibold",
20
+ "bold"
21
+ ]);
22
+ const isBolderWeight = (weight) => typeof weight === "string" && BOLDER_WEIGHTS.has(weight);
23
+ const TEXT_SIZE_MAP = {
24
+ sm: "small",
25
+ lg: "medium",
26
+ xl: "large",
27
+ "2xl": "extraLarge",
28
+ "3xl": "extraLarge"
29
+ };
30
+ const warn = (context, code, component, detail) => {
31
+ context.warnings.push({
32
+ code,
33
+ component,
34
+ detail
35
+ });
36
+ };
37
+ /**
38
+ * The `aui` key is reserved for the submit envelope (see
39
+ * {@link buildSubmitData} and `decodeSubmitData`): Adaptive Cards merge
40
+ * every input's current value into the same submit object keyed by its
41
+ * `id`, so an input id of `aui` would clobber the envelope. Renamed to an
42
+ * unused id derived from `aui_` instead of being emitted as-is.
43
+ */
44
+ const RESERVED_INPUT_ID = "aui";
45
+ function reservedSafeId(id, component, context) {
46
+ const reserved = id === RESERVED_INPUT_ID;
47
+ const base = reserved ? `${RESERVED_INPUT_ID}_` : id;
48
+ let candidate = base;
49
+ let n = 2;
50
+ while (context.usedInputIds.has(candidate)) {
51
+ candidate = `${base}_${n}`;
52
+ n += 1;
53
+ }
54
+ if (reserved) warn(context, "clamped", component, `the input id "${RESERVED_INPUT_ID}" collides with the submit envelope's reserved key and was renamed to "${candidate}".`);
55
+ else if (candidate !== base) warn(context, "clamped", component, `the input id "${base}" was already used on this card and was renamed to "${candidate}".`);
56
+ context.usedInputIds.add(candidate);
57
+ return candidate;
58
+ }
59
+ /**
60
+ * The shared bounded-iteration primitive: slices `value` to `cap` entries
61
+ * without ever reading past that many indices, so a hostile array (sparse or
62
+ * proxied with a fabricated `length`) cannot stall the event loop.
63
+ */
64
+ const clampArray = (value, cap) => {
65
+ if (!Array.isArray(value)) return {
66
+ items: [],
67
+ truncated: false
68
+ };
69
+ return {
70
+ items: value.slice(0, cap),
71
+ truncated: value.length > cap
72
+ };
73
+ };
74
+ const normalizedList = (node) => {
75
+ if (node === void 0 || node === null) return [];
76
+ if (!Array.isArray(node)) return [node];
77
+ return node.flatMap((child) => normalizedList(child));
78
+ };
79
+ function textBlock(text, extra) {
80
+ return {
81
+ type: "TextBlock",
82
+ text,
83
+ wrap: true,
84
+ ...extra
85
+ };
86
+ }
87
+ function applyPending(element, separator, spacing) {
88
+ if (!separator && !spacing) return element;
89
+ return {
90
+ ...element,
91
+ ...separator ? { separator: true } : {},
92
+ ...spacing ? { spacing: "large" } : {}
93
+ };
94
+ }
95
+ const toChoice = (option) => {
96
+ if (!isRecord(option) || typeof option["value"] !== "string") return;
97
+ return {
98
+ title: asString(option["label"]),
99
+ value: option["value"]
100
+ };
101
+ };
102
+ const choicesFrom = (optionsProp, component, context) => {
103
+ const { items, truncated } = clampArray(optionsProp, 100);
104
+ if (truncated) warn(context, "clamped", component, `options were clamped to 100 entries.`);
105
+ const choices = [];
106
+ for (const item of items) {
107
+ const choice = toChoice(item);
108
+ if (choice !== void 0) choices.push(choice);
109
+ }
110
+ return choices;
111
+ };
112
+ function convertFacts(facts) {
113
+ return {
114
+ type: "FactSet",
115
+ facts: facts.map((fact) => ({
116
+ title: asString(fact.props["label"]),
117
+ value: asString(fact.props["value"])
118
+ }))
119
+ };
120
+ }
121
+ function convertButtons(buttons, context) {
122
+ if (buttons.length > 6) warn(context, "clamped", "Button", `actions beyond 6 were set to secondary mode.`);
123
+ return {
124
+ type: "ActionSet",
125
+ actions: buttons.map((button, index) => buildSubmitAction(asString(button.props["label"]), button.action, index >= 6 ? "secondary" : void 0))
126
+ };
127
+ }
128
+ function withCompanionSubmit(element, input, context) {
129
+ if (element.action === void 0) return [input];
130
+ warn(context, "fallback", element.type, "Teams inputs cannot dispatch on change; a companion submit action was appended.");
131
+ return [input, {
132
+ type: "ActionSet",
133
+ actions: [buildSubmitAction("Submit", element.action)]
134
+ }];
135
+ }
136
+ function cardFooterActionSet(props) {
137
+ const actions = ["confirm", "cancel"].map((key) => props[key]).filter(isRecord).map((footer) => buildSubmitAction(asString(footer["label"]), footer["$action"]));
138
+ return actions.length > 0 ? {
139
+ type: "ActionSet",
140
+ actions
141
+ } : void 0;
142
+ }
143
+ const ALERT_STYLE_MAP = {
144
+ info: "accent",
145
+ success: "good",
146
+ warning: "warning",
147
+ danger: "attention"
148
+ };
149
+ const stringifyCell = (value) => {
150
+ if (typeof value === "string") return value;
151
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
152
+ return "";
153
+ };
154
+ function convertTable(props, context) {
155
+ const { items: rawColumns, truncated: columnsTruncated } = clampArray(props["columns"], 20);
156
+ const { items: rawRows, truncated: rowsTruncated } = clampArray(props["rows"], 100);
157
+ if (columnsTruncated) warn(context, "clamped", "Table", `columns were clamped to 20 entries.`);
158
+ if (rowsTruncated) warn(context, "clamped", "Table", `rows were clamped to 100 entries.`);
159
+ const hasColumns = rawColumns.length > 0;
160
+ const columns = rawColumns.map(() => ({ width: 1 }));
161
+ const headerRow = hasColumns ? {
162
+ type: "TableRow",
163
+ cells: rawColumns.map((column) => ({
164
+ type: "TableCell",
165
+ items: [textBlock(isRecord(column) ? asString(column["label"]) : "")]
166
+ }))
167
+ } : void 0;
168
+ const dataRows = rawRows.map((row) => ({
169
+ type: "TableRow",
170
+ cells: (Array.isArray(row) ? row.slice(0, 20) : []).map((cell) => ({
171
+ type: "TableCell",
172
+ items: [textBlock(stringifyCell(cell))]
173
+ }))
174
+ }));
175
+ return {
176
+ type: "Table",
177
+ firstRowAsHeaders: hasColumns,
178
+ columns,
179
+ rows: headerRow !== void 0 ? [headerRow, ...dataRows] : dataRows
180
+ };
181
+ }
182
+ function convertListViewItem(element, context, depth) {
183
+ const items = convertSequence(element.children, context, depth + 1);
184
+ const selectAction = isRecord(element.action) ? buildSubmitAction("Open", element.action) : void 0;
185
+ return {
186
+ type: "Container",
187
+ items,
188
+ ...selectAction !== void 0 ? { selectAction } : {}
189
+ };
190
+ }
191
+ function convertElement(element, context, depth) {
192
+ const { props } = element;
193
+ switch (element.type) {
194
+ case "Header": return [textBlock(asString(props["text"]), {
195
+ size: "large",
196
+ weight: "bolder",
197
+ style: "heading"
198
+ })];
199
+ case "Text": {
200
+ const mappedSize = typeof props["size"] === "string" ? TEXT_SIZE_MAP[props["size"]] : void 0;
201
+ return [textBlock(asString(props["value"]), {
202
+ ...mappedSize !== void 0 ? { size: mappedSize } : {},
203
+ ...isBolderWeight(props["weight"]) ? { weight: "bolder" } : {},
204
+ ...isSubtleColor(props["color"]) ? { isSubtle: true } : {}
205
+ })];
206
+ }
207
+ case "Caption": return [textBlock(asString(props["value"]), {
208
+ size: "small",
209
+ isSubtle: true
210
+ })];
211
+ case "Badge": return [textBlock(asString(props["value"]), {
212
+ size: "small",
213
+ isSubtle: true
214
+ })];
215
+ case "Markdown": return [textBlock(asString(props["value"]))];
216
+ case "Fact": return [convertFacts([element])];
217
+ case "Image": {
218
+ const size = props["size"];
219
+ let mappedSize;
220
+ if (size === "sm") mappedSize = "small";
221
+ else if (size === "md") mappedSize = "medium";
222
+ else if (size === "lg") mappedSize = "large";
223
+ else if (typeof size === "number") warn(context, "dropped", "Image", "A numeric size was dropped.");
224
+ return [{
225
+ type: "Image",
226
+ url: asString(props["src"]),
227
+ altText: asString(props["alt"]),
228
+ ...mappedSize !== void 0 ? { size: mappedSize } : {}
229
+ }];
230
+ }
231
+ case "Divider":
232
+ case "Spacer":
233
+ case "Icon": return [];
234
+ case "Button": return [convertButtons([element], context)];
235
+ case "Select": {
236
+ const name = asString(props["name"]);
237
+ const placeholder = asString(props["placeholder"]);
238
+ const label = asString(props["label"]);
239
+ return withCompanionSubmit(element, {
240
+ type: "Input.ChoiceSet",
241
+ id: reservedSafeId(name || "select", "Select", context),
242
+ style: "compact",
243
+ choices: choicesFrom(props["options"], "Select", context),
244
+ ...placeholder ? { placeholder } : {},
245
+ ...label ? { label } : {}
246
+ }, context);
247
+ }
248
+ case "RadioGroup": {
249
+ const name = asString(props["name"]);
250
+ const label = asString(props["label"]);
251
+ const defaultValue = props["defaultValue"];
252
+ return withCompanionSubmit(element, {
253
+ type: "Input.ChoiceSet",
254
+ id: reservedSafeId(name || "radiogroup", "RadioGroup", context),
255
+ style: "expanded",
256
+ choices: choicesFrom(props["options"], "RadioGroup", context),
257
+ ...typeof defaultValue === "string" && defaultValue ? { value: defaultValue } : {},
258
+ ...label ? { label } : {}
259
+ }, context);
260
+ }
261
+ case "Checkbox": {
262
+ const name = asString(props["name"]);
263
+ const label = asString(props["label"]);
264
+ return withCompanionSubmit(element, {
265
+ type: "Input.Toggle",
266
+ id: reservedSafeId(name || label || "checkbox", "Checkbox", context),
267
+ title: label,
268
+ value: props["defaultChecked"] === true ? "true" : "false",
269
+ valueOn: "true",
270
+ valueOff: "false"
271
+ }, context);
272
+ }
273
+ case "Input": {
274
+ const name = asString(props["name"]);
275
+ const label = asString(props["label"]);
276
+ const placeholder = asString(props["placeholder"]);
277
+ return withCompanionSubmit(element, {
278
+ type: "Input.Text",
279
+ id: reservedSafeId(name || "input", "Input", context),
280
+ ...label ? { label } : {},
281
+ ...placeholder ? { placeholder } : {},
282
+ ...props["multiline"] === true ? { isMultiline: true } : {}
283
+ }, context);
284
+ }
285
+ case "DatePicker": {
286
+ const name = asString(props["name"]);
287
+ const label = asString(props["label"]);
288
+ const rawValue = props["value"];
289
+ const value = typeof rawValue === "string" && DATE_PATTERN.test(rawValue) ? rawValue : void 0;
290
+ const rawMin = props["min"];
291
+ const min = typeof rawMin === "string" && DATE_PATTERN.test(rawMin) ? rawMin : void 0;
292
+ const rawMax = props["max"];
293
+ const max = typeof rawMax === "string" && DATE_PATTERN.test(rawMax) ? rawMax : void 0;
294
+ return withCompanionSubmit(element, {
295
+ type: "Input.Date",
296
+ id: reservedSafeId(name || "datepicker", "DatePicker", context),
297
+ ...label ? { label } : {},
298
+ ...value !== void 0 ? { value } : {},
299
+ ...min !== void 0 ? { min } : {},
300
+ ...max !== void 0 ? { max } : {}
301
+ }, context);
302
+ }
303
+ case "Form": return [...convertSequence(element.children, context, depth + 1), {
304
+ type: "ActionSet",
305
+ actions: [buildSubmitAction("Submit", element.action)]
306
+ }];
307
+ case "Card": {
308
+ const titleText = asString(props["title"]);
309
+ const items = [];
310
+ if (titleText) items.push(textBlock(titleText, {
311
+ size: "large",
312
+ weight: "bolder",
313
+ style: "heading"
314
+ }));
315
+ items.push(...convertSequence(element.children, context, depth + 1));
316
+ const container = {
317
+ type: "Container",
318
+ style: "default",
319
+ items
320
+ };
321
+ const actionSet = cardFooterActionSet(props);
322
+ return actionSet !== void 0 ? [container, actionSet] : [container];
323
+ }
324
+ case "Col":
325
+ case "Box": return [{
326
+ type: "Container",
327
+ items: convertSequence(element.children, context, depth + 1)
328
+ }];
329
+ case "Row": {
330
+ const children = normalizedList(element.children);
331
+ if (children.length > 3) warn(context, "clamped", "Row", "Teams recommends at most 3 columns.");
332
+ return [{
333
+ type: "ColumnSet",
334
+ columns: children.map((child) => ({
335
+ type: "Column",
336
+ width: "auto",
337
+ items: convertSequence(child, context, depth + 1)
338
+ }))
339
+ }];
340
+ }
341
+ case "ListView": {
342
+ const containers = [];
343
+ for (const child of normalizedList(element.children)) {
344
+ if (!isElement(child)) continue;
345
+ if (child.type === "ListViewItem") containers.push(convertListViewItem(child, context, depth + 1));
346
+ else convertElement(child, context, depth + 1);
347
+ }
348
+ return containers.map((container, index) => index > 0 ? {
349
+ ...container,
350
+ separator: true
351
+ } : container);
352
+ }
353
+ case "ListViewItem": return [convertListViewItem(element, context, depth)];
354
+ case "Table": return [convertTable(props, context)];
355
+ case "Alert": {
356
+ const tone = props["tone"];
357
+ const style = typeof tone === "string" ? ALERT_STYLE_MAP[tone] ?? "default" : "default";
358
+ const titleText = asString(props["title"]);
359
+ const description = asString(props["description"]);
360
+ const items = [];
361
+ if (titleText) items.push(textBlock(titleText, { weight: "bolder" }));
362
+ if (description) items.push(textBlock(description));
363
+ return [{
364
+ type: "Container",
365
+ style,
366
+ items
367
+ }];
368
+ }
369
+ case "Carousel":
370
+ warn(context, "fallback", "Carousel", "A carousel was rendered as sequential cards because it is not at the root.");
371
+ return normalizedList(element.children).filter((child) => isElement(child) && child.type === "Card").flatMap((card) => convertElement(card, context, depth + 1));
372
+ case "Chart":
373
+ warn(context, "dropped", "Chart", "Chart was replaced by a Teams omission note.");
374
+ return [textBlock("Chart omitted on Teams.", {
375
+ isSubtle: true,
376
+ size: "small"
377
+ })];
378
+ default:
379
+ warn(context, "dropped", element.type, "Unknown component type was dropped.");
380
+ return [];
381
+ }
382
+ }
383
+ /**
384
+ * Walks a normalized sequence into card body elements. Nested arrays are
385
+ * flattened into one flat sibling sequence first (via {@link normalizedList}),
386
+ * so a `Divider`/`Fact`/`Button` run merges correctly even when the model
387
+ * wrapped some of its siblings in their own array. Consecutive `Fact`
388
+ * siblings merge into one FactSet and consecutive `Button` siblings merge
389
+ * into one ActionSet; a `Divider`/`Spacer` emits nothing itself but sets
390
+ * `separator`/`spacing` on the next emitted sibling (a trailing or orphan
391
+ * one is dropped silently, since there is no sibling left to carry it).
392
+ * `depth` advances by exactly one per element-nesting level: this call does
393
+ * not itself add a level, since flattening a sibling array or converting a
394
+ * sibling element is not a descent; only a container's own recursive
395
+ * `convertSequence(children, depth + 1)` call is.
396
+ */
397
+ function convertSequence(node, context, depth) {
398
+ if (node === void 0 || node === null || depth > 64) return [];
399
+ const nodes = normalizedList(node);
400
+ const items = [];
401
+ let pendingSeparator = false;
402
+ let pendingSpacing = false;
403
+ const emit = (converted) => {
404
+ const [first, ...rest] = converted;
405
+ if (first === void 0) return;
406
+ items.push(applyPending(first, pendingSeparator, pendingSpacing), ...rest);
407
+ pendingSeparator = false;
408
+ pendingSpacing = false;
409
+ };
410
+ for (let index = 0; index < nodes.length;) {
411
+ const current = nodes[index];
412
+ if (current === void 0 || current === null) {
413
+ index += 1;
414
+ continue;
415
+ }
416
+ if (isElement(current) && current.type === "Divider") {
417
+ pendingSeparator = true;
418
+ index += 1;
419
+ continue;
420
+ }
421
+ if (isElement(current) && current.type === "Spacer") {
422
+ pendingSpacing = true;
423
+ index += 1;
424
+ continue;
425
+ }
426
+ if (isElement(current) && current.type === "Fact") {
427
+ const facts = [];
428
+ while (index < nodes.length) {
429
+ const candidate = nodes[index];
430
+ if (!candidate || !isElement(candidate) || candidate.type !== "Fact") break;
431
+ facts.push(candidate);
432
+ index += 1;
433
+ }
434
+ emit([convertFacts(facts)]);
435
+ continue;
436
+ }
437
+ if (isElement(current) && current.type === "Button") {
438
+ const buttons = [];
439
+ while (index < nodes.length) {
440
+ const candidate = nodes[index];
441
+ if (!candidate || !isElement(candidate) || candidate.type !== "Button") break;
442
+ buttons.push(candidate);
443
+ index += 1;
444
+ }
445
+ emit([convertButtons(buttons, context)]);
446
+ continue;
447
+ }
448
+ if (typeof current === "string" || typeof current === "number") emit([textBlock(String(current))]);
449
+ else emit(convertElement(current, context, depth));
450
+ index += 1;
451
+ }
452
+ return items;
453
+ }
454
+ /** Builds a full card (schema envelope plus body) from an already-normalized root, checking the soft payload budget. */
455
+ function convertRootToCard(root, context) {
456
+ context.usedInputIds = /* @__PURE__ */ new Set();
457
+ const card = buildCard(convertSequence(root, context, 0));
458
+ const size = utf8ByteLength(JSON.stringify(card));
459
+ if (size > 8e4) warn(context, "clamped", "Root", `the card is ${size} bytes, exceeding Teams' 100 KB bot message limit.`);
460
+ return card;
461
+ }
462
+ /**
463
+ * Converts a generative-ui tree into a Microsoft Teams Adaptive Card and
464
+ * non-fatal downgrade warnings. Sizes, weights, and colors map to Adaptive
465
+ * Card's semantic enums rather than raw values. An Input/Select/RadioGroup/
466
+ * Checkbox/DatePicker whose id would be the reserved {@link RESERVED_INPUT_ID}
467
+ * is renamed with a warning (see `decodeSubmitData`). Never throws: an
468
+ * unknown `$type` is skipped with a "dropped" warning, and a malformed input
469
+ * resolves to an empty card plus a "dropped" warning instead of throwing.
470
+ */
471
+ function toAdaptiveCard(node, _options) {
472
+ const context = {
473
+ warnings: [],
474
+ usedInputIds: /* @__PURE__ */ new Set()
475
+ };
476
+ try {
477
+ const { root } = normalizeSpec(boundSpec(node, (reason) => warn(context, "clamped", "Root", clampReasonDetail(reason))));
478
+ return {
479
+ card: convertRootToCard(root, context),
480
+ warnings: context.warnings
481
+ };
482
+ } catch {
483
+ return {
484
+ card: buildCard([]),
485
+ warnings: [{
486
+ code: "dropped",
487
+ component: "Root",
488
+ detail: "Input could not be converted."
489
+ }]
490
+ };
491
+ }
492
+ }
493
+ //#endregion
494
+ export { convertElement, convertRootToCard, convertSequence, toAdaptiveCard };
495
+
496
+ //# sourceMappingURL=toAdaptiveCard.js.map