@assistant-ui/react-generative-ui 0.0.24 → 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 +4 -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 -153
- 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 -584
- 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 -337
- package/src/temporal.ts +0 -263
- 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 -1457
- 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 -1109
- 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
package/src/types.ts
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
import type { ReactNode } from "react";
|
|
2
|
-
import type { ZodType } from "zod";
|
|
3
|
-
import type { Action, NormalizedUIElement } from "./ir";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* The canonical generative-ui element, normalized from the flat `$type` shape
|
|
7
|
-
* or the legacy `component` shape (see {@link normalizeUINode}). `children` is
|
|
8
|
-
* a reserved top-level key, not a prop, so it lives here rather than inside
|
|
9
|
-
* `props`. Alias of {@link NormalizedUIElement}; kept under its old name so the
|
|
10
|
-
* package's public surface stays append-only.
|
|
11
|
-
*/
|
|
12
|
-
export type GenerativeUIElement = NormalizedUIElement;
|
|
13
|
-
|
|
14
|
-
export type GenerativeUIProps = NormalizedUIElement["props"];
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Anything renderable as generative UI — mirrors React's `ReactNode`: an
|
|
18
|
-
* element, primitive text, a list of nodes, or nothing. Widened beyond the
|
|
19
|
-
* model payload (a {@link GenerativeUIElement}) with the renderer's
|
|
20
|
-
* null/boolean/undefined inputs.
|
|
21
|
-
*/
|
|
22
|
-
export type GenerativeUINode =
|
|
23
|
-
| GenerativeUIElement
|
|
24
|
-
| string
|
|
25
|
-
| number
|
|
26
|
-
| boolean
|
|
27
|
-
| null
|
|
28
|
-
| undefined
|
|
29
|
-
| GenerativeUINode[];
|
|
30
|
-
|
|
31
|
-
export type GenerativeUIAction = Action;
|
|
32
|
-
|
|
33
|
-
/** Whether a node's props are still streaming in or have fully arrived. */
|
|
34
|
-
export type GenerativeUIStatus = "streaming" | "done";
|
|
35
|
-
|
|
36
|
-
/** The dispatcher an interactive component calls to fire its `$action`. */
|
|
37
|
-
export type GenerativeUIDispatch = (action: Action) => unknown;
|
|
38
|
-
|
|
39
|
-
/** The render context threaded through {@link renderGenerativeUI}. */
|
|
40
|
-
export type GenerativeUIRenderContext = {
|
|
41
|
-
/** Whether the tool call's arguments are still streaming or are complete. */
|
|
42
|
-
status: GenerativeUIStatus;
|
|
43
|
-
dispatch?: GenerativeUIDispatch;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Props a component's `render` receives: its model props, rendered `children`,
|
|
48
|
-
* and the injected framework keys. `$status` is the discriminant — when it is
|
|
49
|
-
* `"done"`, `P` is complete; while `"streaming"`, `P` is partial. `$action` is
|
|
50
|
-
* the node's behavior payload, present only on interactive nodes. `$dispatch`
|
|
51
|
-
* fires that payload through the host's {@link ActionRegistry}; it is present
|
|
52
|
-
* only when a registry was wired. All three are `$`-prefixed so they never
|
|
53
|
-
* collide with a real `status`/`action`/`dispatch` prop.
|
|
54
|
-
*/
|
|
55
|
-
type StreamingRenderProps<P> =
|
|
56
|
-
| (Partial<P> & {
|
|
57
|
-
children?: ReactNode;
|
|
58
|
-
$status: "streaming";
|
|
59
|
-
$action?: Action;
|
|
60
|
-
$dispatch?: GenerativeUIDispatch;
|
|
61
|
-
})
|
|
62
|
-
| (P & {
|
|
63
|
-
children?: ReactNode;
|
|
64
|
-
$status: "done";
|
|
65
|
-
$action?: Action;
|
|
66
|
-
$dispatch?: GenerativeUIDispatch;
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
/** Props for a component that only renders once complete — `$status` is always `"done"`. */
|
|
70
|
-
type StaticRenderProps<P> = P & {
|
|
71
|
-
children?: ReactNode;
|
|
72
|
-
$status: "done";
|
|
73
|
-
$action?: Action;
|
|
74
|
-
$dispatch?: GenerativeUIDispatch;
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* A component the model is allowed to render, with the schema for its props.
|
|
79
|
-
*
|
|
80
|
-
* Components opt into prop streaming with `streamProperties`: they render as
|
|
81
|
-
* props arrive, so `render` sees `Partial<P>` while `$status` is `"streaming"`
|
|
82
|
-
* and the full `P` once it is `"done"`. By default a component opts out and is
|
|
83
|
-
* only rendered once its props are complete.
|
|
84
|
-
*
|
|
85
|
-
* `render` is a function of `props`, not a React component. Direct hook use is
|
|
86
|
-
* fine (each node is mounted on its own fiber), but a given node must keep its
|
|
87
|
-
* `type` across renders for hook identity to be stable.
|
|
88
|
-
*/
|
|
89
|
-
export type GenerativeUIComponent<P = any> =
|
|
90
|
-
| {
|
|
91
|
-
/** Natural-language description shown to the model when selecting the component. */
|
|
92
|
-
description: string;
|
|
93
|
-
/** Schema for the props the model must provide. Drives the tool parameters. */
|
|
94
|
-
properties: ZodType<P>;
|
|
95
|
-
/**
|
|
96
|
-
* Render from partially-streamed props. Widened to `boolean | undefined`
|
|
97
|
-
* so a non-literal value (e.g. a variable) still resolves to this branch
|
|
98
|
-
* cleanly rather than matching neither; the strict `false | undefined`
|
|
99
|
-
* branch below is the only one that promises complete props.
|
|
100
|
-
*/
|
|
101
|
-
streamProperties: boolean | undefined;
|
|
102
|
-
render: (props: StreamingRenderProps<P>) => ReactNode;
|
|
103
|
-
}
|
|
104
|
-
| {
|
|
105
|
-
description: string;
|
|
106
|
-
properties: ZodType<P>;
|
|
107
|
-
/** Render only once props are complete (the default). */
|
|
108
|
-
streamProperties?: false | undefined;
|
|
109
|
-
render: (props: StaticRenderProps<P>) => ReactNode;
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* The consumer-provided allowlist of components the model is permitted to
|
|
114
|
-
* render. Keys are the `type` values referenced in the generative-ui tree
|
|
115
|
-
* (e.g. `"Card"`, `"Button"`); values describe each component.
|
|
116
|
-
*
|
|
117
|
-
* This registry is the security boundary — any `type` not present is rejected.
|
|
118
|
-
*/
|
|
119
|
-
export type GenerativeUILibrary = Record<string, GenerativeUIComponent>;
|
|
@@ -1,427 +0,0 @@
|
|
|
1
|
-
// @vitest-environment jsdom
|
|
2
|
-
|
|
3
|
-
import { act } from "react";
|
|
4
|
-
import { createRoot, type Root } from "react-dom/client";
|
|
5
|
-
import { renderToStaticMarkup } from "react-dom/server";
|
|
6
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
7
|
-
import { renderGenerativeUI } from "../renderGenerativeUI";
|
|
8
|
-
import { alertVocabulary } from "./alert";
|
|
9
|
-
import { defaultGenerativeUILibrary } from "./index";
|
|
10
|
-
|
|
11
|
-
const render = (node: unknown) =>
|
|
12
|
-
renderToStaticMarkup(<>{renderGenerativeUI(node, alertVocabulary)}</>);
|
|
13
|
-
|
|
14
|
-
(
|
|
15
|
-
globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }
|
|
16
|
-
).IS_REACT_ACT_ENVIRONMENT = true;
|
|
17
|
-
|
|
18
|
-
let root: Root | undefined;
|
|
19
|
-
|
|
20
|
-
afterEach(async () => {
|
|
21
|
-
await act(async () => root?.unmount());
|
|
22
|
-
root = undefined;
|
|
23
|
-
document.body.innerHTML = "";
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
const carousel = {
|
|
27
|
-
$type: "Carousel",
|
|
28
|
-
children: [
|
|
29
|
-
{ $type: "Card", title: "One" },
|
|
30
|
-
{ $type: "Card", title: "Two" },
|
|
31
|
-
{ $type: "Card", title: "Three" },
|
|
32
|
-
],
|
|
33
|
-
};
|
|
34
|
-
|
|
35
|
-
const mountCarousel = async (node: unknown = carousel) => {
|
|
36
|
-
const container = document.createElement("div");
|
|
37
|
-
document.body.append(container);
|
|
38
|
-
root = createRoot(container);
|
|
39
|
-
await act(async () => {
|
|
40
|
-
root!.render(renderGenerativeUI(node, defaultGenerativeUILibrary));
|
|
41
|
-
});
|
|
42
|
-
const track = container.querySelector<HTMLDivElement>(
|
|
43
|
-
'[data-aui="carousel"]',
|
|
44
|
-
);
|
|
45
|
-
if (!track) throw new Error("Carousel track was not rendered.");
|
|
46
|
-
return { container, track };
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const setCarouselLayout = (
|
|
50
|
-
track: HTMLDivElement,
|
|
51
|
-
{
|
|
52
|
-
scrollWidth,
|
|
53
|
-
clientWidth,
|
|
54
|
-
scrollLeft,
|
|
55
|
-
}: { scrollWidth: number; clientWidth: number; scrollLeft: number },
|
|
56
|
-
) => {
|
|
57
|
-
const rtl = track.style.direction === "rtl";
|
|
58
|
-
const rect = (left: number, width: number) =>
|
|
59
|
-
({
|
|
60
|
-
left,
|
|
61
|
-
right: left + width,
|
|
62
|
-
width,
|
|
63
|
-
top: 0,
|
|
64
|
-
bottom: 100,
|
|
65
|
-
height: 100,
|
|
66
|
-
x: left,
|
|
67
|
-
y: 0,
|
|
68
|
-
toJSON: () => ({}),
|
|
69
|
-
}) as DOMRect;
|
|
70
|
-
const trackLeft = 140;
|
|
71
|
-
Object.defineProperties(track, {
|
|
72
|
-
scrollWidth: { configurable: true, value: scrollWidth },
|
|
73
|
-
clientWidth: { configurable: true, value: clientWidth },
|
|
74
|
-
scrollLeft: { configurable: true, value: scrollLeft, writable: true },
|
|
75
|
-
getBoundingClientRect: {
|
|
76
|
-
configurable: true,
|
|
77
|
-
value: () => rect(trackLeft, clientWidth),
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
track
|
|
81
|
-
.querySelectorAll<HTMLElement>(':scope > [data-aui="carousel-slide"]')
|
|
82
|
-
.forEach((slide, index) => {
|
|
83
|
-
Object.defineProperty(slide, "getBoundingClientRect", {
|
|
84
|
-
configurable: true,
|
|
85
|
-
value: () => {
|
|
86
|
-
const offset = index * 200 - Math.abs(track.scrollLeft);
|
|
87
|
-
return rtl
|
|
88
|
-
? rect(trackLeft + clientWidth - offset - 188, 188)
|
|
89
|
-
: rect(trackLeft + offset, 188);
|
|
90
|
-
},
|
|
91
|
-
});
|
|
92
|
-
});
|
|
93
|
-
const scrollBy = vi.fn();
|
|
94
|
-
Object.defineProperty(track, "scrollBy", {
|
|
95
|
-
configurable: true,
|
|
96
|
-
value: scrollBy,
|
|
97
|
-
});
|
|
98
|
-
return scrollBy;
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
const measure = async () => {
|
|
102
|
-
await act(async () => {
|
|
103
|
-
window.dispatchEvent(new Event("resize"));
|
|
104
|
-
});
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
describe("alertVocabulary", () => {
|
|
108
|
-
it("Alert renders with title/description/tone, defaulting tone to info", () => {
|
|
109
|
-
expect(
|
|
110
|
-
render({
|
|
111
|
-
$type: "Alert",
|
|
112
|
-
title: "Heads up",
|
|
113
|
-
description: "Something happened",
|
|
114
|
-
}),
|
|
115
|
-
).toBe(
|
|
116
|
-
'<div data-aui="alert" data-aui-tone="info" role="alert"><header data-aui="alert-title">Heads up</header><p data-aui="alert-desc">Something happened</p></div>',
|
|
117
|
-
);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
it("Alert renders an explicit tone", () => {
|
|
121
|
-
expect(
|
|
122
|
-
render({ $type: "Alert", tone: "danger", title: "Error" }),
|
|
123
|
-
).toContain('data-aui-tone="danger"');
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it("Alert ignores malformed text properties", () => {
|
|
127
|
-
expect(
|
|
128
|
-
render({
|
|
129
|
-
$type: "Alert",
|
|
130
|
-
title: { unexpected: true },
|
|
131
|
-
description: { unexpected: true },
|
|
132
|
-
}),
|
|
133
|
-
).toBe('<div data-aui="alert" data-aui-tone="info" role="alert"></div>');
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("Carousel wraps each child Card in a slide, capped at 10", () => {
|
|
137
|
-
const children = Array.from({ length: 12 }, (_, i) => ({
|
|
138
|
-
$type: "Card",
|
|
139
|
-
title: `c${i}`,
|
|
140
|
-
}));
|
|
141
|
-
const html = renderToStaticMarkup(
|
|
142
|
-
<>
|
|
143
|
-
{renderGenerativeUI(
|
|
144
|
-
{ $type: "Carousel", children },
|
|
145
|
-
defaultGenerativeUILibrary,
|
|
146
|
-
)}
|
|
147
|
-
</>,
|
|
148
|
-
);
|
|
149
|
-
const slideCount = (html.match(/data-aui="carousel-slide"/g) ?? []).length;
|
|
150
|
-
expect(slideCount).toBe(10);
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it("Carousel renders a single non-array child Card in one slide", () => {
|
|
154
|
-
const html = renderToStaticMarkup(
|
|
155
|
-
<>
|
|
156
|
-
{renderGenerativeUI(
|
|
157
|
-
{
|
|
158
|
-
$type: "Carousel",
|
|
159
|
-
children: { $type: "Card", title: "only" } as never,
|
|
160
|
-
},
|
|
161
|
-
defaultGenerativeUILibrary,
|
|
162
|
-
)}
|
|
163
|
-
</>,
|
|
164
|
-
);
|
|
165
|
-
expect((html.match(/data-aui="carousel-slide"/g) ?? []).length).toBe(1);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it("Carousel without a label renders role=group, no aria-label, and no double-name landmark", () => {
|
|
169
|
-
const html = render({ $type: "Carousel" });
|
|
170
|
-
expect(html).toContain('data-aui="carousel"');
|
|
171
|
-
expect(html).toContain('role="group"');
|
|
172
|
-
expect(html).toContain('aria-roledescription="carousel"');
|
|
173
|
-
expect(html).toContain('tabindex="0"');
|
|
174
|
-
expect(html).not.toContain("aria-label");
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("Carousel with a label renders role=region and the given aria-label", () => {
|
|
178
|
-
const html = render({ $type: "Carousel", label: "Featured" });
|
|
179
|
-
expect(html).toContain('role="region"');
|
|
180
|
-
expect(html).toContain('aria-label="Featured"');
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
it("Carousel container carries region semantics and an optional label", () => {
|
|
184
|
-
const html = renderToStaticMarkup(
|
|
185
|
-
<>
|
|
186
|
-
{renderGenerativeUI(
|
|
187
|
-
{
|
|
188
|
-
$type: "Carousel",
|
|
189
|
-
label: "Featured items",
|
|
190
|
-
children: [
|
|
191
|
-
{ $type: "Card", title: "a" },
|
|
192
|
-
{ $type: "Card", title: "b" },
|
|
193
|
-
],
|
|
194
|
-
},
|
|
195
|
-
defaultGenerativeUILibrary,
|
|
196
|
-
)}
|
|
197
|
-
</>,
|
|
198
|
-
);
|
|
199
|
-
expect(html).toContain('role="region"');
|
|
200
|
-
expect(html).toContain('aria-roledescription="carousel"');
|
|
201
|
-
expect(html).toContain('aria-label="Featured items"');
|
|
202
|
-
expect(html).toContain('tabindex="0"');
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
it("Carousel slides carry group semantics and a positional label", () => {
|
|
206
|
-
const html = renderToStaticMarkup(
|
|
207
|
-
<>
|
|
208
|
-
{renderGenerativeUI(
|
|
209
|
-
{
|
|
210
|
-
$type: "Carousel",
|
|
211
|
-
label: "Featured items",
|
|
212
|
-
children: [
|
|
213
|
-
{ $type: "Card", title: "a" },
|
|
214
|
-
{ $type: "Card", title: "b" },
|
|
215
|
-
{ $type: "Card", title: "c" },
|
|
216
|
-
],
|
|
217
|
-
},
|
|
218
|
-
defaultGenerativeUILibrary,
|
|
219
|
-
)}
|
|
220
|
-
</>,
|
|
221
|
-
);
|
|
222
|
-
expect((html.match(/role="group"/g) ?? []).length).toBe(3);
|
|
223
|
-
expect((html.match(/aria-roledescription="slide"/g) ?? []).length).toBe(3);
|
|
224
|
-
expect(html).toContain('aria-label="1 of 3"');
|
|
225
|
-
expect(html).toContain('aria-label="2 of 3"');
|
|
226
|
-
expect(html).toContain('aria-label="3 of 3"');
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
it("Carousel slide labels reflect the 10-slide cap, not the raw child count", () => {
|
|
230
|
-
const children = Array.from({ length: 12 }, (_, i) => ({
|
|
231
|
-
$type: "Card",
|
|
232
|
-
title: `c${i}`,
|
|
233
|
-
}));
|
|
234
|
-
const html = renderToStaticMarkup(
|
|
235
|
-
<>
|
|
236
|
-
{renderGenerativeUI(
|
|
237
|
-
{ $type: "Carousel", children },
|
|
238
|
-
defaultGenerativeUILibrary,
|
|
239
|
-
)}
|
|
240
|
-
</>,
|
|
241
|
-
);
|
|
242
|
-
expect(html).toContain('aria-label="1 of 10"');
|
|
243
|
-
expect(html).toContain('aria-label="10 of 10"');
|
|
244
|
-
expect(html).not.toContain("11 of 10");
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
it("Carousel server markup keeps the track inside an always-present frame", () => {
|
|
248
|
-
const html = renderToStaticMarkup(
|
|
249
|
-
<>{renderGenerativeUI(carousel, defaultGenerativeUILibrary)}</>,
|
|
250
|
-
);
|
|
251
|
-
expect(html).toMatch(
|
|
252
|
-
/<div data-aui="carousel-frame"><div id="[^"]+" data-aui="carousel"/,
|
|
253
|
-
);
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
it("Carousel does not render controls when its slides fit", async () => {
|
|
257
|
-
const { container, track } = await mountCarousel();
|
|
258
|
-
setCarouselLayout(track, {
|
|
259
|
-
scrollWidth: 300,
|
|
260
|
-
clientWidth: 300,
|
|
261
|
-
scrollLeft: 0,
|
|
262
|
-
});
|
|
263
|
-
await measure();
|
|
264
|
-
expect(
|
|
265
|
-
container.querySelector('[data-aui="carousel-controls"]'),
|
|
266
|
-
).toBeNull();
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it("Carousel renders overflow controls with Previous disabled at the start", async () => {
|
|
270
|
-
const { container, track } = await mountCarousel();
|
|
271
|
-
setCarouselLayout(track, {
|
|
272
|
-
scrollWidth: 600,
|
|
273
|
-
clientWidth: 300,
|
|
274
|
-
scrollLeft: 0,
|
|
275
|
-
});
|
|
276
|
-
await measure();
|
|
277
|
-
|
|
278
|
-
const previous = container.querySelector<HTMLButtonElement>(
|
|
279
|
-
'[data-aui="carousel-prev"]',
|
|
280
|
-
);
|
|
281
|
-
const next = container.querySelector<HTMLButtonElement>(
|
|
282
|
-
'[data-aui="carousel-next"]',
|
|
283
|
-
);
|
|
284
|
-
expect(previous).not.toBeNull();
|
|
285
|
-
expect(next).not.toBeNull();
|
|
286
|
-
expect(previous!.disabled).toBe(true);
|
|
287
|
-
expect(next!.disabled).toBe(false);
|
|
288
|
-
expect(previous!.getAttribute("aria-controls")).toBe(track.id);
|
|
289
|
-
expect(next!.getAttribute("aria-controls")).toBe(track.id);
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it("Carousel remeasures streamed slides and observes each slide", async () => {
|
|
293
|
-
const originalResizeObserver = globalThis.ResizeObserver;
|
|
294
|
-
class TestResizeObserver {
|
|
295
|
-
static instances: TestResizeObserver[] = [];
|
|
296
|
-
readonly observed = new Set<Element>();
|
|
297
|
-
|
|
298
|
-
constructor() {
|
|
299
|
-
TestResizeObserver.instances.push(this);
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
observe(element: Element) {
|
|
303
|
-
this.observed.add(element);
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
disconnect() {}
|
|
307
|
-
}
|
|
308
|
-
globalThis.ResizeObserver =
|
|
309
|
-
TestResizeObserver as unknown as typeof ResizeObserver;
|
|
310
|
-
|
|
311
|
-
try {
|
|
312
|
-
const { container, track } = await mountCarousel({
|
|
313
|
-
$type: "Carousel",
|
|
314
|
-
children: [{ $type: "Card", title: "One" }],
|
|
315
|
-
});
|
|
316
|
-
setCarouselLayout(track, {
|
|
317
|
-
scrollWidth: 600,
|
|
318
|
-
clientWidth: 300,
|
|
319
|
-
scrollLeft: 0,
|
|
320
|
-
});
|
|
321
|
-
await act(async () => {
|
|
322
|
-
root!.render(
|
|
323
|
-
renderGenerativeUI(
|
|
324
|
-
{
|
|
325
|
-
$type: "Carousel",
|
|
326
|
-
children: [
|
|
327
|
-
{ $type: "Card", title: "One" },
|
|
328
|
-
{ $type: "Card", title: "Two" },
|
|
329
|
-
{ $type: "Card", title: "Three" },
|
|
330
|
-
],
|
|
331
|
-
},
|
|
332
|
-
defaultGenerativeUILibrary,
|
|
333
|
-
),
|
|
334
|
-
);
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
expect(
|
|
338
|
-
container.querySelector('[data-aui="carousel-controls"]'),
|
|
339
|
-
).not.toBeNull();
|
|
340
|
-
const observer = TestResizeObserver.instances.at(-1);
|
|
341
|
-
expect([...observer!.observed]).toEqual([
|
|
342
|
-
track,
|
|
343
|
-
...track.querySelectorAll('[data-aui="carousel-slide"]'),
|
|
344
|
-
]);
|
|
345
|
-
} finally {
|
|
346
|
-
globalThis.ResizeObserver = originalResizeObserver;
|
|
347
|
-
}
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
it("Carousel moves to the next slide start", async () => {
|
|
351
|
-
const { container, track } = await mountCarousel();
|
|
352
|
-
const scrollBy = setCarouselLayout(track, {
|
|
353
|
-
scrollWidth: 600,
|
|
354
|
-
clientWidth: 300,
|
|
355
|
-
scrollLeft: 0,
|
|
356
|
-
});
|
|
357
|
-
await measure();
|
|
358
|
-
|
|
359
|
-
const next = container.querySelector<HTMLButtonElement>(
|
|
360
|
-
'[data-aui="carousel-next"]',
|
|
361
|
-
);
|
|
362
|
-
await act(async () => next!.click());
|
|
363
|
-
expect(scrollBy).toHaveBeenCalledWith({ left: 200 });
|
|
364
|
-
});
|
|
365
|
-
|
|
366
|
-
it("Carousel moves back one slide from the middle", async () => {
|
|
367
|
-
const { container, track } = await mountCarousel();
|
|
368
|
-
const scrollBy = setCarouselLayout(track, {
|
|
369
|
-
scrollWidth: 600,
|
|
370
|
-
clientWidth: 300,
|
|
371
|
-
scrollLeft: 200,
|
|
372
|
-
});
|
|
373
|
-
await measure();
|
|
374
|
-
|
|
375
|
-
const previous = container.querySelector<HTMLButtonElement>(
|
|
376
|
-
'[data-aui="carousel-prev"]',
|
|
377
|
-
);
|
|
378
|
-
expect(previous!.disabled).toBe(false);
|
|
379
|
-
await act(async () => previous!.click());
|
|
380
|
-
expect(scrollBy).toHaveBeenCalledWith({ left: -200 });
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
it("Carousel pages toward the inline end in right-to-left layouts", async () => {
|
|
384
|
-
const { container, track } = await mountCarousel();
|
|
385
|
-
track.style.direction = "rtl";
|
|
386
|
-
const scrollBy = setCarouselLayout(track, {
|
|
387
|
-
scrollWidth: 600,
|
|
388
|
-
clientWidth: 300,
|
|
389
|
-
scrollLeft: 0,
|
|
390
|
-
});
|
|
391
|
-
await measure();
|
|
392
|
-
|
|
393
|
-
const next = container.querySelector<HTMLButtonElement>(
|
|
394
|
-
'[data-aui="carousel-next"]',
|
|
395
|
-
);
|
|
396
|
-
await act(async () => next!.click());
|
|
397
|
-
expect(scrollBy).toHaveBeenCalledWith({ left: -200 });
|
|
398
|
-
|
|
399
|
-
track.scrollLeft = -300;
|
|
400
|
-
await act(async () => {
|
|
401
|
-
track.dispatchEvent(new Event("scroll"));
|
|
402
|
-
});
|
|
403
|
-
expect(next!.disabled).toBe(true);
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
it("Carousel disables Next at the scroll end", async () => {
|
|
407
|
-
const { container, track } = await mountCarousel();
|
|
408
|
-
setCarouselLayout(track, {
|
|
409
|
-
scrollWidth: 600,
|
|
410
|
-
clientWidth: 300,
|
|
411
|
-
scrollLeft: 300,
|
|
412
|
-
});
|
|
413
|
-
await measure();
|
|
414
|
-
|
|
415
|
-
const next = container.querySelector<HTMLButtonElement>(
|
|
416
|
-
'[data-aui="carousel-next"]',
|
|
417
|
-
);
|
|
418
|
-
expect(next!.disabled).toBe(true);
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
it("Carousel server markup does not contain controls", () => {
|
|
422
|
-
const html = renderToStaticMarkup(
|
|
423
|
-
<>{renderGenerativeUI(carousel, defaultGenerativeUILibrary)}</>,
|
|
424
|
-
);
|
|
425
|
-
expect(html).not.toContain('data-aui="carousel-controls"');
|
|
426
|
-
});
|
|
427
|
-
});
|