@assistant-ui/react-generative-ui 0.0.22 → 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/dist/a2ui/convert.d.ts.map +1 -1
- package/dist/a2ui/convert.js +21 -4
- package/dist/a2ui/convert.js.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +2 -1
- package/dist/constants.js.map +1 -1
- package/dist/slack/decodeBlockAction.d.ts.map +1 -1
- package/dist/slack/decodeBlockAction.js +64 -26
- package/dist/slack/decodeBlockAction.js.map +1 -1
- package/dist/slack/fromSlackBlocks.d.ts.map +1 -1
- package/dist/slack/fromSlackBlocks.js +23 -0
- package/dist/slack/fromSlackBlocks.js.map +1 -1
- package/dist/slack/toSlackBlocks.d.ts.map +1 -1
- package/dist/slack/toSlackBlocks.js +74 -18
- package/dist/slack/toSlackBlocks.js.map +1 -1
- package/dist/slack/types.d.ts +20 -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/decodeSubmitData.d.ts.map +1 -1
- package/dist/teams/decodeSubmitData.js +24 -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.map +1 -1
- package/dist/teams/toAdaptiveCard.js +80 -1
- 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 +19 -1
- 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/collectFormValues.d.ts +1 -1
- package/dist/vocabulary/collectFormValues.d.ts.map +1 -1
- package/dist/vocabulary/collectFormValues.js +7 -3
- package/dist/vocabulary/collectFormValues.js.map +1 -1
- package/dist/vocabulary/data.d.ts +1 -1
- package/dist/vocabulary/interactive.d.ts +7 -1
- package/dist/vocabulary/interactive.d.ts.map +1 -1
- package/dist/vocabulary/interactive.js +62 -12
- package/dist/vocabulary/interactive.js.map +1 -1
- package/package.json +5 -5
- package/src/a2ui/convert.test.ts +223 -14
- package/src/a2ui/convert.ts +41 -7
- package/src/constants.ts +1 -0
- package/src/slack/datePicker.test.ts +612 -0
- package/src/slack/decodeBlockAction.ts +114 -41
- package/src/slack/fromSlackBlocks.ts +29 -0
- package/src/slack/toSlackBlocks.ts +123 -36
- package/src/slack/types.ts +24 -0
- package/src/slack.ts +2 -0
- package/src/teams/decodeSubmitData.test.ts +216 -0
- package/src/teams/decodeSubmitData.ts +49 -4
- package/src/teams/temporal.test.ts +766 -0
- package/src/teams/temporalId.ts +56 -0
- package/src/teams/toAdaptiveCard.ts +166 -2
- package/src/teams/toTeamsAttachments.ts +4 -1
- package/src/teams/types.ts +17 -0
- package/src/teams.ts +1 -0
- package/src/temporal.test.ts +319 -0
- package/src/temporal.ts +259 -0
- package/src/vocabulary/collectFormValues.test.ts +198 -1
- package/src/vocabulary/collectFormValues.ts +31 -2
- package/src/vocabulary/datepicker.dom.test.tsx +1148 -0
- package/src/vocabulary/interactive.dom.test.tsx +59 -11
- package/src/vocabulary/interactive.tsx +151 -15
|
@@ -12,6 +12,11 @@ import { createActionRegistry, type ActionHandler } from "../actionRegistry";
|
|
|
12
12
|
import { AnsweredValuesProvider } from "../answeredValues";
|
|
13
13
|
import type { GenerativeUIDispatch, GenerativeUILibrary } from "../types";
|
|
14
14
|
import { renderGenerativeUI } from "../renderGenerativeUI";
|
|
15
|
+
import {
|
|
16
|
+
fromLocalDateTime,
|
|
17
|
+
normalizeTemporalInputValue,
|
|
18
|
+
toLocalDateTime,
|
|
19
|
+
} from "../temporal";
|
|
15
20
|
import { defaultGenerativeUILibrary } from "./index";
|
|
16
21
|
|
|
17
22
|
(
|
|
@@ -1533,11 +1538,19 @@ describe.each([
|
|
|
1533
1538
|
type: "number",
|
|
1534
1539
|
},
|
|
1535
1540
|
{
|
|
1536
|
-
title: "date and time",
|
|
1541
|
+
title: "floating date and time",
|
|
1537
1542
|
field: { component: "DateTimeInput", enableDate: true, enableTime: true },
|
|
1538
|
-
initial: "2025-12-15T17:00
|
|
1539
|
-
edited: "2025-12-16T08:30
|
|
1540
|
-
type: "
|
|
1543
|
+
initial: "2025-12-15T17:00",
|
|
1544
|
+
edited: "2025-12-16T08:30",
|
|
1545
|
+
type: "datetime-local",
|
|
1546
|
+
},
|
|
1547
|
+
{
|
|
1548
|
+
title: "floating date and time with seconds",
|
|
1549
|
+
field: { component: "DateTimeInput", enableDate: true, enableTime: true },
|
|
1550
|
+
initial: "2025-12-15T17:00:00",
|
|
1551
|
+
displayed: "2025-12-15T17:00",
|
|
1552
|
+
edited: "2025-12-16T08:30",
|
|
1553
|
+
type: "datetime-local",
|
|
1541
1554
|
},
|
|
1542
1555
|
{
|
|
1543
1556
|
title: "timestamp in date mode",
|
|
@@ -1550,8 +1563,17 @@ describe.each([
|
|
|
1550
1563
|
title: "time only",
|
|
1551
1564
|
field: { component: "DateTimeInput", enableTime: true },
|
|
1552
1565
|
initial: "17:00:00",
|
|
1566
|
+
displayed: "17:00",
|
|
1553
1567
|
edited: "08:30:45",
|
|
1554
|
-
type: "
|
|
1568
|
+
type: "time",
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
title: "fractional time",
|
|
1572
|
+
field: { component: "DateTimeInput", enableTime: true },
|
|
1573
|
+
initial: "17:00:00.000",
|
|
1574
|
+
displayed: "17:00",
|
|
1575
|
+
edited: "08:30",
|
|
1576
|
+
type: "time",
|
|
1555
1577
|
},
|
|
1556
1578
|
{
|
|
1557
1579
|
title: "omitted temporal flags",
|
|
@@ -1593,15 +1615,15 @@ describe.each([
|
|
|
1593
1615
|
type: "number",
|
|
1594
1616
|
},
|
|
1595
1617
|
{
|
|
1596
|
-
title: "unresolved
|
|
1597
|
-
field: { component: "DateTimeInput",
|
|
1618
|
+
title: "unresolved time",
|
|
1619
|
+
field: { component: "DateTimeInput", enableTime: true },
|
|
1598
1620
|
initial: undefined,
|
|
1599
|
-
edited: "
|
|
1600
|
-
type: "
|
|
1621
|
+
edited: "08:30",
|
|
1622
|
+
type: "time",
|
|
1601
1623
|
},
|
|
1602
1624
|
])(
|
|
1603
1625
|
"preserves untouched, edited, and cleared $title values",
|
|
1604
|
-
async ({ field, initial, edited, type }) => {
|
|
1626
|
+
async ({ field, initial, displayed, edited, type }) => {
|
|
1605
1627
|
const { container, render, submit } = await mountSurface(field, initial);
|
|
1606
1628
|
const input = () =>
|
|
1607
1629
|
container.querySelector<HTMLInputElement>('input[name="/form/value"]')!;
|
|
@@ -1620,7 +1642,7 @@ describe.each([
|
|
|
1620
1642
|
|
|
1621
1643
|
expect(input().type).toBe(type);
|
|
1622
1644
|
expect(input().getAttribute("aria-label")).toBe("Value");
|
|
1623
|
-
expect(input().value).toBe(initial ?? "");
|
|
1645
|
+
expect(input().value).toBe(displayed ?? initial ?? "");
|
|
1624
1646
|
if (type === "number") expect(input().step).toBe("any");
|
|
1625
1647
|
await submit(initial ?? "");
|
|
1626
1648
|
|
|
@@ -1646,6 +1668,32 @@ describe.each([
|
|
|
1646
1668
|
},
|
|
1647
1669
|
);
|
|
1648
1670
|
|
|
1671
|
+
it("preserves an instant offset in the A2UI button context", async () => {
|
|
1672
|
+
const initial = "2025-12-15T17:00:00+02:00";
|
|
1673
|
+
const { container, submit } = await mountSurface(
|
|
1674
|
+
{ component: "DateTimeInput", enableDate: true, enableTime: true },
|
|
1675
|
+
initial,
|
|
1676
|
+
);
|
|
1677
|
+
const input = container.querySelector<HTMLInputElement>(
|
|
1678
|
+
'input[name="/form/value"]',
|
|
1679
|
+
)!;
|
|
1680
|
+
expect(input.type).toBe("datetime-local");
|
|
1681
|
+
expect(input.value).toBe(
|
|
1682
|
+
normalizeTemporalInputValue(toLocalDateTime(initial)),
|
|
1683
|
+
);
|
|
1684
|
+
await submit(initial);
|
|
1685
|
+
|
|
1686
|
+
await act(async () => {
|
|
1687
|
+
Object.getOwnPropertyDescriptor(
|
|
1688
|
+
HTMLInputElement.prototype,
|
|
1689
|
+
"value",
|
|
1690
|
+
)!.set!.call(input, "2025-12-16T09:30");
|
|
1691
|
+
input.dispatchEvent(new Event("input", { bubbles: true }));
|
|
1692
|
+
});
|
|
1693
|
+
expect(input.value).toBe("2025-12-16T09:30");
|
|
1694
|
+
await submit(fromLocalDateTime("2025-12-16T09:30", initial));
|
|
1695
|
+
});
|
|
1696
|
+
|
|
1649
1697
|
it.each([undefined, "Pick one"])(
|
|
1650
1698
|
"keeps chips empty until selected with placeholder %s",
|
|
1651
1699
|
async (placeholder) => {
|
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import {
|
|
4
4
|
CHECKBOX_GROUP_ATTR,
|
|
5
5
|
FIELD_NAME_ATTR,
|
|
6
|
+
FIELD_VALUE_ATTR,
|
|
6
7
|
GENERATED_NAME_ATTR,
|
|
7
8
|
} from "../constants";
|
|
8
9
|
import type { Action } from "../ir";
|
|
@@ -17,6 +18,14 @@ import { useAnsweredValue } from "../answeredValues";
|
|
|
17
18
|
import { actionAttr, fire } from "./dispatch";
|
|
18
19
|
import { toTextContent } from "./toTextContent";
|
|
19
20
|
import { useRadioGroupName } from "../RadioGroupScope";
|
|
21
|
+
import {
|
|
22
|
+
classifyTemporal,
|
|
23
|
+
fromLocalDateTime,
|
|
24
|
+
getTemporalInputStep,
|
|
25
|
+
normalizeTemporalInputValue,
|
|
26
|
+
toLocalDateTime,
|
|
27
|
+
toPickerLocalDateTime,
|
|
28
|
+
} from "../temporal";
|
|
20
29
|
|
|
21
30
|
const optionSchema = z.object({
|
|
22
31
|
label: z.string(),
|
|
@@ -368,6 +377,7 @@ function InputRender({
|
|
|
368
377
|
}
|
|
369
378
|
|
|
370
379
|
type DatePickerRenderProps = {
|
|
380
|
+
inputType?: "date" | "datetime" | "time";
|
|
371
381
|
value?: string;
|
|
372
382
|
min?: string;
|
|
373
383
|
max?: string;
|
|
@@ -378,7 +388,12 @@ type DatePickerRenderProps = {
|
|
|
378
388
|
$dispatch?: GenerativeUIDispatch;
|
|
379
389
|
};
|
|
380
390
|
|
|
391
|
+
const subscribeToHydration = () => () => {};
|
|
392
|
+
const clientHydrationSnapshot = () => true;
|
|
393
|
+
const serverHydrationSnapshot = () => false;
|
|
394
|
+
|
|
381
395
|
function DatePickerRender({
|
|
396
|
+
inputType = "date",
|
|
382
397
|
value,
|
|
383
398
|
min,
|
|
384
399
|
max,
|
|
@@ -392,25 +407,125 @@ function DatePickerRender({
|
|
|
392
407
|
const isBound = updateBinding !== undefined;
|
|
393
408
|
const initialValue =
|
|
394
409
|
typeof answeredValue === "string" ? answeredValue : value;
|
|
410
|
+
const [selection, setSelection] = React.useState({
|
|
411
|
+
initialValue,
|
|
412
|
+
value: initialValue,
|
|
413
|
+
anchor: initialValue,
|
|
414
|
+
});
|
|
415
|
+
if (selection.initialValue !== initialValue) {
|
|
416
|
+
setSelection({
|
|
417
|
+
initialValue,
|
|
418
|
+
value: initialValue,
|
|
419
|
+
anchor:
|
|
420
|
+
isBound && initialValue === selection.value
|
|
421
|
+
? selection.anchor
|
|
422
|
+
: initialValue,
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
const currentValue = isBound ? initialValue : selection.value;
|
|
426
|
+
const temporal = classifyTemporal(currentValue ?? "");
|
|
427
|
+
const anchor = classifyTemporal(selection.anchor ?? "");
|
|
428
|
+
const hasInstantAnchor =
|
|
429
|
+
inputType === "datetime" && anchor.kind === "instant";
|
|
430
|
+
const canonicalFieldValue = (value: string | undefined) => {
|
|
431
|
+
const current = value ?? "";
|
|
432
|
+
const kind = classifyTemporal(current).kind;
|
|
433
|
+
if (inputType === "time")
|
|
434
|
+
return kind === "time" && normalizeTemporalInputValue(current) !== current
|
|
435
|
+
? value
|
|
436
|
+
: undefined;
|
|
437
|
+
if (inputType !== "datetime") return undefined;
|
|
438
|
+
if (kind === "instant") return value;
|
|
439
|
+
if (hasInstantAnchor) return selection.anchor;
|
|
440
|
+
return kind === "floating" &&
|
|
441
|
+
normalizeTemporalInputValue(current) !== current
|
|
442
|
+
? value
|
|
443
|
+
: undefined;
|
|
444
|
+
};
|
|
445
|
+
const minimum = classifyTemporal(min ?? "");
|
|
446
|
+
const maximum = classifyTemporal(max ?? "");
|
|
447
|
+
const hydrated = React.useSyncExternalStore(
|
|
448
|
+
subscribeToHydration,
|
|
449
|
+
clientHydrationSnapshot,
|
|
450
|
+
serverHydrationSnapshot,
|
|
451
|
+
);
|
|
452
|
+
const showRawValue =
|
|
453
|
+
inputType === "datetime" && temporal.kind === "instant" && !hydrated;
|
|
454
|
+
const displayValue =
|
|
455
|
+
currentValue === undefined
|
|
456
|
+
? undefined
|
|
457
|
+
: inputType === "datetime" && hydrated
|
|
458
|
+
? toPickerLocalDateTime(currentValue)
|
|
459
|
+
: normalizeTemporalInputValue(currentValue);
|
|
395
460
|
return (
|
|
396
461
|
<input
|
|
397
462
|
key={isBound ? undefined : initialValue}
|
|
398
|
-
type=
|
|
463
|
+
type={
|
|
464
|
+
showRawValue
|
|
465
|
+
? "text"
|
|
466
|
+
: inputType === "datetime"
|
|
467
|
+
? "datetime-local"
|
|
468
|
+
: inputType
|
|
469
|
+
}
|
|
470
|
+
readOnly={showRawValue || undefined}
|
|
399
471
|
data-aui="datepicker"
|
|
400
472
|
data-aui-action={actionAttr($action)}
|
|
473
|
+
{...{
|
|
474
|
+
[FIELD_VALUE_ATTR]: canonicalFieldValue(currentValue),
|
|
475
|
+
}}
|
|
401
476
|
name={name}
|
|
402
477
|
aria-label={label}
|
|
403
|
-
{...(isBound
|
|
404
|
-
? {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
478
|
+
{...(isBound || inputType === "datetime"
|
|
479
|
+
? { value: displayValue ?? "" }
|
|
480
|
+
: { defaultValue: displayValue })}
|
|
481
|
+
min={
|
|
482
|
+
inputType === "datetime" && hydrated && min !== undefined
|
|
483
|
+
? normalizeTemporalInputValue(
|
|
484
|
+
toLocalDateTime(
|
|
485
|
+
min,
|
|
486
|
+
minimum.kind === "instant" ? minimum.precision : undefined,
|
|
487
|
+
),
|
|
488
|
+
)
|
|
489
|
+
: min === undefined
|
|
490
|
+
? undefined
|
|
491
|
+
: normalizeTemporalInputValue(min)
|
|
492
|
+
}
|
|
493
|
+
max={
|
|
494
|
+
inputType === "datetime" && hydrated && max !== undefined
|
|
495
|
+
? normalizeTemporalInputValue(
|
|
496
|
+
toLocalDateTime(
|
|
497
|
+
max,
|
|
498
|
+
maximum.kind === "instant" ? maximum.precision : undefined,
|
|
499
|
+
),
|
|
500
|
+
)
|
|
501
|
+
: max === undefined
|
|
502
|
+
? undefined
|
|
503
|
+
: normalizeTemporalInputValue(max)
|
|
504
|
+
}
|
|
505
|
+
step={
|
|
506
|
+
inputType !== "date"
|
|
507
|
+
? getTemporalInputStep(selection.anchor, currentValue, min, max)
|
|
508
|
+
: undefined
|
|
509
|
+
}
|
|
411
510
|
onChange={(e) => {
|
|
412
|
-
|
|
413
|
-
|
|
511
|
+
const inputValue = normalizeTemporalInputValue(e.currentTarget.value);
|
|
512
|
+
const nextValue =
|
|
513
|
+
inputType === "datetime"
|
|
514
|
+
? fromLocalDateTime(inputValue, selection.anchor)
|
|
515
|
+
: inputValue;
|
|
516
|
+
const canonical = canonicalFieldValue(nextValue);
|
|
517
|
+
if (canonical !== undefined) {
|
|
518
|
+
e.currentTarget.setAttribute(FIELD_VALUE_ATTR, canonical);
|
|
519
|
+
} else {
|
|
520
|
+
e.currentTarget.removeAttribute(FIELD_VALUE_ATTR);
|
|
521
|
+
}
|
|
522
|
+
setSelection({
|
|
523
|
+
initialValue,
|
|
524
|
+
value: nextValue,
|
|
525
|
+
anchor: selection.anchor,
|
|
526
|
+
});
|
|
527
|
+
updateBinding?.(nextValue);
|
|
528
|
+
fire($action, $dispatch, nextValue, e.currentTarget);
|
|
414
529
|
}}
|
|
415
530
|
/>
|
|
416
531
|
);
|
|
@@ -837,11 +952,32 @@ export const interactiveVocabulary = {
|
|
|
837
952
|
},
|
|
838
953
|
DatePicker: {
|
|
839
954
|
description:
|
|
840
|
-
"A date input. Carries `$action` describing the on-select behavior.",
|
|
955
|
+
"A date, datetime, or time input. Carries `$action` describing the on-select behavior.",
|
|
841
956
|
properties: z.object({
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
957
|
+
inputType: z
|
|
958
|
+
.enum(["date", "datetime", "time"])
|
|
959
|
+
.optional()
|
|
960
|
+
.describe(
|
|
961
|
+
'Temporal input type. Defaults to "date". Values remain strings.',
|
|
962
|
+
),
|
|
963
|
+
value: z
|
|
964
|
+
.string()
|
|
965
|
+
.optional()
|
|
966
|
+
.describe(
|
|
967
|
+
"Initial value: YYYY-MM-DD for date, HH:mm or HH:mm:ss with optional fraction of at most 9 digits for time, YYYY-MM-DDTHH:mm with optional :ss and fraction of at most 9 digits for datetime. A datetime with Z or ±HH:mm is an instant, displayed in the viewer's time zone and submitted with the same offset and precision. A datetime without an offset is local and submitted unchanged. An empty datetime submits with the viewer's offset and seconds.",
|
|
968
|
+
),
|
|
969
|
+
min: z
|
|
970
|
+
.string()
|
|
971
|
+
.optional()
|
|
972
|
+
.describe(
|
|
973
|
+
"Minimum value: YYYY-MM-DD for date, HH:mm or HH:mm:ss with optional fraction of at most 9 digits for time, YYYY-MM-DDTHH:mm with optional :ss and fraction of at most 9 digits for datetime. Datetimes with Z or ±HH:mm are converted to the viewer's time zone.",
|
|
974
|
+
),
|
|
975
|
+
max: z
|
|
976
|
+
.string()
|
|
977
|
+
.optional()
|
|
978
|
+
.describe(
|
|
979
|
+
"Maximum value: YYYY-MM-DD for date, HH:mm or HH:mm:ss with optional fraction of at most 9 digits for time, YYYY-MM-DDTHH:mm with optional :ss and fraction of at most 9 digits for datetime. Datetimes with Z or ±HH:mm are converted to the viewer's time zone.",
|
|
980
|
+
),
|
|
845
981
|
label: z
|
|
846
982
|
.string()
|
|
847
983
|
.optional()
|