@assistant-ui/react-generative-ui 0.0.21 → 0.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +363 -85
- 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 +3 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +4 -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 +96 -7
- package/dist/slack/decodeBlockAction.js.map +1 -1
- package/dist/slack/fromSlackBlocks.d.ts.map +1 -1
- package/dist/slack/fromSlackBlocks.js +43 -3
- package/dist/slack/fromSlackBlocks.js.map +1 -1
- package/dist/slack/toSlackBlocks.d.ts.map +1 -1
- package/dist/slack/toSlackBlocks.js +244 -20
- package/dist/slack/toSlackBlocks.js.map +1 -1
- package/dist/slack/types.d.ts +24 -1
- package/dist/slack/types.d.ts.map +1 -1
- package/dist/slack.d.ts +1 -1
- package/dist/slack.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 +29 -3
- package/dist/teams/decodeSubmitData.js.map +1 -1
- package/dist/teams/temporalId.d.ts +5 -0
- package/dist/teams/temporalId.d.ts.map +1 -0
- package/dist/teams/temporalId.js +28 -0
- package/dist/teams/temporalId.js.map +1 -0
- package/dist/teams/toAdaptiveCard.d.ts +5 -4
- package/dist/teams/toAdaptiveCard.d.ts.map +1 -1
- package/dist/teams/toAdaptiveCard.js +136 -18
- package/dist/teams/toAdaptiveCard.js.map +1 -1
- package/dist/teams/toTeamsAttachments.d.ts.map +1 -1
- package/dist/teams/toTeamsAttachments.js.map +1 -1
- package/dist/teams/types.d.ts +23 -2
- package/dist/teams/types.d.ts.map +1 -1
- package/dist/teams.d.ts +1 -1
- package/dist/teams.d.ts.map +1 -1
- package/dist/temporal.d.ts +33 -0
- package/dist/temporal.d.ts.map +1 -0
- package/dist/temporal.js +152 -0
- package/dist/temporal.js.map +1 -0
- 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 +15 -4
- 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 +143 -8
- package/dist/vocabulary/interactive.d.ts.map +1 -1
- package/dist/vocabulary/interactive.js +458 -107
- 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 +8 -5
- 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 +2054 -229
- package/src/a2ui/convert.ts +665 -134
- 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 +5 -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/datePicker.test.ts +612 -0
- package/src/slack/decodeBlockAction.test.ts +98 -0
- package/src/slack/decodeBlockAction.ts +182 -16
- package/src/slack/fromSlackBlocks.test.ts +222 -3
- package/src/slack/fromSlackBlocks.ts +82 -2
- package/src/slack/toSlackBlocks.test.ts +682 -3
- package/src/slack/toSlackBlocks.ts +463 -25
- package/src/slack/types.ts +28 -0
- package/src/slack.ts +2 -0
- package/src/teams/constants.ts +1 -1
- package/src/teams/decodeSubmitData.test.ts +249 -0
- package/src/teams/decodeSubmitData.ts +57 -4
- package/src/teams/temporal.test.ts +766 -0
- package/src/teams/temporalId.ts +56 -0
- package/src/teams/toAdaptiveCard.test.ts +219 -0
- package/src/teams/toAdaptiveCard.ts +259 -21
- package/src/teams/toTeamsAttachments.ts +4 -1
- package/src/teams/types.ts +21 -1
- package/src/teams.ts +1 -0
- package/src/temporal.test.ts +319 -0
- package/src/temporal.ts +259 -0
- package/src/vocabulary/alert.test.tsx +288 -7
- package/src/vocabulary/alert.tsx +168 -26
- package/src/vocabulary/collectFormValues.test.ts +225 -1
- package/src/vocabulary/collectFormValues.ts +54 -5
- package/src/vocabulary/data.test.tsx +248 -6
- package/src/vocabulary/data.tsx +398 -100
- package/src/vocabulary/datepicker.dom.test.tsx +1148 -0
- 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 +1747 -0
- package/src/vocabulary/interactive.test.tsx +60 -4
- package/src/vocabulary/interactive.tsx +910 -155
- package/src/vocabulary/layout.test.tsx +6 -4
- package/src/vocabulary/layout.tsx +13 -2
- package/src/vocabulary/list.tsx +2 -2
|
@@ -11,6 +11,16 @@ import {
|
|
|
11
11
|
type NormalizedUIElement,
|
|
12
12
|
type NormalizedUINode,
|
|
13
13
|
} from "../ir";
|
|
14
|
+
import {
|
|
15
|
+
classifyTemporal,
|
|
16
|
+
splitTemporalMinutes,
|
|
17
|
+
temporalOffsetLabel,
|
|
18
|
+
} from "../temporal";
|
|
19
|
+
import {
|
|
20
|
+
factTrend,
|
|
21
|
+
formatFactDelta,
|
|
22
|
+
formatValue,
|
|
23
|
+
} from "../vocabulary/formatValue";
|
|
14
24
|
import {
|
|
15
25
|
CHOICE_OPTION_CAP,
|
|
16
26
|
PAYLOAD_SOFT_CAP,
|
|
@@ -21,6 +31,7 @@ import {
|
|
|
21
31
|
buildSubmitAction,
|
|
22
32
|
utf8ByteLength,
|
|
23
33
|
} from "./constants";
|
|
34
|
+
import { encodeTemporalInputId } from "./temporalId";
|
|
24
35
|
import type {
|
|
25
36
|
AdaptiveCardResult,
|
|
26
37
|
TeamsActionSet,
|
|
@@ -52,6 +63,9 @@ const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
|
52
63
|
const asString = (value: unknown): string =>
|
|
53
64
|
typeof value === "string" ? value : "";
|
|
54
65
|
|
|
66
|
+
const asFiniteNumber = (value: unknown): number | undefined =>
|
|
67
|
+
typeof value === "number" && Number.isFinite(value) ? value : undefined;
|
|
68
|
+
|
|
55
69
|
const DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
|
|
56
70
|
|
|
57
71
|
const SUBTLE_COLORS = new Set([
|
|
@@ -100,7 +114,12 @@ function reservedSafeId(
|
|
|
100
114
|
context: ConversionContext,
|
|
101
115
|
): string {
|
|
102
116
|
const reserved = id === RESERVED_INPUT_ID;
|
|
103
|
-
const
|
|
117
|
+
const temporalReserved = /^_*aui:datetime:/.test(id);
|
|
118
|
+
const base = reserved
|
|
119
|
+
? `${RESERVED_INPUT_ID}_`
|
|
120
|
+
: temporalReserved
|
|
121
|
+
? `_${id}`
|
|
122
|
+
: id;
|
|
104
123
|
let candidate = base;
|
|
105
124
|
let n = 2;
|
|
106
125
|
while (context.usedInputIds.has(candidate)) {
|
|
@@ -114,6 +133,13 @@ function reservedSafeId(
|
|
|
114
133
|
component,
|
|
115
134
|
`the input id "${RESERVED_INPUT_ID}" collides with the submit envelope's reserved key and was renamed to "${candidate}".`,
|
|
116
135
|
);
|
|
136
|
+
} else if (temporalReserved) {
|
|
137
|
+
warn(
|
|
138
|
+
context,
|
|
139
|
+
"fallback",
|
|
140
|
+
component,
|
|
141
|
+
`the input id "${id}" collides with the reserved datetime namespace and was renamed to "${candidate}".`,
|
|
142
|
+
);
|
|
117
143
|
} else if (candidate !== base) {
|
|
118
144
|
warn(
|
|
119
145
|
context,
|
|
@@ -171,7 +197,14 @@ const toChoice = (option: unknown): TeamsInputChoice | undefined => {
|
|
|
171
197
|
if (!isRecord(option) || typeof option["value"] !== "string") {
|
|
172
198
|
return undefined;
|
|
173
199
|
}
|
|
174
|
-
|
|
200
|
+
const label = asString(option["label"]);
|
|
201
|
+
const description = asString(option["description"]);
|
|
202
|
+
return {
|
|
203
|
+
title: description
|
|
204
|
+
? [label, description].filter(Boolean).join(": ")
|
|
205
|
+
: label,
|
|
206
|
+
value: option["value"],
|
|
207
|
+
};
|
|
175
208
|
};
|
|
176
209
|
|
|
177
210
|
const choicesFrom = (
|
|
@@ -206,10 +239,18 @@ const choicesFrom = (
|
|
|
206
239
|
};
|
|
207
240
|
|
|
208
241
|
function convertFacts(facts: readonly NormalizedUIElement[]): TeamsCardElement {
|
|
209
|
-
const set: TeamsFact[] = facts.map((fact) =>
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
242
|
+
const set: TeamsFact[] = facts.map((fact) => {
|
|
243
|
+
const value = asString(fact.props["value"]);
|
|
244
|
+
const delta = fact.props["delta"];
|
|
245
|
+
const deltaText =
|
|
246
|
+
typeof delta === "string"
|
|
247
|
+
? formatFactDelta(delta, factTrend(delta, fact.props["trend"]))
|
|
248
|
+
: undefined;
|
|
249
|
+
return {
|
|
250
|
+
title: asString(fact.props["label"]),
|
|
251
|
+
value: deltaText === undefined ? value : `${value} (${deltaText})`,
|
|
252
|
+
};
|
|
253
|
+
});
|
|
213
254
|
return { type: "FactSet", facts: set };
|
|
214
255
|
}
|
|
215
256
|
|
|
@@ -277,13 +318,6 @@ const ALERT_STYLE_MAP: Record<string, TeamsContainerStyle> = {
|
|
|
277
318
|
danger: "attention",
|
|
278
319
|
};
|
|
279
320
|
|
|
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
321
|
function convertTable(
|
|
288
322
|
props: Readonly<Record<string, unknown>>,
|
|
289
323
|
context: ConversionContext,
|
|
@@ -342,9 +376,18 @@ function convertTable(
|
|
|
342
376
|
cells: (Array.isArray(row)
|
|
343
377
|
? copyBounded(row, TABLE_COLUMN_CAP).items
|
|
344
378
|
: []
|
|
345
|
-
).map((cell) => ({
|
|
379
|
+
).map((cell, index) => ({
|
|
346
380
|
type: "TableCell" as const,
|
|
347
|
-
items: [
|
|
381
|
+
items: [
|
|
382
|
+
textBlock(
|
|
383
|
+
formatValue(
|
|
384
|
+
cell,
|
|
385
|
+
isRecord(rawColumns[index])
|
|
386
|
+
? rawColumns[index]["format"]
|
|
387
|
+
: undefined,
|
|
388
|
+
),
|
|
389
|
+
),
|
|
390
|
+
],
|
|
348
391
|
})),
|
|
349
392
|
}));
|
|
350
393
|
|
|
@@ -465,11 +508,15 @@ export function convertElement(
|
|
|
465
508
|
const name = asString(props["name"]);
|
|
466
509
|
const placeholder = asString(props["placeholder"]);
|
|
467
510
|
const label = asString(props["label"]);
|
|
511
|
+
const defaultValue = props["defaultValue"];
|
|
468
512
|
const input: TeamsCardElement = {
|
|
469
513
|
type: "Input.ChoiceSet",
|
|
470
514
|
id: reservedSafeId(name || "select", "Select", context),
|
|
471
515
|
style: "compact",
|
|
472
516
|
choices: choicesFrom(props["options"], "Select", context),
|
|
517
|
+
...(typeof defaultValue === "string" && defaultValue
|
|
518
|
+
? { value: defaultValue }
|
|
519
|
+
: {}),
|
|
473
520
|
...(placeholder ? { placeholder } : {}),
|
|
474
521
|
...(label ? { label } : {}),
|
|
475
522
|
};
|
|
@@ -491,6 +538,26 @@ export function convertElement(
|
|
|
491
538
|
};
|
|
492
539
|
return withCompanionSubmit(element, input, context);
|
|
493
540
|
}
|
|
541
|
+
case "CheckboxGroup": {
|
|
542
|
+
const name = asString(props["name"]);
|
|
543
|
+
const label = asString(props["label"]);
|
|
544
|
+
const checked = Array.isArray(props["defaultValue"])
|
|
545
|
+
? props["defaultValue"].filter(
|
|
546
|
+
(value): value is string =>
|
|
547
|
+
typeof value === "string" && value !== "",
|
|
548
|
+
)
|
|
549
|
+
: [];
|
|
550
|
+
const input: TeamsCardElement = {
|
|
551
|
+
type: "Input.ChoiceSet",
|
|
552
|
+
id: reservedSafeId(name || "checkboxgroup", "CheckboxGroup", context),
|
|
553
|
+
style: "expanded",
|
|
554
|
+
isMultiSelect: true,
|
|
555
|
+
choices: choicesFrom(props["options"], "CheckboxGroup", context),
|
|
556
|
+
...(checked.length > 0 ? { value: checked.join(",") } : {}),
|
|
557
|
+
...(label ? { label } : {}),
|
|
558
|
+
};
|
|
559
|
+
return withCompanionSubmit(element, input, context);
|
|
560
|
+
}
|
|
494
561
|
case "Checkbox": {
|
|
495
562
|
const name = asString(props["name"]);
|
|
496
563
|
const label = asString(props["label"]);
|
|
@@ -504,22 +571,189 @@ export function convertElement(
|
|
|
504
571
|
};
|
|
505
572
|
return withCompanionSubmit(element, input, context);
|
|
506
573
|
}
|
|
574
|
+
case "Slider": {
|
|
575
|
+
const name = asString(props["name"]);
|
|
576
|
+
const label = asString(props["label"]);
|
|
577
|
+
const min = asFiniteNumber(props["min"]);
|
|
578
|
+
const max = asFiniteNumber(props["max"]);
|
|
579
|
+
const defaultValue = asFiniteNumber(props["defaultValue"]);
|
|
580
|
+
const input = {
|
|
581
|
+
type: "Input.Number",
|
|
582
|
+
id: reservedSafeId(name || "slider", "Slider", context),
|
|
583
|
+
...(label ? { label } : {}),
|
|
584
|
+
...(min !== undefined ? { min } : {}),
|
|
585
|
+
...(max !== undefined ? { max } : {}),
|
|
586
|
+
...(defaultValue !== undefined ? { value: defaultValue } : {}),
|
|
587
|
+
} as unknown as TeamsCardElement;
|
|
588
|
+
return withCompanionSubmit(element, input, context);
|
|
589
|
+
}
|
|
507
590
|
case "Input": {
|
|
508
591
|
const name = asString(props["name"]);
|
|
509
592
|
const label = asString(props["label"]);
|
|
510
593
|
const placeholder = asString(props["placeholder"]);
|
|
594
|
+
const defaultValue = props["defaultValue"];
|
|
511
595
|
const input: TeamsCardElement = {
|
|
512
596
|
type: "Input.Text",
|
|
513
597
|
id: reservedSafeId(name || "input", "Input", context),
|
|
598
|
+
...(props["inputType"] === "password" ? { style: "password" } : {}),
|
|
514
599
|
...(label ? { label } : {}),
|
|
515
600
|
...(placeholder ? { placeholder } : {}),
|
|
516
|
-
...(
|
|
601
|
+
...(typeof defaultValue === "string" && defaultValue
|
|
602
|
+
? { value: defaultValue }
|
|
603
|
+
: {}),
|
|
604
|
+
...(props["multiline"] === true &&
|
|
605
|
+
(props["inputType"] === undefined || props["inputType"] === "text")
|
|
606
|
+
? { isMultiline: true }
|
|
607
|
+
: {}),
|
|
517
608
|
};
|
|
518
609
|
return withCompanionSubmit(element, input, context);
|
|
519
610
|
}
|
|
520
611
|
case "DatePicker": {
|
|
521
612
|
const name = asString(props["name"]);
|
|
522
613
|
const label = asString(props["label"]);
|
|
614
|
+
const mode = props["inputType"];
|
|
615
|
+
if (mode === "time") {
|
|
616
|
+
const id = reservedSafeId(name || "datepicker", "DatePicker", context);
|
|
617
|
+
const rawValue = asString(props["value"]);
|
|
618
|
+
const parts = splitTemporalMinutes(rawValue);
|
|
619
|
+
if (
|
|
620
|
+
classifyTemporal(rawValue).kind === "time" &&
|
|
621
|
+
parts?.droppedPrecision
|
|
622
|
+
) {
|
|
623
|
+
warn(
|
|
624
|
+
context,
|
|
625
|
+
"dropped",
|
|
626
|
+
"DatePicker",
|
|
627
|
+
"Nonzero seconds were dropped from the time value.",
|
|
628
|
+
);
|
|
629
|
+
}
|
|
630
|
+
const value = parts?.time;
|
|
631
|
+
const rawMin = asString(props["min"]);
|
|
632
|
+
const minParts = splitTemporalMinutes(rawMin);
|
|
633
|
+
let min = minParts?.time;
|
|
634
|
+
if (
|
|
635
|
+
classifyTemporal(rawMin).kind === "time" &&
|
|
636
|
+
minParts?.droppedPrecision
|
|
637
|
+
) {
|
|
638
|
+
const minuteOfDay =
|
|
639
|
+
Number(minParts.time.slice(0, 2)) * 60 +
|
|
640
|
+
Number(minParts.time.slice(3, 5)) +
|
|
641
|
+
1;
|
|
642
|
+
if (minuteOfDay === 24 * 60) {
|
|
643
|
+
min = undefined;
|
|
644
|
+
warn(
|
|
645
|
+
context,
|
|
646
|
+
"dropped",
|
|
647
|
+
"DatePicker",
|
|
648
|
+
"The time minimum rounds past 23:59 and was dropped.",
|
|
649
|
+
);
|
|
650
|
+
} else {
|
|
651
|
+
min = `${String(Math.floor(minuteOfDay / 60)).padStart(2, "0")}:${String(minuteOfDay % 60).padStart(2, "0")}`;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
const rawMax = asString(props["max"]);
|
|
655
|
+
const maxParts = splitTemporalMinutes(rawMax);
|
|
656
|
+
const max = maxParts?.time;
|
|
657
|
+
if (
|
|
658
|
+
classifyTemporal(rawMax).kind === "time" &&
|
|
659
|
+
maxParts?.droppedPrecision
|
|
660
|
+
) {
|
|
661
|
+
warn(
|
|
662
|
+
context,
|
|
663
|
+
"dropped",
|
|
664
|
+
"DatePicker",
|
|
665
|
+
rawMax.slice(6, 8) === "00"
|
|
666
|
+
? "Nonzero fractional seconds were dropped from the time maximum."
|
|
667
|
+
: /\.\d*[1-9]/.test(rawMax)
|
|
668
|
+
? "Nonzero seconds and fractional seconds were dropped from the time maximum."
|
|
669
|
+
: "Nonzero seconds were dropped from the time maximum.",
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
const input: TeamsCardElement = {
|
|
673
|
+
type: "Input.Time",
|
|
674
|
+
id,
|
|
675
|
+
...(label ? { label } : {}),
|
|
676
|
+
...(classifyTemporal(rawValue).kind === "time" && value !== undefined
|
|
677
|
+
? { value }
|
|
678
|
+
: {}),
|
|
679
|
+
...(classifyTemporal(rawMin).kind === "time" && min !== undefined
|
|
680
|
+
? { min }
|
|
681
|
+
: {}),
|
|
682
|
+
...(classifyTemporal(rawMax).kind === "time" && max !== undefined
|
|
683
|
+
? { max }
|
|
684
|
+
: {}),
|
|
685
|
+
};
|
|
686
|
+
return withCompanionSubmit(element, input, context);
|
|
687
|
+
}
|
|
688
|
+
if (mode === "datetime") {
|
|
689
|
+
const rawValue = asString(props["value"]);
|
|
690
|
+
const temporal = classifyTemporal(rawValue);
|
|
691
|
+
const fieldId = reservedSafeId(
|
|
692
|
+
name || "datepicker",
|
|
693
|
+
"DatePicker",
|
|
694
|
+
context,
|
|
695
|
+
);
|
|
696
|
+
const dateId = encodeTemporalInputId({ fieldId, role: "date" });
|
|
697
|
+
const timeId = encodeTemporalInputId({
|
|
698
|
+
fieldId,
|
|
699
|
+
role: "time",
|
|
700
|
+
...(temporal.kind === "instant" ? { previousValue: rawValue } : {}),
|
|
701
|
+
});
|
|
702
|
+
const parts = splitTemporalMinutes(rawValue);
|
|
703
|
+
if (temporal.kind === "floating" && parts?.droppedPrecision) {
|
|
704
|
+
warn(
|
|
705
|
+
context,
|
|
706
|
+
"dropped",
|
|
707
|
+
"DatePicker",
|
|
708
|
+
"Nonzero seconds were dropped from the datetime value.",
|
|
709
|
+
);
|
|
710
|
+
}
|
|
711
|
+
const offsetLabel = temporalOffsetLabel(rawValue);
|
|
712
|
+
const boundDate = (raw: unknown) => {
|
|
713
|
+
const value = asString(raw);
|
|
714
|
+
return classifyTemporal(value).kind === "date"
|
|
715
|
+
? value
|
|
716
|
+
: splitTemporalMinutes(
|
|
717
|
+
value,
|
|
718
|
+
temporal.kind === "instant" ? temporal.offset : undefined,
|
|
719
|
+
)?.date;
|
|
720
|
+
};
|
|
721
|
+
const min = boundDate(props["min"]);
|
|
722
|
+
const max = boundDate(props["max"]);
|
|
723
|
+
const dateInput: TeamsCardElement = {
|
|
724
|
+
type: "Input.Date",
|
|
725
|
+
id: dateId,
|
|
726
|
+
...(label ? { label } : {}),
|
|
727
|
+
...(parts?.date !== undefined ? { value: parts.date } : {}),
|
|
728
|
+
...(min !== undefined ? { min } : {}),
|
|
729
|
+
...(max !== undefined ? { max } : {}),
|
|
730
|
+
};
|
|
731
|
+
const timeInput: TeamsCardElement = {
|
|
732
|
+
type: "Input.Time",
|
|
733
|
+
id: timeId,
|
|
734
|
+
...(label || offsetLabel
|
|
735
|
+
? {
|
|
736
|
+
label: `${label ? `${label} time` : "Time"}${offsetLabel ? ` (${offsetLabel})` : ""}`,
|
|
737
|
+
}
|
|
738
|
+
: {}),
|
|
739
|
+
...(parts?.time !== undefined ? { value: parts.time } : {}),
|
|
740
|
+
};
|
|
741
|
+
if (element.action === undefined) return [dateInput, timeInput];
|
|
742
|
+
const [input, submit] = withCompanionSubmit(
|
|
743
|
+
element,
|
|
744
|
+
timeInput,
|
|
745
|
+
context,
|
|
746
|
+
);
|
|
747
|
+
return [dateInput, input!, submit!];
|
|
748
|
+
}
|
|
749
|
+
if (mode !== undefined && mode !== "date") {
|
|
750
|
+
warn(
|
|
751
|
+
context,
|
|
752
|
+
"dropped",
|
|
753
|
+
"DatePicker",
|
|
754
|
+
"Unsupported inputType was dropped; the picker was rendered as a date input.",
|
|
755
|
+
);
|
|
756
|
+
}
|
|
523
757
|
const rawValue = props["value"];
|
|
524
758
|
const value =
|
|
525
759
|
typeof rawValue === "string" && DATE_PATTERN.test(rawValue)
|
|
@@ -785,16 +1019,20 @@ export function convertRootToCard(
|
|
|
785
1019
|
* Converts a generative-ui tree into a Microsoft Teams Adaptive Card and
|
|
786
1020
|
* non-fatal conversion warnings. Sizes, weights, and colors map to Adaptive
|
|
787
1021
|
* 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
|
|
1022
|
+
* CheckboxGroup/Checkbox/Slider/DatePicker whose id would be the reserved
|
|
1023
|
+
* {@link RESERVED_INPUT_ID} is renamed with a warning (see `decodeSubmitData`).
|
|
1024
|
+
* Never throws: an unknown `$type` is skipped with a "dropped" warning, and a
|
|
1025
|
+
* malformed input resolves to an empty card plus a "dropped" warning instead
|
|
1026
|
+
* of throwing.
|
|
792
1027
|
*/
|
|
793
1028
|
export function toAdaptiveCard(
|
|
794
1029
|
node: unknown,
|
|
795
1030
|
_options?: ToAdaptiveCardOptions,
|
|
796
1031
|
): AdaptiveCardResult {
|
|
797
|
-
const context: ConversionContext = {
|
|
1032
|
+
const context: ConversionContext = {
|
|
1033
|
+
warnings: [],
|
|
1034
|
+
usedInputIds: new Set(),
|
|
1035
|
+
};
|
|
798
1036
|
try {
|
|
799
1037
|
const bounded = boundSpec(node, (reason) =>
|
|
800
1038
|
warn(context, "clamped", "Root", clampReasonDetail(reason)),
|
|
@@ -77,7 +77,10 @@ export function toTeamsAttachments(
|
|
|
77
77
|
_options?: ToAdaptiveCardOptions,
|
|
78
78
|
): TeamsAttachmentsResult {
|
|
79
79
|
const warnings: TeamsConversionWarning[] = [];
|
|
80
|
-
const context: ConversionContext = {
|
|
80
|
+
const context: ConversionContext = {
|
|
81
|
+
warnings,
|
|
82
|
+
usedInputIds: new Set(),
|
|
83
|
+
};
|
|
81
84
|
try {
|
|
82
85
|
const bounded = boundSpec(node, (reason) =>
|
|
83
86
|
warnings.push({
|
package/src/teams/types.ts
CHANGED
|
@@ -55,6 +55,11 @@ export interface TeamsSubmitData {
|
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
export type TeamsTemporalField = { readonly fieldId: string } & (
|
|
59
|
+
| { readonly role: "date" }
|
|
60
|
+
| { readonly role: "time"; readonly previousValue?: string }
|
|
61
|
+
);
|
|
62
|
+
|
|
58
63
|
/**
|
|
59
64
|
* A submit action. `data` carries the resume payload under an `aui` key so
|
|
60
65
|
* {@link decodeSubmitData} can invert it out of the merged `activity.value`
|
|
@@ -109,11 +114,12 @@ export interface TeamsInputChoice {
|
|
|
109
114
|
readonly value: string;
|
|
110
115
|
}
|
|
111
116
|
|
|
112
|
-
/** A dropdown (`compact`) or
|
|
117
|
+
/** A dropdown (`compact`), radio group (`expanded`), or checkbox group (`expanded` with `isMultiSelect`) input. */
|
|
113
118
|
export interface TeamsInputChoiceSet {
|
|
114
119
|
readonly type: "Input.ChoiceSet";
|
|
115
120
|
readonly id: string;
|
|
116
121
|
readonly style: "compact" | "expanded";
|
|
122
|
+
readonly isMultiSelect?: true;
|
|
117
123
|
readonly choices: readonly TeamsInputChoice[];
|
|
118
124
|
readonly placeholder?: string;
|
|
119
125
|
readonly label?: string;
|
|
@@ -138,8 +144,10 @@ export interface TeamsInputToggle {
|
|
|
138
144
|
export interface TeamsInputText {
|
|
139
145
|
readonly type: "Input.Text";
|
|
140
146
|
readonly id: string;
|
|
147
|
+
readonly style?: "password";
|
|
141
148
|
readonly label?: string;
|
|
142
149
|
readonly placeholder?: string;
|
|
150
|
+
readonly value?: string;
|
|
143
151
|
readonly isMultiline?: true;
|
|
144
152
|
readonly separator?: true;
|
|
145
153
|
readonly spacing?: "large";
|
|
@@ -157,6 +165,17 @@ export interface TeamsInputDate {
|
|
|
157
165
|
readonly spacing?: "large";
|
|
158
166
|
}
|
|
159
167
|
|
|
168
|
+
export interface TeamsInputTime {
|
|
169
|
+
readonly type: "Input.Time";
|
|
170
|
+
readonly id: string;
|
|
171
|
+
readonly label?: string;
|
|
172
|
+
readonly value?: string;
|
|
173
|
+
readonly min?: string;
|
|
174
|
+
readonly max?: string;
|
|
175
|
+
readonly separator?: true;
|
|
176
|
+
readonly spacing?: "large";
|
|
177
|
+
}
|
|
178
|
+
|
|
160
179
|
/** One cell inside a {@link TeamsTableRow}. */
|
|
161
180
|
export interface TeamsTableCell {
|
|
162
181
|
readonly type: "TableCell";
|
|
@@ -196,6 +215,7 @@ export type TeamsCardElement =
|
|
|
196
215
|
| TeamsInputToggle
|
|
197
216
|
| TeamsInputText
|
|
198
217
|
| TeamsInputDate
|
|
218
|
+
| TeamsInputTime
|
|
199
219
|
| TeamsTable;
|
|
200
220
|
|
|
201
221
|
/** A Microsoft Teams Adaptive Card, pinned to schema version 1.5 (the Teams desktop/web cap; mobile clients cap at 1.2). */
|