@assistant-ui/react-generative-ui 0.0.23 → 0.1.0
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/README.md +2 -0
- package/dist/a2ui.d.ts +1 -4
- package/dist/a2ui.d.ts.map +1 -1
- package/dist/a2ui.js +1 -4
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -10
- package/dist/index.server.d.ts +1 -2
- package/dist/index.server.d.ts.map +1 -1
- package/dist/index.server.js +1 -10
- package/dist/ir.d.ts +1 -111
- package/dist/ir.d.ts.map +1 -1
- package/dist/ir.js +1 -175
- package/dist/slack.d.ts +1 -4
- package/dist/slack.d.ts.map +1 -1
- package/dist/slack.js +1 -4
- package/dist/teams.d.ts +1 -4
- package/dist/teams.d.ts.map +1 -1
- package/dist/teams.js +1 -4
- package/package.json +5 -5
- package/src/a2ui.ts +1 -22
- package/src/index.server.ts +1 -2
- package/src/index.test.ts +75 -0
- package/src/index.ts +1 -2
- package/src/ir.ts +1 -279
- package/src/slack.ts +1 -39
- package/src/teams.ts +1 -35
- package/dist/JSONGenerativeUI.client.d.ts +0 -28
- package/dist/JSONGenerativeUI.client.d.ts.map +0 -1
- package/dist/JSONGenerativeUI.client.js +0 -101
- package/dist/JSONGenerativeUI.client.js.map +0 -1
- package/dist/JSONGenerativeUI.server.d.ts +0 -21
- package/dist/JSONGenerativeUI.server.d.ts.map +0 -1
- package/dist/JSONGenerativeUI.server.js +0 -32
- package/dist/JSONGenerativeUI.server.js.map +0 -1
- package/dist/JSONGenerativeUI.shared.d.ts +0 -63
- package/dist/JSONGenerativeUI.shared.d.ts.map +0 -1
- package/dist/JSONGenerativeUI.shared.js +0 -31
- package/dist/JSONGenerativeUI.shared.js.map +0 -1
- package/dist/RadioGroupScope.d.ts +0 -6
- package/dist/RadioGroupScope.d.ts.map +0 -1
- package/dist/RadioGroupScope.js +0 -25
- package/dist/RadioGroupScope.js.map +0 -1
- package/dist/a2ui/BindingContext.d.ts +0 -11
- package/dist/a2ui/BindingContext.d.ts.map +0 -1
- package/dist/a2ui/BindingContext.js +0 -12
- package/dist/a2ui/BindingContext.js.map +0 -1
- package/dist/a2ui/PresentRenderer.d.ts +0 -11
- package/dist/a2ui/PresentRenderer.d.ts.map +0 -1
- package/dist/a2ui/PresentRenderer.js +0 -39
- package/dist/a2ui/PresentRenderer.js.map +0 -1
- package/dist/a2ui/convert.d.ts +0 -15
- package/dist/a2ui/convert.d.ts.map +0 -1
- package/dist/a2ui/convert.js +0 -728
- package/dist/a2ui/convert.js.map +0 -1
- package/dist/a2ui/dataModel.d.ts +0 -9
- package/dist/a2ui/dataModel.d.ts.map +0 -1
- package/dist/a2ui/dataModel.js +0 -44
- package/dist/a2ui/dataModel.js.map +0 -1
- package/dist/a2ui/reducer.d.ts +0 -8
- package/dist/a2ui/reducer.d.ts.map +0 -1
- package/dist/a2ui/reducer.js +0 -242
- package/dist/a2ui/reducer.js.map +0 -1
- package/dist/a2ui/snapshot.d.ts +0 -3
- package/dist/a2ui/snapshot.d.ts.map +0 -1
- package/dist/a2ui/snapshot.js +0 -33
- package/dist/a2ui/snapshot.js.map +0 -1
- package/dist/a2ui/types.d.ts +0 -88
- package/dist/a2ui/types.d.ts.map +0 -1
- package/dist/a2ui/types.js +0 -6
- package/dist/a2ui/types.js.map +0 -1
- package/dist/a2ui/valueFunctions.d.ts +0 -16
- package/dist/a2ui/valueFunctions.d.ts.map +0 -1
- package/dist/a2ui/valueFunctions.js +0 -332
- package/dist/a2ui/valueFunctions.js.map +0 -1
- package/dist/actionRegistry.d.ts +0 -29
- package/dist/actionRegistry.d.ts.map +0 -1
- package/dist/actionRegistry.js +0 -41
- package/dist/actionRegistry.js.map +0 -1
- package/dist/answeredValues.d.ts +0 -16
- package/dist/answeredValues.d.ts.map +0 -1
- package/dist/answeredValues.js +0 -37
- package/dist/answeredValues.js.map +0 -1
- package/dist/buildPresentParameters.d.ts +0 -21
- package/dist/buildPresentParameters.d.ts.map +0 -1
- package/dist/buildPresentParameters.js +0 -95
- package/dist/buildPresentParameters.js.map +0 -1
- package/dist/constants.d.ts +0 -15
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -19
- package/dist/constants.js.map +0 -1
- package/dist/convert/boundSpec.d.ts +0 -48
- package/dist/convert/boundSpec.d.ts.map +0 -1
- package/dist/convert/boundSpec.js +0 -108
- package/dist/convert/boundSpec.js.map +0 -1
- package/dist/convert/copyBounded.d.ts +0 -25
- package/dist/convert/copyBounded.d.ts.map +0 -1
- package/dist/convert/copyBounded.js +0 -38
- package/dist/convert/copyBounded.js.map +0 -1
- package/dist/convert/isElement.d.ts +0 -3
- package/dist/convert/isElement.d.ts.map +0 -1
- package/dist/convert/isElement.js +0 -6
- package/dist/convert/isElement.js.map +0 -1
- package/dist/convert/takeRun.d.ts +0 -6
- package/dist/convert/takeRun.d.ts.map +0 -1
- package/dist/convert/takeRun.js +0 -20
- package/dist/convert/takeRun.js.map +0 -1
- package/dist/defineGenerativeComponents.d.ts +0 -29
- package/dist/defineGenerativeComponents.d.ts.map +0 -1
- package/dist/defineGenerativeComponents.js +0 -34
- package/dist/defineGenerativeComponents.js.map +0 -1
- package/dist/fieldReferences.d.ts +0 -9
- package/dist/fieldReferences.d.ts.map +0 -1
- package/dist/fieldReferences.js +0 -53
- package/dist/fieldReferences.js.map +0 -1
- package/dist/generativeUIToJSX.d.ts +0 -18
- package/dist/generativeUIToJSX.d.ts.map +0 -1
- package/dist/generativeUIToJSX.js +0 -76
- package/dist/generativeUIToJSX.js.map +0 -1
- package/dist/index.shared.d.ts +0 -15
- package/dist/index.shared.d.ts.map +0 -1
- package/dist/index.shared.js +0 -9
- package/dist/ir.js.map +0 -1
- package/dist/renderGenerativeUI.d.ts +0 -14
- package/dist/renderGenerativeUI.d.ts.map +0 -1
- package/dist/renderGenerativeUI.js +0 -83
- package/dist/renderGenerativeUI.js.map +0 -1
- package/dist/scopeSchema.d.ts +0 -6
- package/dist/scopeSchema.d.ts.map +0 -1
- package/dist/scopeSchema.js +0 -56
- package/dist/scopeSchema.js.map +0 -1
- package/dist/slack/constants.d.ts +0 -93
- package/dist/slack/constants.d.ts.map +0 -1
- package/dist/slack/constants.js +0 -146
- package/dist/slack/constants.js.map +0 -1
- package/dist/slack/decodeBlockAction.d.ts +0 -10
- package/dist/slack/decodeBlockAction.d.ts.map +0 -1
- package/dist/slack/decodeBlockAction.js +0 -129
- package/dist/slack/decodeBlockAction.js.map +0 -1
- package/dist/slack/fromSlackBlocks.d.ts +0 -27
- package/dist/slack/fromSlackBlocks.d.ts.map +0 -1
- package/dist/slack/fromSlackBlocks.js +0 -483
- package/dist/slack/fromSlackBlocks.js.map +0 -1
- package/dist/slack/toSlackBlocks.d.ts +0 -4
- package/dist/slack/toSlackBlocks.d.ts.map +0 -1
- package/dist/slack/toSlackBlocks.js +0 -901
- package/dist/slack/toSlackBlocks.js.map +0 -1
- package/dist/slack/types.d.ts +0 -204
- package/dist/slack/types.d.ts.map +0 -1
- package/dist/slack/types.js +0 -0
- package/dist/teams/constants.d.ts +0 -34
- package/dist/teams/constants.d.ts.map +0 -1
- package/dist/teams/constants.js +0 -68
- package/dist/teams/constants.js.map +0 -1
- package/dist/teams/decodeSubmitData.d.ts +0 -26
- package/dist/teams/decodeSubmitData.d.ts.map +0 -1
- package/dist/teams/decodeSubmitData.js +0 -70
- package/dist/teams/decodeSubmitData.js.map +0 -1
- package/dist/teams/temporalId.d.ts +0 -5
- package/dist/teams/temporalId.d.ts.map +0 -1
- package/dist/teams/temporalId.js +0 -28
- package/dist/teams/temporalId.js.map +0 -1
- package/dist/teams/toAdaptiveCard.d.ts +0 -45
- package/dist/teams/toAdaptiveCard.d.ts.map +0 -1
- package/dist/teams/toAdaptiveCard.js +0 -642
- package/dist/teams/toAdaptiveCard.js.map +0 -1
- package/dist/teams/toTeamsAttachments.d.ts +0 -10
- package/dist/teams/toTeamsAttachments.d.ts.map +0 -1
- package/dist/teams/toTeamsAttachments.js +0 -99
- package/dist/teams/toTeamsAttachments.js.map +0 -1
- package/dist/teams/types.d.ts +0 -224
- package/dist/teams/types.d.ts.map +0 -1
- package/dist/teams/types.js +0 -0
- package/dist/temporal.d.ts +0 -33
- package/dist/temporal.d.ts.map +0 -1
- package/dist/temporal.js +0 -152
- package/dist/temporal.js.map +0 -1
- package/dist/types.d.ts +0 -99
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -0
- package/dist/vocabulary/alert.d.ts +0 -32
- package/dist/vocabulary/alert.d.ts.map +0 -1
- package/dist/vocabulary/alert.js +0 -160
- package/dist/vocabulary/alert.js.map +0 -1
- package/dist/vocabulary/collectFormValues.d.ts +0 -23
- package/dist/vocabulary/collectFormValues.d.ts.map +0 -1
- package/dist/vocabulary/collectFormValues.js +0 -44
- package/dist/vocabulary/collectFormValues.js.map +0 -1
- package/dist/vocabulary/data.d.ts +0 -66
- package/dist/vocabulary/data.d.ts.map +0 -1
- package/dist/vocabulary/data.js +0 -503
- package/dist/vocabulary/data.js.map +0 -1
- package/dist/vocabulary/dispatch.d.ts +0 -9
- package/dist/vocabulary/dispatch.d.ts.map +0 -1
- package/dist/vocabulary/dispatch.js +0 -50
- package/dist/vocabulary/dispatch.js.map +0 -1
- package/dist/vocabulary/fact.d.ts +0 -19
- package/dist/vocabulary/fact.d.ts.map +0 -1
- package/dist/vocabulary/fact.js +0 -51
- package/dist/vocabulary/fact.js.map +0 -1
- package/dist/vocabulary/form.d.ts +0 -11
- package/dist/vocabulary/form.d.ts.map +0 -1
- package/dist/vocabulary/form.js +0 -23
- package/dist/vocabulary/form.js.map +0 -1
- package/dist/vocabulary/formatValue.d.ts +0 -7
- package/dist/vocabulary/formatValue.d.ts.map +0 -1
- package/dist/vocabulary/formatValue.js +0 -76
- package/dist/vocabulary/formatValue.js.map +0 -1
- package/dist/vocabulary/icon.d.ts +0 -41
- package/dist/vocabulary/icon.d.ts.map +0 -1
- package/dist/vocabulary/icon.js +0 -272
- package/dist/vocabulary/icon.js.map +0 -1
- package/dist/vocabulary/index.d.ts +0 -12
- package/dist/vocabulary/index.d.ts.map +0 -1
- package/dist/vocabulary/index.js +0 -36
- package/dist/vocabulary/index.js.map +0 -1
- package/dist/vocabulary/interactive.d.ts +0 -230
- package/dist/vocabulary/interactive.d.ts.map +0 -1
- package/dist/vocabulary/interactive.js +0 -530
- package/dist/vocabulary/interactive.js.map +0 -1
- package/dist/vocabulary/layout.d.ts +0 -79
- package/dist/vocabulary/layout.d.ts.map +0 -1
- package/dist/vocabulary/layout.js +0 -139
- package/dist/vocabulary/layout.js.map +0 -1
- package/dist/vocabulary/list.d.ts +0 -14
- package/dist/vocabulary/list.d.ts.map +0 -1
- package/dist/vocabulary/list.js +0 -50
- package/dist/vocabulary/list.js.map +0 -1
- package/dist/vocabulary/media.d.ts +0 -25
- package/dist/vocabulary/media.d.ts.map +0 -1
- package/dist/vocabulary/media.js +0 -42
- package/dist/vocabulary/media.js.map +0 -1
- package/dist/vocabulary/text.d.ts +0 -57
- package/dist/vocabulary/text.d.ts.map +0 -1
- package/dist/vocabulary/text.js +0 -49
- package/dist/vocabulary/text.js.map +0 -1
- package/dist/vocabulary/toTextContent.d.ts +0 -2
- package/dist/vocabulary/toTextContent.d.ts.map +0 -1
- package/dist/vocabulary/toTextContent.js +0 -6
- package/dist/vocabulary/toTextContent.js.map +0 -1
- package/src/JSONGenerativeUI.client.tsx +0 -168
- package/src/JSONGenerativeUI.server.tsx +0 -39
- package/src/JSONGenerativeUI.shared.ts +0 -90
- package/src/JSONGenerativeUI.test.tsx +0 -1298
- package/src/RadioGroupScope.server.test.tsx +0 -24
- package/src/RadioGroupScope.tsx +0 -34
- package/src/a2ui/BindingContext.ts +0 -26
- package/src/a2ui/PresentRenderer.tsx +0 -83
- package/src/a2ui/convert.test.ts +0 -2923
- package/src/a2ui/convert.ts +0 -1207
- package/src/a2ui/dataModel.ts +0 -69
- package/src/a2ui/reducer.test.ts +0 -422
- package/src/a2ui/reducer.ts +0 -363
- package/src/a2ui/snapshot.test.ts +0 -52
- package/src/a2ui/snapshot.ts +0 -52
- package/src/a2ui/types.ts +0 -114
- package/src/a2ui/valueFunctions.test.ts +0 -271
- package/src/a2ui/valueFunctions.ts +0 -523
- package/src/actionRegistry.test.ts +0 -90
- package/src/actionRegistry.ts +0 -88
- package/src/answeredValues.test.ts +0 -33
- package/src/answeredValues.tsx +0 -70
- package/src/buildPresentParameters.test.ts +0 -246
- package/src/buildPresentParameters.ts +0 -130
- package/src/constants.ts +0 -17
- package/src/convert/boundSpec.test.ts +0 -192
- package/src/convert/boundSpec.ts +0 -134
- package/src/convert/copyBounded.test.ts +0 -131
- package/src/convert/copyBounded.ts +0 -38
- package/src/convert/isElement.ts +0 -6
- package/src/convert/takeRun.test.ts +0 -64
- package/src/convert/takeRun.ts +0 -19
- package/src/defineGenerativeComponents.ts +0 -38
- package/src/fieldReferences.test.ts +0 -66
- package/src/fieldReferences.ts +0 -83
- package/src/generativeUIToJSX.test.ts +0 -268
- package/src/generativeUIToJSX.ts +0 -152
- package/src/index.server.test.ts +0 -106
- package/src/index.shared.ts +0 -61
- package/src/ir.test.ts +0 -203
- package/src/renderGenerativeUI.test.tsx +0 -373
- package/src/renderGenerativeUI.tsx +0 -165
- package/src/scopeSchema.test.ts +0 -72
- package/src/scopeSchema.ts +0 -71
- package/src/slack/a2uiSlack.test.ts +0 -198
- package/src/slack/constants.ts +0 -194
- package/src/slack/datePicker.test.ts +0 -612
- package/src/slack/decodeBlockAction.test.ts +0 -233
- package/src/slack/decodeBlockAction.ts +0 -232
- package/src/slack/fromSlackBlocks.test.ts +0 -1465
- package/src/slack/fromSlackBlocks.ts +0 -789
- package/src/slack/toSlackBlocks.test.ts +0 -3065
- package/src/slack/toSlackBlocks.ts +0 -1818
- package/src/slack/types.ts +0 -264
- package/src/teams/constants.ts +0 -93
- package/src/teams/decodeSubmitData.test.ts +0 -397
- package/src/teams/decodeSubmitData.ts +0 -110
- package/src/teams/temporal.test.ts +0 -766
- package/src/teams/temporalId.ts +0 -56
- package/src/teams/toAdaptiveCard.test.ts +0 -1897
- package/src/teams/toAdaptiveCard.ts +0 -1057
- package/src/teams/toTeamsAttachments.test.ts +0 -74
- package/src/teams/toTeamsAttachments.ts +0 -133
- package/src/teams/types.ts +0 -266
- package/src/temporal.test.ts +0 -319
- package/src/temporal.ts +0 -259
- package/src/types.ts +0 -119
- package/src/vocabulary/alert.test.tsx +0 -427
- package/src/vocabulary/alert.tsx +0 -217
- package/src/vocabulary/collectFormValues.test.ts +0 -389
- package/src/vocabulary/collectFormValues.ts +0 -116
- package/src/vocabulary/data.test.tsx +0 -858
- package/src/vocabulary/data.tsx +0 -819
- package/src/vocabulary/datepicker.dom.test.tsx +0 -1148
- package/src/vocabulary/dispatch.ts +0 -83
- package/src/vocabulary/fact.test.tsx +0 -94
- package/src/vocabulary/fact.tsx +0 -54
- package/src/vocabulary/form.test.tsx +0 -113
- package/src/vocabulary/form.tsx +0 -39
- package/src/vocabulary/formatValue.test.ts +0 -72
- package/src/vocabulary/formatValue.ts +0 -127
- package/src/vocabulary/icon.test.tsx +0 -64
- package/src/vocabulary/icon.tsx +0 -174
- package/src/vocabulary/index.test.ts +0 -65
- package/src/vocabulary/index.ts +0 -33
- package/src/vocabulary/interactive.dispatch.test.tsx +0 -454
- package/src/vocabulary/interactive.dom.test.tsx +0 -1747
- package/src/vocabulary/interactive.test.tsx +0 -377
- package/src/vocabulary/interactive.tsx +0 -1048
- package/src/vocabulary/layout.test.tsx +0 -323
- package/src/vocabulary/layout.tsx +0 -247
- package/src/vocabulary/list.test.tsx +0 -222
- package/src/vocabulary/list.tsx +0 -50
- package/src/vocabulary/media.test.tsx +0 -85
- package/src/vocabulary/media.tsx +0 -56
- package/src/vocabulary/text.test.tsx +0 -102
- package/src/vocabulary/text.tsx +0 -60
- package/src/vocabulary/toTextContent.ts +0 -2
|
@@ -1,323 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi } from "vitest";
|
|
2
|
-
import type { ReactElement } from "react";
|
|
3
|
-
import { renderToStaticMarkup } from "react-dom/server";
|
|
4
|
-
import { renderGenerativeUI } from "../renderGenerativeUI";
|
|
5
|
-
import { createActionRegistry } from "../actionRegistry";
|
|
6
|
-
import { layoutVocabulary } from "./layout";
|
|
7
|
-
import type { FormControlElementLike } from "./collectFormValues";
|
|
8
|
-
|
|
9
|
-
const render = (node: unknown) =>
|
|
10
|
-
renderToStaticMarkup(<>{renderGenerativeUI(node, layoutVocabulary)}</>);
|
|
11
|
-
|
|
12
|
-
const el = (
|
|
13
|
-
partial: Partial<FormControlElementLike>,
|
|
14
|
-
): FormControlElementLike => ({
|
|
15
|
-
name: "",
|
|
16
|
-
type: "text",
|
|
17
|
-
value: "",
|
|
18
|
-
disabled: false,
|
|
19
|
-
hasAttribute: () => false,
|
|
20
|
-
...partial,
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const getFooterButtons = (out: ReactElement) => {
|
|
24
|
-
const footer = (out.props as { children: unknown[] })
|
|
25
|
-
.children[2] as ReactElement;
|
|
26
|
-
const [confirmBtn, cancelBtn] = (
|
|
27
|
-
footer.props as { children: [ReactElement | null, ReactElement | null] }
|
|
28
|
-
).children;
|
|
29
|
-
return { confirmBtn, cancelBtn };
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
describe("layoutVocabulary", () => {
|
|
33
|
-
it("Card renders a section with optional title and padding/background hooks", () => {
|
|
34
|
-
expect(
|
|
35
|
-
render({
|
|
36
|
-
$type: "Card",
|
|
37
|
-
title: "T",
|
|
38
|
-
padding: 2,
|
|
39
|
-
background: "var(--muted)",
|
|
40
|
-
}),
|
|
41
|
-
).toBe(
|
|
42
|
-
'<section data-aui="card" data-aui-padding="2" data-aui-background="var(--muted)" data-aui-surface="" style="background:var(--muted);color:white"><header data-aui="card-title">T</header></section>',
|
|
43
|
-
);
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it("Card flags itself as a surface only when a background or a footer makes it one", () => {
|
|
47
|
-
expect(render({ $type: "Card", title: "Plain" })).not.toContain(
|
|
48
|
-
"data-aui-surface",
|
|
49
|
-
);
|
|
50
|
-
expect(render({ $type: "Card", background: "var(--muted)" })).toContain(
|
|
51
|
-
"data-aui-surface",
|
|
52
|
-
);
|
|
53
|
-
expect(render({ $type: "Card", confirm: { label: "Yes" } })).toContain(
|
|
54
|
-
"data-aui-surface",
|
|
55
|
-
);
|
|
56
|
-
expect(render({ $type: "Card", cancel: { label: "No" } })).toContain(
|
|
57
|
-
"data-aui-surface",
|
|
58
|
-
);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("Card applies background as an inline style so arbitrary CSS values (colors, gradients) render", () => {
|
|
62
|
-
const html = render({
|
|
63
|
-
$type: "Card",
|
|
64
|
-
background: "linear-gradient(90deg, #3b82f6, #1d4ed8)",
|
|
65
|
-
});
|
|
66
|
-
expect(html).toContain(
|
|
67
|
-
'style="background:linear-gradient(90deg, #3b82f6, #1d4ed8);color:white"',
|
|
68
|
-
);
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
it("Card sets its text color to white whenever a background is provided", () => {
|
|
72
|
-
const html = render({ $type: "Card", background: "#1d4ed8" });
|
|
73
|
-
expect(html).toContain("color:white");
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it("Card omits the style attribute when background is not provided", () => {
|
|
77
|
-
expect(render({ $type: "Card", title: "T" })).not.toContain("style=");
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it("Card without a title omits the header", () => {
|
|
81
|
-
expect(render({ $type: "Card" })).toBe(
|
|
82
|
-
'<section data-aui="card"></section>',
|
|
83
|
-
);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it("Col renders a div with gap/align hooks and nested children", () => {
|
|
87
|
-
expect(
|
|
88
|
-
render({
|
|
89
|
-
$type: "Col",
|
|
90
|
-
gap: 2,
|
|
91
|
-
align: "center",
|
|
92
|
-
children: { $type: "Text", value: "x" } as never,
|
|
93
|
-
}),
|
|
94
|
-
).toContain('data-aui="col"');
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it("Row renders a div with gap/align/justify hooks", () => {
|
|
98
|
-
const html = render({
|
|
99
|
-
$type: "Row",
|
|
100
|
-
gap: 4,
|
|
101
|
-
align: "start",
|
|
102
|
-
justify: "between",
|
|
103
|
-
});
|
|
104
|
-
expect(html).toBe(
|
|
105
|
-
'<div data-aui="row" data-aui-gap="4" data-aui-align="start" data-aui-justify="between"></div>',
|
|
106
|
-
);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("Spacer renders an empty div", () => {
|
|
110
|
-
expect(render({ $type: "Spacer" })).toBe('<div data-aui="spacer"></div>');
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
it("Badge renders a span with the value and variant hook", () => {
|
|
114
|
-
expect(render({ $type: "Badge", value: "new", variant: "success" })).toBe(
|
|
115
|
-
'<span data-aui="badge" data-aui-variant="success">new</span>',
|
|
116
|
-
);
|
|
117
|
-
});
|
|
118
|
-
|
|
119
|
-
it("ignores malformed card and badge text properties", () => {
|
|
120
|
-
expect(
|
|
121
|
-
render({
|
|
122
|
-
$type: "Card",
|
|
123
|
-
title: { unexpected: true },
|
|
124
|
-
confirm: { label: { unexpected: true } },
|
|
125
|
-
cancel: { label: { unexpected: true } },
|
|
126
|
-
}),
|
|
127
|
-
).toBe(
|
|
128
|
-
'<section data-aui="card" data-aui-surface=""><footer data-aui="card-footer"><button type="button" data-aui="card-confirm"></button><button type="button" data-aui="card-cancel"></button></footer></section>',
|
|
129
|
-
);
|
|
130
|
-
expect(render({ $type: "Badge", value: { unexpected: true } })).toBe(
|
|
131
|
-
'<span data-aui="badge"></span>',
|
|
132
|
-
);
|
|
133
|
-
});
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
describe("layoutVocabulary Box", () => {
|
|
137
|
-
it("renders a bare div with no style attribute when no props are given", () => {
|
|
138
|
-
expect(render({ $type: "Box" })).toBe('<div data-aui="box"></div>');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("applies width/height as inline pixel lengths when given numbers", () => {
|
|
142
|
-
const html = render({ $type: "Box", width: 200, height: 8 });
|
|
143
|
-
expect(html).toContain('style="width:200px;height:8px"');
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
it("applies width/height verbatim when given strings", () => {
|
|
147
|
-
const html = render({ $type: "Box", width: "100%", height: "1.5rem" });
|
|
148
|
-
expect(html).toContain('style="width:100%;height:1.5rem"');
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it("applies a numeric radius as an inline pixel border-radius", () => {
|
|
152
|
-
const html = render({ $type: "Box", radius: 6 });
|
|
153
|
-
expect(html).toContain('style="border-radius:6px"');
|
|
154
|
-
expect(html).not.toContain("data-aui-radius");
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it('applies radius "full" as a 9999px inline border-radius and a data-aui-radius hook', () => {
|
|
158
|
-
const html = render({ $type: "Box", radius: "full" });
|
|
159
|
-
expect(html).toContain('data-aui-radius="full"');
|
|
160
|
-
expect(html).toContain('style="border-radius:9999px"');
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it("applies background as an inline style", () => {
|
|
164
|
-
const html = render({ $type: "Box", background: "#3b82f6" });
|
|
165
|
-
expect(html).toContain('style="background:#3b82f6"');
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("renders a progress bar as a track Box containing a partial-width fill Box", () => {
|
|
169
|
-
const html = render({
|
|
170
|
-
$type: "Box",
|
|
171
|
-
width: "100%",
|
|
172
|
-
height: 8,
|
|
173
|
-
radius: "full",
|
|
174
|
-
background: "var(--muted)",
|
|
175
|
-
children: {
|
|
176
|
-
$type: "Box",
|
|
177
|
-
width: "60%",
|
|
178
|
-
height: 8,
|
|
179
|
-
radius: "full",
|
|
180
|
-
background: "var(--primary)",
|
|
181
|
-
},
|
|
182
|
-
});
|
|
183
|
-
expect(html).toBe(
|
|
184
|
-
'<div data-aui="box" data-aui-radius="full" style="width:100%;height:8px;background:var(--muted);border-radius:9999px">' +
|
|
185
|
-
'<div data-aui="box" data-aui-radius="full" style="width:60%;height:8px;background:var(--primary);border-radius:9999px"></div>' +
|
|
186
|
-
"</div>",
|
|
187
|
-
);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
it("renders nested children alongside style props", () => {
|
|
191
|
-
const html = render({
|
|
192
|
-
$type: "Box",
|
|
193
|
-
background: "red",
|
|
194
|
-
children: "content",
|
|
195
|
-
});
|
|
196
|
-
expect(html).toBe(
|
|
197
|
-
'<div data-aui="box" style="background:red">content</div>',
|
|
198
|
-
);
|
|
199
|
-
});
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
describe("layoutVocabulary Card asForm/confirm/cancel", () => {
|
|
203
|
-
it("Card with asForm renders a form element with the data-aui-asform hook", () => {
|
|
204
|
-
const html = render({ $type: "Card", asForm: true, title: "T" });
|
|
205
|
-
expect(html).toContain("<form ");
|
|
206
|
-
expect(html).toContain('data-aui-asform="true"');
|
|
207
|
-
expect(html).not.toContain("<section");
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it("Card without asForm renders a section with no data-aui-asform hook", () => {
|
|
211
|
-
const html = render({ $type: "Card" });
|
|
212
|
-
expect(html).toContain("<section");
|
|
213
|
-
expect(html).not.toContain("data-aui-asform");
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
it("Card omits the footer when neither confirm nor cancel is present", () => {
|
|
217
|
-
expect(render({ $type: "Card" })).not.toContain("card-footer");
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it("Card renders a footer when only confirm is present", () => {
|
|
221
|
-
expect(render({ $type: "Card", confirm: { label: "OK" } })).toContain(
|
|
222
|
-
'data-aui="card-footer"',
|
|
223
|
-
);
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
it("Card renders a footer when only cancel is present", () => {
|
|
227
|
-
expect(render({ $type: "Card", cancel: { label: "Cancel" } })).toContain(
|
|
228
|
-
'data-aui="card-footer"',
|
|
229
|
-
);
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
it("Card confirm renders a submit button when asForm is set", () => {
|
|
233
|
-
const html = render({
|
|
234
|
-
$type: "Card",
|
|
235
|
-
asForm: true,
|
|
236
|
-
confirm: { label: "Save" },
|
|
237
|
-
});
|
|
238
|
-
expect(html).toContain('type="submit" data-aui="card-confirm"');
|
|
239
|
-
expect(html).toContain("Save");
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("Card confirm renders a click button when asForm is not set", () => {
|
|
243
|
-
const html = render({ $type: "Card", confirm: { label: "Save" } });
|
|
244
|
-
expect(html).toContain('type="button" data-aui="card-confirm"');
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it("Card cancel always renders a plain button regardless of asForm", () => {
|
|
248
|
-
const html = render({
|
|
249
|
-
$type: "Card",
|
|
250
|
-
asForm: true,
|
|
251
|
-
cancel: { label: "Dismiss" },
|
|
252
|
-
});
|
|
253
|
-
expect(html).toContain('type="button" data-aui="card-cancel"');
|
|
254
|
-
});
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
describe("layoutVocabulary Card asForm/confirm/cancel dispatch", () => {
|
|
258
|
-
it("Card asForm submit collects named values and dispatches confirm.$action with $input", () => {
|
|
259
|
-
const handler = vi.fn();
|
|
260
|
-
const registry = createActionRegistry({ save: handler });
|
|
261
|
-
const out = layoutVocabulary.Card.render({
|
|
262
|
-
asForm: true,
|
|
263
|
-
confirm: { label: "Save", $action: { type: "save" } },
|
|
264
|
-
$status: "done",
|
|
265
|
-
$dispatch: registry.dispatch,
|
|
266
|
-
}) as ReactElement;
|
|
267
|
-
const onSubmit = (out.props as { onSubmit: (e: unknown) => void }).onSubmit;
|
|
268
|
-
const event = {
|
|
269
|
-
preventDefault: vi.fn(),
|
|
270
|
-
currentTarget: {
|
|
271
|
-
elements: [el({ name: "email", type: "email", value: "a@x.com" })],
|
|
272
|
-
},
|
|
273
|
-
};
|
|
274
|
-
onSubmit(event);
|
|
275
|
-
expect(event.preventDefault).toHaveBeenCalled();
|
|
276
|
-
expect(handler).toHaveBeenCalledWith({
|
|
277
|
-
payload: { type: "save", $input: { email: "a@x.com" } },
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
it("Card confirm click (non-asForm) fires confirm.$action without $input", () => {
|
|
282
|
-
const handler = vi.fn();
|
|
283
|
-
const registry = createActionRegistry({ save: handler });
|
|
284
|
-
const out = layoutVocabulary.Card.render({
|
|
285
|
-
confirm: { label: "Save", $action: { type: "save" } },
|
|
286
|
-
$status: "done",
|
|
287
|
-
$dispatch: registry.dispatch,
|
|
288
|
-
}) as ReactElement;
|
|
289
|
-
const { confirmBtn } = getFooterButtons(out);
|
|
290
|
-
const onClick = (confirmBtn!.props as { onClick: (e: object) => void })
|
|
291
|
-
.onClick;
|
|
292
|
-
onClick({ currentTarget: {} });
|
|
293
|
-
expect(handler).toHaveBeenCalledWith({ payload: { type: "save" } });
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it("Card confirm button has no onClick when asForm is set (the form submit handles it)", () => {
|
|
297
|
-
const out = layoutVocabulary.Card.render({
|
|
298
|
-
asForm: true,
|
|
299
|
-
confirm: { label: "Save", $action: { type: "save" } },
|
|
300
|
-
$status: "done",
|
|
301
|
-
}) as ReactElement;
|
|
302
|
-
const { confirmBtn } = getFooterButtons(out);
|
|
303
|
-
expect(
|
|
304
|
-
(confirmBtn!.props as { onClick?: unknown }).onClick,
|
|
305
|
-
).toBeUndefined();
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
it("Card cancel click fires cancel.$action without $input regardless of asForm", () => {
|
|
309
|
-
const handler = vi.fn();
|
|
310
|
-
const registry = createActionRegistry({ dismiss: handler });
|
|
311
|
-
const out = layoutVocabulary.Card.render({
|
|
312
|
-
asForm: true,
|
|
313
|
-
cancel: { label: "Dismiss", $action: { type: "dismiss" } },
|
|
314
|
-
$status: "done",
|
|
315
|
-
$dispatch: registry.dispatch,
|
|
316
|
-
}) as ReactElement;
|
|
317
|
-
const { cancelBtn } = getFooterButtons(out);
|
|
318
|
-
const onClick = (cancelBtn!.props as { onClick: (e: object) => void })
|
|
319
|
-
.onClick;
|
|
320
|
-
onClick({ currentTarget: {} });
|
|
321
|
-
expect(handler).toHaveBeenCalledWith({ payload: { type: "dismiss" } });
|
|
322
|
-
});
|
|
323
|
-
});
|
|
@@ -1,247 +0,0 @@
|
|
|
1
|
-
import type { CSSProperties, FormEvent } from "react";
|
|
2
|
-
import { z } from "zod";
|
|
3
|
-
import type { GenerativeUILibrary } from "../types";
|
|
4
|
-
import { ALIGNS, JUSTIFIES } from "../ir";
|
|
5
|
-
import { fire } from "./dispatch";
|
|
6
|
-
import { collectFormValuesFromEvent } from "./collectFormValues";
|
|
7
|
-
import { toTextContent } from "./toTextContent";
|
|
8
|
-
|
|
9
|
-
const toCssLength = (value: string | number): string =>
|
|
10
|
-
typeof value === "number" ? `${value}px` : value;
|
|
11
|
-
|
|
12
|
-
const cardActionSchema = z.looseObject({
|
|
13
|
-
type: z
|
|
14
|
-
.string()
|
|
15
|
-
.describe("The action type, resolved by the host's action registry."),
|
|
16
|
-
});
|
|
17
|
-
|
|
18
|
-
const cardFooterButtonSchema = z.object({
|
|
19
|
-
label: z.string().describe("Footer button label."),
|
|
20
|
-
$action: cardActionSchema
|
|
21
|
-
.optional()
|
|
22
|
-
.describe("Action fired when the button is activated."),
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
export const layoutVocabulary = {
|
|
26
|
-
Card: {
|
|
27
|
-
description:
|
|
28
|
-
"A titled section of related content, the default way to break a response into parts. It renders as plain content, so several in a row read as one answer rather than a stack of boxes; it takes on a framed surface only when `background` is set, when `confirm`/`cancel` add a footer, or when it is a slot in a `Carousel`. Set `asForm` to collect named child control values on submit.",
|
|
29
|
-
properties: z.object({
|
|
30
|
-
title: z.string().optional().describe("Optional card title."),
|
|
31
|
-
padding: z
|
|
32
|
-
.number()
|
|
33
|
-
.min(0)
|
|
34
|
-
.max(8)
|
|
35
|
-
.optional()
|
|
36
|
-
.describe(
|
|
37
|
-
"Padding in 4px units (e.g. 2 = 8px). 0 to 8 is the supported range.",
|
|
38
|
-
),
|
|
39
|
-
background: z
|
|
40
|
-
.string()
|
|
41
|
-
.optional()
|
|
42
|
-
.describe(
|
|
43
|
-
"Background color or gradient (any CSS background value). Setting this also switches the card's own text to white, since a custom background makes the card a tinted surface; children can still opt into a different color explicitly.",
|
|
44
|
-
),
|
|
45
|
-
asForm: z
|
|
46
|
-
.boolean()
|
|
47
|
-
.optional()
|
|
48
|
-
.describe(
|
|
49
|
-
"Render as a form; submitting it fires `confirm.$action` with every named child control's value, keyed by `name`.",
|
|
50
|
-
),
|
|
51
|
-
confirm: cardFooterButtonSchema
|
|
52
|
-
.optional()
|
|
53
|
-
.describe("Confirm button shown in the footer."),
|
|
54
|
-
cancel: cardFooterButtonSchema
|
|
55
|
-
.optional()
|
|
56
|
-
.describe("Cancel button shown in the footer."),
|
|
57
|
-
}),
|
|
58
|
-
render: ({
|
|
59
|
-
title,
|
|
60
|
-
padding,
|
|
61
|
-
background,
|
|
62
|
-
asForm,
|
|
63
|
-
confirm,
|
|
64
|
-
cancel,
|
|
65
|
-
$dispatch,
|
|
66
|
-
children,
|
|
67
|
-
}) => {
|
|
68
|
-
const Root = asForm ? "form" : "section";
|
|
69
|
-
const cardTitle = toTextContent(title);
|
|
70
|
-
const footer =
|
|
71
|
-
confirm || cancel ? (
|
|
72
|
-
<footer data-aui="card-footer">
|
|
73
|
-
{confirm ? (
|
|
74
|
-
<button
|
|
75
|
-
type={asForm ? "submit" : "button"}
|
|
76
|
-
data-aui="card-confirm"
|
|
77
|
-
onClick={
|
|
78
|
-
asForm
|
|
79
|
-
? undefined
|
|
80
|
-
: (e) =>
|
|
81
|
-
fire(
|
|
82
|
-
confirm.$action,
|
|
83
|
-
$dispatch,
|
|
84
|
-
undefined,
|
|
85
|
-
e.currentTarget,
|
|
86
|
-
)
|
|
87
|
-
}
|
|
88
|
-
>
|
|
89
|
-
{toTextContent(confirm.label)}
|
|
90
|
-
</button>
|
|
91
|
-
) : null}
|
|
92
|
-
{cancel ? (
|
|
93
|
-
<button
|
|
94
|
-
type="button"
|
|
95
|
-
data-aui="card-cancel"
|
|
96
|
-
onClick={(e) =>
|
|
97
|
-
fire(cancel.$action, $dispatch, undefined, e.currentTarget)
|
|
98
|
-
}
|
|
99
|
-
>
|
|
100
|
-
{toTextContent(cancel.label)}
|
|
101
|
-
</button>
|
|
102
|
-
) : null}
|
|
103
|
-
</footer>
|
|
104
|
-
) : null;
|
|
105
|
-
|
|
106
|
-
return (
|
|
107
|
-
<Root
|
|
108
|
-
data-aui="card"
|
|
109
|
-
data-aui-padding={padding}
|
|
110
|
-
data-aui-background={background}
|
|
111
|
-
data-aui-surface={
|
|
112
|
-
background !== undefined || footer !== null ? "" : undefined
|
|
113
|
-
}
|
|
114
|
-
style={
|
|
115
|
-
background !== undefined
|
|
116
|
-
? { background, color: "white" }
|
|
117
|
-
: undefined
|
|
118
|
-
}
|
|
119
|
-
data-aui-asform={asForm || undefined}
|
|
120
|
-
onSubmit={
|
|
121
|
-
asForm
|
|
122
|
-
? (event: FormEvent<HTMLFormElement>) => {
|
|
123
|
-
event.preventDefault();
|
|
124
|
-
fire(
|
|
125
|
-
confirm?.$action,
|
|
126
|
-
$dispatch,
|
|
127
|
-
collectFormValuesFromEvent(event),
|
|
128
|
-
event.currentTarget,
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
: undefined
|
|
132
|
-
}
|
|
133
|
-
>
|
|
134
|
-
{cardTitle ? (
|
|
135
|
-
<header data-aui="card-title">{cardTitle}</header>
|
|
136
|
-
) : null}
|
|
137
|
-
{children}
|
|
138
|
-
{footer}
|
|
139
|
-
</Root>
|
|
140
|
-
);
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
Col: {
|
|
144
|
-
description: "A vertical stack; children laid out top to bottom.",
|
|
145
|
-
properties: z.object({
|
|
146
|
-
gap: z
|
|
147
|
-
.number()
|
|
148
|
-
.min(0)
|
|
149
|
-
.max(8)
|
|
150
|
-
.optional()
|
|
151
|
-
.describe(
|
|
152
|
-
"Gap between children in 4px units. 0 to 8 is the supported range.",
|
|
153
|
-
),
|
|
154
|
-
align: z.enum(ALIGNS).optional().describe("Cross-axis alignment."),
|
|
155
|
-
}),
|
|
156
|
-
render: ({ gap, align, children }) => (
|
|
157
|
-
<div data-aui="col" data-aui-gap={gap} data-aui-align={align}>
|
|
158
|
-
{children}
|
|
159
|
-
</div>
|
|
160
|
-
),
|
|
161
|
-
},
|
|
162
|
-
Row: {
|
|
163
|
-
description: "A horizontal row; children laid out left to right.",
|
|
164
|
-
properties: z.object({
|
|
165
|
-
gap: z
|
|
166
|
-
.number()
|
|
167
|
-
.min(0)
|
|
168
|
-
.max(8)
|
|
169
|
-
.optional()
|
|
170
|
-
.describe(
|
|
171
|
-
"Gap between children in 4px units. 0 to 8 is the supported range.",
|
|
172
|
-
),
|
|
173
|
-
align: z.enum(ALIGNS).optional().describe("Cross-axis alignment."),
|
|
174
|
-
justify: z.enum(JUSTIFIES).optional().describe("Main-axis distribution."),
|
|
175
|
-
}),
|
|
176
|
-
render: ({ gap, align, justify, children }) => (
|
|
177
|
-
<div
|
|
178
|
-
data-aui="row"
|
|
179
|
-
data-aui-gap={gap}
|
|
180
|
-
data-aui-align={align}
|
|
181
|
-
data-aui-justify={justify}
|
|
182
|
-
>
|
|
183
|
-
{children}
|
|
184
|
-
</div>
|
|
185
|
-
),
|
|
186
|
-
},
|
|
187
|
-
Spacer: {
|
|
188
|
-
description: "Empty space that pushes neighbors apart.",
|
|
189
|
-
properties: z.object({}),
|
|
190
|
-
render: () => <div data-aui="spacer" />,
|
|
191
|
-
},
|
|
192
|
-
Badge: {
|
|
193
|
-
description: "A small labeled tag, e.g. for status or category.",
|
|
194
|
-
properties: z.object({
|
|
195
|
-
value: z.string().describe("Badge text."),
|
|
196
|
-
variant: z.string().optional().describe("Visual variant token."),
|
|
197
|
-
}),
|
|
198
|
-
render: ({ value, variant, children }) => (
|
|
199
|
-
<span data-aui="badge" data-aui-variant={variant}>
|
|
200
|
-
{toTextContent(value)}
|
|
201
|
-
{children}
|
|
202
|
-
</span>
|
|
203
|
-
),
|
|
204
|
-
},
|
|
205
|
-
Box: {
|
|
206
|
-
description:
|
|
207
|
-
"A generic container for composition, e.g. a progress bar built from an outer track Box containing a partial-width fill Box. Size, radius, and background accept arbitrary values, so they render as inline styles rather than data-attribute hooks.",
|
|
208
|
-
properties: z.object({
|
|
209
|
-
width: z
|
|
210
|
-
.union([z.string(), z.number()])
|
|
211
|
-
.optional()
|
|
212
|
-
.describe("Width; a number is pixels."),
|
|
213
|
-
height: z
|
|
214
|
-
.union([z.string(), z.number()])
|
|
215
|
-
.optional()
|
|
216
|
-
.describe("Height; a number is pixels."),
|
|
217
|
-
radius: z
|
|
218
|
-
.union([z.literal("full"), z.number()])
|
|
219
|
-
.optional()
|
|
220
|
-
.describe(
|
|
221
|
-
'Corner radius; "full" is a pill/circle, a number is pixels.',
|
|
222
|
-
),
|
|
223
|
-
background: z
|
|
224
|
-
.string()
|
|
225
|
-
.optional()
|
|
226
|
-
.describe("Background color or gradient (any CSS background value)."),
|
|
227
|
-
}),
|
|
228
|
-
render: ({ width, height, radius, background, children }) => {
|
|
229
|
-
const style: CSSProperties = {};
|
|
230
|
-
if (width !== undefined) style.width = toCssLength(width);
|
|
231
|
-
if (height !== undefined) style.height = toCssLength(height);
|
|
232
|
-
if (background !== undefined) style.background = background;
|
|
233
|
-
if (radius === "full") style.borderRadius = "9999px";
|
|
234
|
-
else if (typeof radius === "number") style.borderRadius = `${radius}px`;
|
|
235
|
-
|
|
236
|
-
return (
|
|
237
|
-
<div
|
|
238
|
-
data-aui="box"
|
|
239
|
-
data-aui-radius={radius === "full" ? "full" : undefined}
|
|
240
|
-
style={style}
|
|
241
|
-
>
|
|
242
|
-
{children}
|
|
243
|
-
</div>
|
|
244
|
-
);
|
|
245
|
-
},
|
|
246
|
-
},
|
|
247
|
-
} satisfies GenerativeUILibrary;
|