@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
@@ -202,6 +202,22 @@ describe("toAdaptiveCard", () => {
202
202
  expect((card.body[0] as TeamsFactSet).facts).toHaveLength(5);
203
203
  });
204
204
 
205
+ it("appends a directional fact delta to the value text", () => {
206
+ const { card } = toAdaptiveCard({
207
+ $type: "Fact",
208
+ label: "Revenue",
209
+ value: "$12.4k",
210
+ delta: "8%",
211
+ trend: "down",
212
+ });
213
+ expect(card.body).toEqual([
214
+ {
215
+ type: "FactSet",
216
+ facts: [{ title: "Revenue", value: "$12.4k (↓ 8%)" }],
217
+ },
218
+ ]);
219
+ });
220
+
205
221
  it("breaks Fact merging on a non-Fact sibling", () => {
206
222
  const { card } = toAdaptiveCard([
207
223
  { $type: "Fact", label: "A", value: "1" },
@@ -481,6 +497,24 @@ describe("toAdaptiveCard", () => {
481
497
  expect((card.body[0] as TeamsInputChoiceSet).id).toBe("select");
482
498
  });
483
499
 
500
+ it("sets value from a non-empty defaultValue", () => {
501
+ const { card } = toAdaptiveCard({
502
+ $type: "Select",
503
+ defaultValue: "blue",
504
+ options: [{ label: "Blue", value: "blue" }],
505
+ });
506
+ expect((card.body[0] as TeamsInputChoiceSet).value).toBe("blue");
507
+ });
508
+
509
+ it("omits value when defaultValue is empty", () => {
510
+ const { card } = toAdaptiveCard({
511
+ $type: "Select",
512
+ defaultValue: "",
513
+ options: [{ label: "Blue", value: "blue" }],
514
+ });
515
+ expect((card.body[0] as TeamsInputChoiceSet).value).toBeUndefined();
516
+ });
517
+
484
518
  it("does not dispatch a prop array through Symbol.species", () => {
485
519
  let dispatches = 0;
486
520
  const injected = Array.from(
@@ -556,6 +590,86 @@ describe("toAdaptiveCard", () => {
556
590
  },
557
591
  ]);
558
592
  });
593
+
594
+ it("appends option descriptions to choice titles", () => {
595
+ const { card } = toAdaptiveCard({
596
+ $type: "RadioGroup",
597
+ options: [
598
+ {
599
+ label: "Free",
600
+ description: "For personal projects",
601
+ value: "free",
602
+ },
603
+ ],
604
+ });
605
+ expect((card.body[0] as TeamsInputChoiceSet).choices).toEqual([
606
+ { title: "Free: For personal projects", value: "free" },
607
+ ]);
608
+ });
609
+ });
610
+
611
+ describe("CheckboxGroup", () => {
612
+ it("renders an expanded multi-select Input.ChoiceSet with the default values joined by commas", () => {
613
+ const { card } = toAdaptiveCard({
614
+ $type: "CheckboxGroup",
615
+ name: "toppings",
616
+ label: "Toppings",
617
+ defaultValue: ["basil", 7, "onion"],
618
+ options: [
619
+ { label: "Basil", value: "basil" },
620
+ { label: "Olives", value: "olives" },
621
+ { label: "Onion", value: "onion" },
622
+ ],
623
+ });
624
+ expect(card.body).toEqual([
625
+ {
626
+ type: "Input.ChoiceSet",
627
+ id: "toppings",
628
+ style: "expanded",
629
+ isMultiSelect: true,
630
+ choices: [
631
+ { title: "Basil", value: "basil" },
632
+ { title: "Olives", value: "olives" },
633
+ { title: "Onion", value: "onion" },
634
+ ],
635
+ value: "basil,onion",
636
+ label: "Toppings",
637
+ },
638
+ ]);
639
+ });
640
+
641
+ it("falls back to the checkboxgroup id and appends a companion submit for an action", () => {
642
+ const { card, warnings } = toAdaptiveCard({
643
+ $type: "CheckboxGroup",
644
+ options: [{ label: "Basil", value: "basil" }],
645
+ $action: { type: "pick" },
646
+ });
647
+ expect(card.body).toEqual([
648
+ {
649
+ type: "Input.ChoiceSet",
650
+ id: "checkboxgroup",
651
+ style: "expanded",
652
+ isMultiSelect: true,
653
+ choices: [{ title: "Basil", value: "basil" }],
654
+ },
655
+ {
656
+ type: "ActionSet",
657
+ actions: [
658
+ {
659
+ type: "Action.Submit",
660
+ title: "Submit",
661
+ data: { aui: { type: "pick" } },
662
+ },
663
+ ],
664
+ },
665
+ ]);
666
+ expect(warnings).toContainEqual({
667
+ code: "fallback",
668
+ component: "CheckboxGroup",
669
+ detail:
670
+ "Teams inputs cannot dispatch on change; a companion submit action was appended.",
671
+ });
672
+ });
559
673
  });
560
674
 
561
675
  describe("Checkbox", () => {
@@ -583,9 +697,58 @@ describe("toAdaptiveCard", () => {
583
697
  expect((card.body[0] as TeamsInputToggle).id).toBe("Agree");
584
698
  expect((card.body[0] as TeamsInputToggle).value).toBe("false");
585
699
  });
700
+
701
+ it("maps a switch like a checkbox", () => {
702
+ const { card } = toAdaptiveCard({
703
+ $type: "Checkbox",
704
+ label: "Subscribe",
705
+ variant: "switch",
706
+ });
707
+ expect(card.body[0]).toMatchObject({
708
+ type: "Input.Toggle",
709
+ title: "Subscribe",
710
+ });
711
+ });
712
+ });
713
+
714
+ describe("Slider", () => {
715
+ it("renders an Input.Number with its bounds, value, and label", () => {
716
+ const { card } = toAdaptiveCard({
717
+ $type: "Slider",
718
+ name: "quantity",
719
+ label: "Quantity",
720
+ min: 1,
721
+ max: 12,
722
+ defaultValue: 3,
723
+ });
724
+ expect(card.body[0]).toEqual({
725
+ type: "Input.Number",
726
+ id: "quantity",
727
+ label: "Quantity",
728
+ min: 1,
729
+ max: 12,
730
+ value: 3,
731
+ });
732
+ });
586
733
  });
587
734
 
588
735
  describe("Input", () => {
736
+ it("maps password and number inputs while preserving string values", () => {
737
+ const { card } = toAdaptiveCard([
738
+ {
739
+ $type: "Input",
740
+ name: "quantity",
741
+ inputType: "number",
742
+ defaultValue: "2.5",
743
+ },
744
+ { $type: "Input", name: "password", inputType: "password" },
745
+ ]);
746
+ expect(card.body).toEqual([
747
+ { type: "Input.Text", id: "quantity", value: "2.5" },
748
+ { type: "Input.Text", id: "password", style: "password" },
749
+ ]);
750
+ });
751
+
589
752
  it("renders an Input.Text", () => {
590
753
  const { card } = toAdaptiveCard({
591
754
  $type: "Input",
@@ -612,6 +775,38 @@ describe("toAdaptiveCard", () => {
612
775
  expect((card.body[0] as TeamsInputText).isMultiline).toBe(true);
613
776
  });
614
777
 
778
+ it("keeps a multiline password input single-line", () => {
779
+ const { card } = toAdaptiveCard({
780
+ $type: "Input",
781
+ name: "password",
782
+ inputType: "password",
783
+ multiline: true,
784
+ });
785
+ expect(card.body[0]).toEqual({
786
+ type: "Input.Text",
787
+ id: "password",
788
+ style: "password",
789
+ });
790
+ });
791
+
792
+ it("sets value from a non-empty defaultValue", () => {
793
+ const { card } = toAdaptiveCard({
794
+ $type: "Input",
795
+ name: "notes",
796
+ defaultValue: "Draft reply",
797
+ });
798
+ expect((card.body[0] as TeamsInputText).value).toBe("Draft reply");
799
+ });
800
+
801
+ it("omits value when defaultValue is empty", () => {
802
+ const { card } = toAdaptiveCard({
803
+ $type: "Input",
804
+ name: "notes",
805
+ defaultValue: "",
806
+ });
807
+ expect((card.body[0] as TeamsInputText).value).toBeUndefined();
808
+ });
809
+
615
810
  it("emits no ActionSet when $action is absent", () => {
616
811
  const { card } = toAdaptiveCard({ $type: "Input", name: "email" });
617
812
  expect(card.body).toHaveLength(1);
@@ -663,7 +858,9 @@ describe("toAdaptiveCard", () => {
663
858
  ["Input", { $type: "Input", name: "aui" }],
664
859
  ["Select", { $type: "Select", name: "aui", options: [] }],
665
860
  ["RadioGroup", { $type: "RadioGroup", name: "aui", options: [] }],
861
+ ["CheckboxGroup", { $type: "CheckboxGroup", name: "aui", options: [] }],
666
862
  ["Checkbox", { $type: "Checkbox", name: "aui", label: "Agree" }],
863
+ ["Slider", { $type: "Slider", name: "aui", min: 0, max: 10 }],
667
864
  ["DatePicker", { $type: "DatePicker", name: "aui" }],
668
865
  ] as const)(
669
866
  "renames a %s named aui to aui_ with a warning",
@@ -1142,6 +1339,28 @@ describe("toAdaptiveCard", () => {
1142
1339
  ]);
1143
1340
  });
1144
1341
 
1342
+ it("formats table cells from their columns", () => {
1343
+ const { card } = toAdaptiveCard({
1344
+ $type: "Table",
1345
+ columns: [
1346
+ { label: "Number", format: { kind: "number", decimals: 1 } },
1347
+ {
1348
+ label: "Revenue",
1349
+ format: { kind: "currency", currency: "USD", decimals: 2 },
1350
+ },
1351
+ { label: "Share", format: { kind: "percent", decimals: 0 } },
1352
+ { label: "Date", format: { kind: "date" } },
1353
+ ],
1354
+ rows: [[1234.5, 12.4, 0.08, "2024-01-02"]],
1355
+ });
1356
+ const table = card.body[0] as TeamsTable;
1357
+ expect(
1358
+ table.rows[1]?.cells.map(
1359
+ (cell) => (cell.items[0] as TeamsTextBlock).text,
1360
+ ),
1361
+ ).toEqual(["1,234.5", "$12.40", "8%", "Jan 2, 2024"]);
1362
+ });
1363
+
1145
1364
  it("sets firstRowAsHeaders false and emits no header row when there are no columns", () => {
1146
1365
  const { card } = toAdaptiveCard({
1147
1366
  $type: "Table",
@@ -11,6 +11,11 @@ import {
11
11
  type NormalizedUIElement,
12
12
  type NormalizedUINode,
13
13
  } from "../ir";
14
+ import {
15
+ factTrend,
16
+ formatFactDelta,
17
+ formatValue,
18
+ } from "../vocabulary/formatValue";
14
19
  import {
15
20
  CHOICE_OPTION_CAP,
16
21
  PAYLOAD_SOFT_CAP,
@@ -52,6 +57,9 @@ const isRecord = (value: unknown): value is Record<string, unknown> =>
52
57
  const asString = (value: unknown): string =>
53
58
  typeof value === "string" ? value : "";
54
59
 
60
+ const asFiniteNumber = (value: unknown): number | undefined =>
61
+ typeof value === "number" && Number.isFinite(value) ? value : undefined;
62
+
55
63
  const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
56
64
 
57
65
  const SUBTLE_COLORS = new Set([
@@ -171,7 +179,14 @@ const toChoice = (option: unknown): TeamsInputChoice | undefined => {
171
179
  if (!isRecord(option) || typeof option["value"] !== "string") {
172
180
  return undefined;
173
181
  }
174
- return { title: asString(option["label"]), value: option["value"] };
182
+ const label = asString(option["label"]);
183
+ const description = asString(option["description"]);
184
+ return {
185
+ title: description
186
+ ? [label, description].filter(Boolean).join(": ")
187
+ : label,
188
+ value: option["value"],
189
+ };
175
190
  };
176
191
 
177
192
  const choicesFrom = (
@@ -206,10 +221,18 @@ const choicesFrom = (
206
221
  };
207
222
 
208
223
  function convertFacts(facts: readonly NormalizedUIElement[]): TeamsCardElement {
209
- const set: TeamsFact[] = facts.map((fact) => ({
210
- title: asString(fact.props["label"]),
211
- value: asString(fact.props["value"]),
212
- }));
224
+ const set: TeamsFact[] = facts.map((fact) => {
225
+ const value = asString(fact.props["value"]);
226
+ const delta = fact.props["delta"];
227
+ const deltaText =
228
+ typeof delta === "string"
229
+ ? formatFactDelta(delta, factTrend(delta, fact.props["trend"]))
230
+ : undefined;
231
+ return {
232
+ title: asString(fact.props["label"]),
233
+ value: deltaText === undefined ? value : `${value} (${deltaText})`,
234
+ };
235
+ });
213
236
  return { type: "FactSet", facts: set };
214
237
  }
215
238
 
@@ -277,13 +300,6 @@ const ALERT_STYLE_MAP: Record<string, TeamsContainerStyle> = {
277
300
  danger: "attention",
278
301
  };
279
302
 
280
- const stringifyCell = (value: unknown): string => {
281
- if (typeof value === "string") return value;
282
- if (typeof value === "number" || typeof value === "boolean")
283
- return String(value);
284
- return "";
285
- };
286
-
287
303
  function convertTable(
288
304
  props: Readonly<Record<string, unknown>>,
289
305
  context: ConversionContext,
@@ -342,9 +358,18 @@ function convertTable(
342
358
  cells: (Array.isArray(row)
343
359
  ? copyBounded(row, TABLE_COLUMN_CAP).items
344
360
  : []
345
- ).map((cell) => ({
361
+ ).map((cell, index) => ({
346
362
  type: "TableCell" as const,
347
- items: [textBlock(stringifyCell(cell))],
363
+ items: [
364
+ textBlock(
365
+ formatValue(
366
+ cell,
367
+ isRecord(rawColumns[index])
368
+ ? rawColumns[index]["format"]
369
+ : undefined,
370
+ ),
371
+ ),
372
+ ],
348
373
  })),
349
374
  }));
350
375
 
@@ -465,11 +490,15 @@ export function convertElement(
465
490
  const name = asString(props["name"]);
466
491
  const placeholder = asString(props["placeholder"]);
467
492
  const label = asString(props["label"]);
493
+ const defaultValue = props["defaultValue"];
468
494
  const input: TeamsCardElement = {
469
495
  type: "Input.ChoiceSet",
470
496
  id: reservedSafeId(name || "select", "Select", context),
471
497
  style: "compact",
472
498
  choices: choicesFrom(props["options"], "Select", context),
499
+ ...(typeof defaultValue === "string" && defaultValue
500
+ ? { value: defaultValue }
501
+ : {}),
473
502
  ...(placeholder ? { placeholder } : {}),
474
503
  ...(label ? { label } : {}),
475
504
  };
@@ -491,6 +520,26 @@ export function convertElement(
491
520
  };
492
521
  return withCompanionSubmit(element, input, context);
493
522
  }
523
+ case "CheckboxGroup": {
524
+ const name = asString(props["name"]);
525
+ const label = asString(props["label"]);
526
+ const checked = Array.isArray(props["defaultValue"])
527
+ ? props["defaultValue"].filter(
528
+ (value): value is string =>
529
+ typeof value === "string" && value !== "",
530
+ )
531
+ : [];
532
+ const input: TeamsCardElement = {
533
+ type: "Input.ChoiceSet",
534
+ id: reservedSafeId(name || "checkboxgroup", "CheckboxGroup", context),
535
+ style: "expanded",
536
+ isMultiSelect: true,
537
+ choices: choicesFrom(props["options"], "CheckboxGroup", context),
538
+ ...(checked.length > 0 ? { value: checked.join(",") } : {}),
539
+ ...(label ? { label } : {}),
540
+ };
541
+ return withCompanionSubmit(element, input, context);
542
+ }
494
543
  case "Checkbox": {
495
544
  const name = asString(props["name"]);
496
545
  const label = asString(props["label"]);
@@ -504,16 +553,40 @@ export function convertElement(
504
553
  };
505
554
  return withCompanionSubmit(element, input, context);
506
555
  }
556
+ case "Slider": {
557
+ const name = asString(props["name"]);
558
+ const label = asString(props["label"]);
559
+ const min = asFiniteNumber(props["min"]);
560
+ const max = asFiniteNumber(props["max"]);
561
+ const defaultValue = asFiniteNumber(props["defaultValue"]);
562
+ const input = {
563
+ type: "Input.Number",
564
+ id: reservedSafeId(name || "slider", "Slider", context),
565
+ ...(label ? { label } : {}),
566
+ ...(min !== undefined ? { min } : {}),
567
+ ...(max !== undefined ? { max } : {}),
568
+ ...(defaultValue !== undefined ? { value: defaultValue } : {}),
569
+ } as unknown as TeamsCardElement;
570
+ return withCompanionSubmit(element, input, context);
571
+ }
507
572
  case "Input": {
508
573
  const name = asString(props["name"]);
509
574
  const label = asString(props["label"]);
510
575
  const placeholder = asString(props["placeholder"]);
576
+ const defaultValue = props["defaultValue"];
511
577
  const input: TeamsCardElement = {
512
578
  type: "Input.Text",
513
579
  id: reservedSafeId(name || "input", "Input", context),
580
+ ...(props["inputType"] === "password" ? { style: "password" } : {}),
514
581
  ...(label ? { label } : {}),
515
582
  ...(placeholder ? { placeholder } : {}),
516
- ...(props["multiline"] === true ? { isMultiline: true } : {}),
583
+ ...(typeof defaultValue === "string" && defaultValue
584
+ ? { value: defaultValue }
585
+ : {}),
586
+ ...(props["multiline"] === true &&
587
+ (props["inputType"] === undefined || props["inputType"] === "text")
588
+ ? { isMultiline: true }
589
+ : {}),
517
590
  };
518
591
  return withCompanionSubmit(element, input, context);
519
592
  }
@@ -785,10 +858,11 @@ export function convertRootToCard(
785
858
  * Converts a generative-ui tree into a Microsoft Teams Adaptive Card and
786
859
  * non-fatal conversion warnings. Sizes, weights, and colors map to Adaptive
787
860
  * Card's semantic enums rather than raw values. An Input/Select/RadioGroup/
788
- * Checkbox/DatePicker whose id would be the reserved {@link RESERVED_INPUT_ID}
789
- * is renamed with a warning (see `decodeSubmitData`). Never throws: an
790
- * unknown `$type` is skipped with a "dropped" warning, and a malformed input
791
- * resolves to an empty card plus a "dropped" warning instead of throwing.
861
+ * CheckboxGroup/Checkbox/Slider/DatePicker whose id would be the reserved
862
+ * {@link RESERVED_INPUT_ID} is renamed with a warning (see `decodeSubmitData`).
863
+ * Never throws: an unknown `$type` is skipped with a "dropped" warning, and a
864
+ * malformed input resolves to an empty card plus a "dropped" warning instead
865
+ * of throwing.
792
866
  */
793
867
  export function toAdaptiveCard(
794
868
  node: unknown,
@@ -109,11 +109,12 @@ export interface TeamsInputChoice {
109
109
  readonly value: string;
110
110
  }
111
111
 
112
- /** A dropdown (`compact`) or radio group (`expanded`) input. */
112
+ /** A dropdown (`compact`), radio group (`expanded`), or checkbox group (`expanded` with `isMultiSelect`) input. */
113
113
  export interface TeamsInputChoiceSet {
114
114
  readonly type: "Input.ChoiceSet";
115
115
  readonly id: string;
116
116
  readonly style: "compact" | "expanded";
117
+ readonly isMultiSelect?: true;
117
118
  readonly choices: readonly TeamsInputChoice[];
118
119
  readonly placeholder?: string;
119
120
  readonly label?: string;
@@ -138,8 +139,10 @@ export interface TeamsInputToggle {
138
139
  export interface TeamsInputText {
139
140
  readonly type: "Input.Text";
140
141
  readonly id: string;
142
+ readonly style?: "password";
141
143
  readonly label?: string;
142
144
  readonly placeholder?: string;
145
+ readonly value?: string;
143
146
  readonly isMultiline?: true;
144
147
  readonly separator?: true;
145
148
  readonly spacing?: "large";