@assistant-ui/react-generative-ui 0.0.21 → 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.
- package/LICENSE +1 -1
- package/dist/JSONGenerativeUI.client.d.ts.map +1 -1
- package/dist/JSONGenerativeUI.client.js +30 -13
- package/dist/JSONGenerativeUI.client.js.map +1 -1
- package/dist/RadioGroupScope.d.ts +6 -0
- package/dist/RadioGroupScope.d.ts.map +1 -0
- package/dist/RadioGroupScope.js +25 -0
- package/dist/RadioGroupScope.js.map +1 -0
- package/dist/a2ui/BindingContext.d.ts +11 -0
- package/dist/a2ui/BindingContext.d.ts.map +1 -0
- package/dist/a2ui/BindingContext.js +12 -0
- package/dist/a2ui/BindingContext.js.map +1 -0
- package/dist/a2ui/PresentRenderer.d.ts +11 -0
- package/dist/a2ui/PresentRenderer.d.ts.map +1 -0
- package/dist/a2ui/PresentRenderer.js +39 -0
- package/dist/a2ui/PresentRenderer.js.map +1 -0
- package/dist/a2ui/convert.d.ts +6 -0
- package/dist/a2ui/convert.d.ts.map +1 -1
- package/dist/a2ui/convert.js +344 -83
- package/dist/a2ui/convert.js.map +1 -1
- package/dist/a2ui/dataModel.d.ts +9 -0
- package/dist/a2ui/dataModel.d.ts.map +1 -0
- package/dist/a2ui/dataModel.js +44 -0
- package/dist/a2ui/dataModel.js.map +1 -0
- package/dist/a2ui/reducer.d.ts +5 -0
- package/dist/a2ui/reducer.d.ts.map +1 -1
- package/dist/a2ui/reducer.js +3 -3
- package/dist/a2ui/reducer.js.map +1 -1
- package/dist/a2ui/types.d.ts +8 -2
- package/dist/a2ui/types.d.ts.map +1 -1
- package/dist/a2ui/types.js.map +1 -1
- package/dist/a2ui/valueFunctions.d.ts +16 -0
- package/dist/a2ui/valueFunctions.d.ts.map +1 -0
- package/dist/a2ui/valueFunctions.js +332 -0
- package/dist/a2ui/valueFunctions.js.map +1 -0
- package/dist/actionRegistry.d.ts +1 -1
- package/dist/actionRegistry.js.map +1 -1
- package/dist/answeredValues.d.ts +16 -0
- package/dist/answeredValues.d.ts.map +1 -0
- package/dist/answeredValues.js +37 -0
- package/dist/answeredValues.js.map +1 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +3 -1
- package/dist/constants.js.map +1 -1
- package/dist/fieldReferences.d.ts +9 -0
- package/dist/fieldReferences.d.ts.map +1 -0
- package/dist/fieldReferences.js +53 -0
- package/dist/fieldReferences.js.map +1 -0
- package/dist/generativeUIToJSX.js +3 -3
- package/dist/generativeUIToJSX.js.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.server.js +2 -2
- package/dist/index.shared.js +2 -2
- package/dist/ir.d.ts +1 -1
- package/dist/ir.js.map +1 -1
- package/dist/renderGenerativeUI.d.ts.map +1 -1
- package/dist/renderGenerativeUI.js +12 -1
- package/dist/renderGenerativeUI.js.map +1 -1
- package/dist/slack/constants.d.ts +2 -0
- package/dist/slack/constants.d.ts.map +1 -1
- package/dist/slack/constants.js +3 -1
- package/dist/slack/constants.js.map +1 -1
- package/dist/slack/decodeBlockAction.d.ts +3 -1
- package/dist/slack/decodeBlockAction.d.ts.map +1 -1
- package/dist/slack/decodeBlockAction.js +58 -7
- package/dist/slack/decodeBlockAction.js.map +1 -1
- package/dist/slack/fromSlackBlocks.d.ts.map +1 -1
- package/dist/slack/fromSlackBlocks.js +20 -3
- package/dist/slack/fromSlackBlocks.js.map +1 -1
- package/dist/slack/toSlackBlocks.d.ts.map +1 -1
- package/dist/slack/toSlackBlocks.js +187 -19
- package/dist/slack/toSlackBlocks.js.map +1 -1
- package/dist/slack/types.d.ts +4 -0
- package/dist/slack/types.d.ts.map +1 -1
- package/dist/teams/constants.d.ts +1 -1
- package/dist/teams/constants.d.ts.map +1 -1
- package/dist/teams/constants.js +1 -1
- package/dist/teams/constants.js.map +1 -1
- package/dist/teams/decodeSubmitData.d.ts +4 -0
- package/dist/teams/decodeSubmitData.d.ts.map +1 -1
- package/dist/teams/decodeSubmitData.js +6 -1
- package/dist/teams/decodeSubmitData.js.map +1 -1
- package/dist/teams/toAdaptiveCard.d.ts +5 -4
- package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
- package/dist/teams/toAdaptiveCard.js +56 -17
- package/dist/teams/toAdaptiveCard.js.map +1 -1
- package/dist/teams/types.d.ts +4 -1
- package/dist/teams/types.d.ts.map +1 -1
- package/dist/vocabulary/alert.d.ts +9 -2
- package/dist/vocabulary/alert.d.ts.map +1 -1
- package/dist/vocabulary/alert.js +116 -19
- package/dist/vocabulary/alert.js.map +1 -1
- package/dist/vocabulary/collectFormValues.d.ts +2 -1
- package/dist/vocabulary/collectFormValues.d.ts.map +1 -1
- package/dist/vocabulary/collectFormValues.js +10 -3
- package/dist/vocabulary/collectFormValues.js.map +1 -1
- package/dist/vocabulary/data.d.ts +22 -5
- package/dist/vocabulary/data.d.ts.map +1 -1
- package/dist/vocabulary/data.js +278 -83
- package/dist/vocabulary/data.js.map +1 -1
- package/dist/vocabulary/dispatch.d.ts +3 -2
- package/dist/vocabulary/dispatch.d.ts.map +1 -1
- package/dist/vocabulary/dispatch.js +33 -4
- package/dist/vocabulary/dispatch.js.map +1 -1
- package/dist/vocabulary/fact.d.ts +8 -1
- package/dist/vocabulary/fact.d.ts.map +1 -1
- package/dist/vocabulary/fact.js +37 -11
- package/dist/vocabulary/fact.js.map +1 -1
- package/dist/vocabulary/form.d.ts.map +1 -1
- package/dist/vocabulary/form.js +3 -3
- package/dist/vocabulary/form.js.map +1 -1
- package/dist/vocabulary/formatValue.d.ts +7 -0
- package/dist/vocabulary/formatValue.d.ts.map +1 -0
- package/dist/vocabulary/formatValue.js +76 -0
- package/dist/vocabulary/formatValue.js.map +1 -0
- package/dist/vocabulary/icon.d.ts +1 -1
- package/dist/vocabulary/interactive.d.ts +137 -8
- package/dist/vocabulary/interactive.d.ts.map +1 -1
- package/dist/vocabulary/interactive.js +404 -103
- package/dist/vocabulary/interactive.js.map +1 -1
- package/dist/vocabulary/layout.d.ts.map +1 -1
- package/dist/vocabulary/layout.js +4 -4
- package/dist/vocabulary/layout.js.map +1 -1
- package/dist/vocabulary/list.js +2 -2
- package/dist/vocabulary/list.js.map +1 -1
- package/dist/vocabulary/text.d.ts +1 -1
- package/package.json +6 -3
- package/src/JSONGenerativeUI.client.tsx +84 -40
- package/src/JSONGenerativeUI.test.tsx +964 -68
- package/src/RadioGroupScope.server.test.tsx +24 -0
- package/src/RadioGroupScope.tsx +34 -0
- package/src/a2ui/BindingContext.ts +26 -0
- package/src/a2ui/PresentRenderer.tsx +83 -0
- package/src/a2ui/convert.test.ts +1795 -179
- package/src/a2ui/convert.ts +629 -132
- package/src/a2ui/dataModel.ts +69 -0
- package/src/a2ui/reducer.test.ts +35 -0
- package/src/a2ui/reducer.ts +5 -4
- package/src/a2ui/types.ts +9 -2
- package/src/a2ui/valueFunctions.test.ts +271 -0
- package/src/a2ui/valueFunctions.ts +523 -0
- package/src/actionRegistry.ts +1 -1
- package/src/answeredValues.test.ts +33 -0
- package/src/answeredValues.tsx +70 -0
- package/src/constants.ts +4 -0
- package/src/fieldReferences.test.ts +66 -0
- package/src/fieldReferences.ts +83 -0
- package/src/generativeUIToJSX.test.ts +18 -0
- package/src/generativeUIToJSX.ts +4 -6
- package/src/index.server.test.ts +106 -0
- package/src/ir.ts +1 -1
- package/src/renderGenerativeUI.test.tsx +14 -5
- package/src/renderGenerativeUI.tsx +30 -1
- package/src/slack/a2uiSlack.test.ts +198 -0
- package/src/slack/constants.ts +3 -0
- package/src/slack/decodeBlockAction.test.ts +98 -0
- package/src/slack/decodeBlockAction.ts +110 -17
- package/src/slack/fromSlackBlocks.test.ts +222 -3
- package/src/slack/fromSlackBlocks.ts +53 -2
- package/src/slack/toSlackBlocks.test.ts +682 -3
- package/src/slack/toSlackBlocks.ts +374 -23
- package/src/slack/types.ts +4 -0
- package/src/teams/constants.ts +1 -1
- package/src/teams/decodeSubmitData.test.ts +33 -0
- package/src/teams/decodeSubmitData.ts +9 -1
- package/src/teams/toAdaptiveCard.test.ts +219 -0
- package/src/teams/toAdaptiveCard.ts +93 -19
- package/src/teams/types.ts +4 -1
- package/src/vocabulary/alert.test.tsx +288 -7
- package/src/vocabulary/alert.tsx +168 -26
- package/src/vocabulary/collectFormValues.test.ts +28 -1
- package/src/vocabulary/collectFormValues.ts +25 -5
- package/src/vocabulary/data.test.tsx +248 -6
- package/src/vocabulary/data.tsx +398 -100
- package/src/vocabulary/dispatch.ts +63 -2
- package/src/vocabulary/fact.test.tsx +67 -0
- package/src/vocabulary/fact.tsx +40 -9
- package/src/vocabulary/form.tsx +7 -2
- package/src/vocabulary/formatValue.test.ts +72 -0
- package/src/vocabulary/formatValue.ts +127 -0
- package/src/vocabulary/index.test.ts +2 -0
- package/src/vocabulary/interactive.dispatch.test.tsx +167 -79
- package/src/vocabulary/interactive.dom.test.tsx +1699 -0
- package/src/vocabulary/interactive.test.tsx +60 -4
- package/src/vocabulary/interactive.tsx +770 -151
- package/src/vocabulary/layout.test.tsx +6 -4
- package/src/vocabulary/layout.tsx +13 -2
- 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
|
-
|
|
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
|
-
|
|
211
|
-
|
|
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: [
|
|
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
|
-
...(
|
|
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
|
|
789
|
-
* is renamed with a warning (see `decodeSubmitData`).
|
|
790
|
-
* unknown `$type` is skipped with a "dropped" warning, and a
|
|
791
|
-
* resolves to an empty card plus a "dropped" warning instead
|
|
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,
|
package/src/teams/types.ts
CHANGED
|
@@ -109,11 +109,12 @@ export interface TeamsInputChoice {
|
|
|
109
109
|
readonly value: string;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
-
/** A dropdown (`compact`) or
|
|
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";
|