@assistant-ui/react-generative-ui 0.0.22 → 0.0.24
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 +153 -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 +116 -12
- package/dist/vocabulary/interactive.js.map +1 -1
- package/package.json +6 -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 +337 -0
- package/src/temporal.ts +263 -0
- package/src/vocabulary/collectFormValues.test.ts +198 -1
- package/src/vocabulary/collectFormValues.ts +31 -2
- package/src/vocabulary/datepicker.dom.test.tsx +1457 -0
- package/src/vocabulary/interactive.dom.test.tsx +59 -11
- package/src/vocabulary/interactive.tsx +212 -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,186 @@ 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
|
+
committedValue: initialValue,
|
|
415
|
+
edited: false,
|
|
416
|
+
});
|
|
417
|
+
if (selection.initialValue !== initialValue) {
|
|
418
|
+
const isEcho = isBound && initialValue === selection.value;
|
|
419
|
+
setSelection({
|
|
420
|
+
initialValue,
|
|
421
|
+
value: initialValue,
|
|
422
|
+
anchor: isEcho ? selection.anchor : initialValue,
|
|
423
|
+
committedValue: isEcho ? selection.committedValue : initialValue,
|
|
424
|
+
edited: isEcho && selection.edited,
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
const currentValue = isBound ? initialValue : selection.value;
|
|
428
|
+
const temporal = classifyTemporal(currentValue ?? "");
|
|
429
|
+
const anchor = classifyTemporal(selection.anchor ?? "");
|
|
430
|
+
const hasInstantAnchor =
|
|
431
|
+
inputType === "datetime" && anchor.kind === "instant";
|
|
432
|
+
const canonicalFieldValue = (value: string | undefined) => {
|
|
433
|
+
const current = value ?? "";
|
|
434
|
+
const kind = classifyTemporal(current).kind;
|
|
435
|
+
if (inputType === "time")
|
|
436
|
+
return kind === "time" && normalizeTemporalInputValue(current) !== current
|
|
437
|
+
? value
|
|
438
|
+
: undefined;
|
|
439
|
+
if (inputType !== "datetime") return undefined;
|
|
440
|
+
if (kind === "instant") return value;
|
|
441
|
+
if (hasInstantAnchor) return selection.anchor;
|
|
442
|
+
return kind === "floating" &&
|
|
443
|
+
normalizeTemporalInputValue(current) !== current
|
|
444
|
+
? value
|
|
445
|
+
: undefined;
|
|
446
|
+
};
|
|
447
|
+
const minimum = classifyTemporal(min ?? "");
|
|
448
|
+
const maximum = classifyTemporal(max ?? "");
|
|
449
|
+
const hydrated = React.useSyncExternalStore(
|
|
450
|
+
subscribeToHydration,
|
|
451
|
+
clientHydrationSnapshot,
|
|
452
|
+
serverHydrationSnapshot,
|
|
453
|
+
);
|
|
454
|
+
const showRawValue =
|
|
455
|
+
inputType === "datetime" && temporal.kind === "instant" && !hydrated;
|
|
456
|
+
const displayValue =
|
|
457
|
+
currentValue === undefined
|
|
458
|
+
? undefined
|
|
459
|
+
: inputType === "datetime" && hydrated
|
|
460
|
+
? toPickerLocalDateTime(currentValue)
|
|
461
|
+
: normalizeTemporalInputValue(currentValue);
|
|
462
|
+
const keyboardActive = React.useRef(false);
|
|
463
|
+
const valueFromInput = (input: HTMLInputElement) => {
|
|
464
|
+
const inputValue = normalizeTemporalInputValue(input.value);
|
|
465
|
+
return inputType === "datetime"
|
|
466
|
+
? fromLocalDateTime(inputValue, selection.anchor)
|
|
467
|
+
: inputValue;
|
|
468
|
+
};
|
|
469
|
+
const commit = (input: HTMLInputElement) => {
|
|
470
|
+
if (!selection.edited) return;
|
|
471
|
+
const nextValue = valueFromInput(input);
|
|
472
|
+
setSelection({ ...selection, committedValue: nextValue, edited: false });
|
|
473
|
+
if (nextValue !== selection.committedValue)
|
|
474
|
+
fire($action, $dispatch, nextValue, input);
|
|
475
|
+
};
|
|
395
476
|
return (
|
|
396
477
|
<input
|
|
397
478
|
key={isBound ? undefined : initialValue}
|
|
398
|
-
type=
|
|
479
|
+
type={
|
|
480
|
+
showRawValue
|
|
481
|
+
? "text"
|
|
482
|
+
: inputType === "datetime"
|
|
483
|
+
? "datetime-local"
|
|
484
|
+
: inputType
|
|
485
|
+
}
|
|
486
|
+
readOnly={showRawValue || undefined}
|
|
399
487
|
data-aui="datepicker"
|
|
400
488
|
data-aui-action={actionAttr($action)}
|
|
489
|
+
{...{
|
|
490
|
+
[FIELD_VALUE_ATTR]: canonicalFieldValue(currentValue),
|
|
491
|
+
}}
|
|
401
492
|
name={name}
|
|
402
493
|
aria-label={label}
|
|
403
|
-
{...(isBound
|
|
404
|
-
? {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
494
|
+
{...(isBound || inputType === "datetime"
|
|
495
|
+
? { value: displayValue ?? "" }
|
|
496
|
+
: { defaultValue: displayValue })}
|
|
497
|
+
min={
|
|
498
|
+
inputType === "datetime" && hydrated && min !== undefined
|
|
499
|
+
? normalizeTemporalInputValue(
|
|
500
|
+
toLocalDateTime(
|
|
501
|
+
min,
|
|
502
|
+
minimum.kind === "instant" ? minimum.precision : undefined,
|
|
503
|
+
),
|
|
504
|
+
)
|
|
505
|
+
: min === undefined
|
|
506
|
+
? undefined
|
|
507
|
+
: normalizeTemporalInputValue(min)
|
|
508
|
+
}
|
|
509
|
+
max={
|
|
510
|
+
inputType === "datetime" && hydrated && max !== undefined
|
|
511
|
+
? normalizeTemporalInputValue(
|
|
512
|
+
toLocalDateTime(
|
|
513
|
+
max,
|
|
514
|
+
maximum.kind === "instant" ? maximum.precision : undefined,
|
|
515
|
+
),
|
|
516
|
+
)
|
|
517
|
+
: max === undefined
|
|
518
|
+
? undefined
|
|
519
|
+
: normalizeTemporalInputValue(max)
|
|
520
|
+
}
|
|
521
|
+
step={
|
|
522
|
+
inputType !== "date"
|
|
523
|
+
? getTemporalInputStep(
|
|
524
|
+
selection.anchor,
|
|
525
|
+
hasInstantAnchor ? displayValue : currentValue,
|
|
526
|
+
min,
|
|
527
|
+
max,
|
|
528
|
+
)
|
|
529
|
+
: undefined
|
|
530
|
+
}
|
|
411
531
|
onChange={(e) => {
|
|
412
|
-
|
|
413
|
-
|
|
532
|
+
const nextValue = valueFromInput(e.currentTarget);
|
|
533
|
+
const canonical = canonicalFieldValue(nextValue);
|
|
534
|
+
if (canonical !== undefined) {
|
|
535
|
+
e.currentTarget.setAttribute(FIELD_VALUE_ATTR, canonical);
|
|
536
|
+
} else {
|
|
537
|
+
e.currentTarget.removeAttribute(FIELD_VALUE_ATTR);
|
|
538
|
+
}
|
|
539
|
+
// A change without an editing key down is a pick from the native picker, which commits at once; a typed value waits for blur or Enter.
|
|
540
|
+
const typed = keyboardActive.current;
|
|
541
|
+
keyboardActive.current = false;
|
|
542
|
+
setSelection({
|
|
543
|
+
initialValue,
|
|
544
|
+
value: nextValue,
|
|
545
|
+
anchor: selection.anchor,
|
|
546
|
+
committedValue: typed ? selection.committedValue : nextValue,
|
|
547
|
+
edited: typed,
|
|
548
|
+
});
|
|
549
|
+
updateBinding?.(nextValue);
|
|
550
|
+
if (!typed && nextValue !== selection.committedValue)
|
|
551
|
+
fire($action, $dispatch, nextValue, e.currentTarget);
|
|
552
|
+
}}
|
|
553
|
+
onBlur={(e) => {
|
|
554
|
+
keyboardActive.current = false;
|
|
555
|
+
commit(e.currentTarget);
|
|
556
|
+
}}
|
|
557
|
+
onKeyDown={(e) => {
|
|
558
|
+
if (
|
|
559
|
+
e.key === "Enter" &&
|
|
560
|
+
!e.nativeEvent.isComposing &&
|
|
561
|
+
!e.currentTarget.form
|
|
562
|
+
) {
|
|
563
|
+
commit(e.currentTarget);
|
|
564
|
+
return;
|
|
565
|
+
}
|
|
566
|
+
keyboardActive.current =
|
|
567
|
+
!e.altKey &&
|
|
568
|
+
!e.ctrlKey &&
|
|
569
|
+
!e.metaKey &&
|
|
570
|
+
(e.key.length === 1 ||
|
|
571
|
+
[
|
|
572
|
+
"ArrowDown",
|
|
573
|
+
"ArrowUp",
|
|
574
|
+
"Backspace",
|
|
575
|
+
"Delete",
|
|
576
|
+
"End",
|
|
577
|
+
"Home",
|
|
578
|
+
"PageDown",
|
|
579
|
+
"PageUp",
|
|
580
|
+
].includes(e.key));
|
|
581
|
+
}}
|
|
582
|
+
onKeyUp={() => {
|
|
583
|
+
keyboardActive.current = false;
|
|
584
|
+
}}
|
|
585
|
+
onPaste={() => {
|
|
586
|
+
keyboardActive.current = true;
|
|
587
|
+
}}
|
|
588
|
+
onCut={() => {
|
|
589
|
+
keyboardActive.current = true;
|
|
414
590
|
}}
|
|
415
591
|
/>
|
|
416
592
|
);
|
|
@@ -837,11 +1013,32 @@ export const interactiveVocabulary = {
|
|
|
837
1013
|
},
|
|
838
1014
|
DatePicker: {
|
|
839
1015
|
description:
|
|
840
|
-
"A date input. Carries `$action
|
|
1016
|
+
"A date, datetime, or time input. Carries `$action`, which runs once per committed value: a pick from the picker, or a typed value on blur or Enter.",
|
|
841
1017
|
properties: z.object({
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
1018
|
+
inputType: z
|
|
1019
|
+
.enum(["date", "datetime", "time"])
|
|
1020
|
+
.optional()
|
|
1021
|
+
.describe(
|
|
1022
|
+
'Temporal input type. Defaults to "date". Values remain strings.',
|
|
1023
|
+
),
|
|
1024
|
+
value: z
|
|
1025
|
+
.string()
|
|
1026
|
+
.optional()
|
|
1027
|
+
.describe(
|
|
1028
|
+
"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.",
|
|
1029
|
+
),
|
|
1030
|
+
min: z
|
|
1031
|
+
.string()
|
|
1032
|
+
.optional()
|
|
1033
|
+
.describe(
|
|
1034
|
+
"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.",
|
|
1035
|
+
),
|
|
1036
|
+
max: z
|
|
1037
|
+
.string()
|
|
1038
|
+
.optional()
|
|
1039
|
+
.describe(
|
|
1040
|
+
"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.",
|
|
1041
|
+
),
|
|
845
1042
|
label: z
|
|
846
1043
|
.string()
|
|
847
1044
|
.optional()
|