a2ui-react 0.2.1 → 0.3.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 +59 -2
- package/dist/index.js +59 -41
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +59 -41
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,12 +20,31 @@ yarn add a2ui-react
|
|
|
20
20
|
|
|
21
21
|
## Setup
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
### 1. Configure Tailwind v4 (Critical)
|
|
24
|
+
|
|
25
|
+
Add the `@source` directive to your CSS so Tailwind scans the component classes:
|
|
26
|
+
|
|
27
|
+
```css
|
|
28
|
+
@import 'tailwindcss';
|
|
29
|
+
@source "../node_modules/a2ui-react/dist";
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Without this, Tailwind v4 won't scan node_modules and components render unstyled.
|
|
33
|
+
|
|
34
|
+
### 2. Import Theme CSS
|
|
35
|
+
|
|
36
|
+
Import the theme CSS in your app entry point:
|
|
24
37
|
|
|
25
38
|
```tsx
|
|
26
39
|
import 'a2ui-react/theme.css'
|
|
27
40
|
```
|
|
28
41
|
|
|
42
|
+
> **Vite users**: If the import doesn't resolve, copy the CSS locally:
|
|
43
|
+
> ```bash
|
|
44
|
+
> cp node_modules/a2ui-react/dist/theme.css src/a2ui-theme.css
|
|
45
|
+
> ```
|
|
46
|
+
> Then import `'./a2ui-theme.css'` instead.
|
|
47
|
+
|
|
29
48
|
Or if using your own Tailwind setup, ensure your CSS includes the [shadcn/ui theme variables](https://ui.shadcn.com/docs/theming).
|
|
30
49
|
|
|
31
50
|
## Quick Start
|
|
@@ -193,7 +212,45 @@ function App() {
|
|
|
193
212
|
## Requirements
|
|
194
213
|
|
|
195
214
|
- React 18.0.0+ or React 19.0.0+
|
|
196
|
-
- Tailwind CSS (for styling)
|
|
215
|
+
- Tailwind CSS v4 (for styling)
|
|
216
|
+
|
|
217
|
+
## Important Notes
|
|
218
|
+
|
|
219
|
+
### Component ID Format
|
|
220
|
+
|
|
221
|
+
The `id` must be present both in the update wrapper AND inside the component object:
|
|
222
|
+
|
|
223
|
+
```tsx
|
|
224
|
+
// ✅ Correct
|
|
225
|
+
{
|
|
226
|
+
id: 'greeting',
|
|
227
|
+
component: {
|
|
228
|
+
type: 'Text',
|
|
229
|
+
id: 'greeting', // Required inside component
|
|
230
|
+
content: 'Hello!'
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ❌ Won't work - missing id inside component
|
|
235
|
+
{
|
|
236
|
+
id: 'greeting',
|
|
237
|
+
component: {
|
|
238
|
+
type: 'Text',
|
|
239
|
+
content: 'Hello!'
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### a2ui-go Compatibility
|
|
245
|
+
|
|
246
|
+
If using [a2ui-go](https://github.com/burka/a2ui-go) as your backend, you'll need to transform messages:
|
|
247
|
+
|
|
248
|
+
| a2ui-go format | a2ui-react format |
|
|
249
|
+
|----------------|-------------------|
|
|
250
|
+
| `surfaceUpdate.components` | `surfaceUpdate.updates` |
|
|
251
|
+
| `{"id":"x", "Text":{...}}` | `{"id":"x", "component":{"type":"Text",...}}` |
|
|
252
|
+
| `text` field | `content` field |
|
|
253
|
+
| `usageHint` field | `style` field |
|
|
197
254
|
|
|
198
255
|
## License
|
|
199
256
|
|
package/dist/index.js
CHANGED
|
@@ -168,6 +168,32 @@ var c = {
|
|
|
168
168
|
...props
|
|
169
169
|
})
|
|
170
170
|
};
|
|
171
|
+
function isV09Format(update) {
|
|
172
|
+
return typeof update.component === "string";
|
|
173
|
+
}
|
|
174
|
+
function normalizeComponentUpdate(update) {
|
|
175
|
+
if (isV09Format(update)) {
|
|
176
|
+
const { id, component: type, ...rest } = update;
|
|
177
|
+
return { id, type, ...rest };
|
|
178
|
+
}
|
|
179
|
+
const component = update.component;
|
|
180
|
+
if (!component.id) {
|
|
181
|
+
component.id = update.id;
|
|
182
|
+
}
|
|
183
|
+
if (component.type === "Text") {
|
|
184
|
+
const textComponent = component;
|
|
185
|
+
if (textComponent.content && !textComponent.text) {
|
|
186
|
+
textComponent.text = textComponent.content;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return component;
|
|
190
|
+
}
|
|
191
|
+
function getTextContent(component) {
|
|
192
|
+
return component.text ?? component.content ?? "";
|
|
193
|
+
}
|
|
194
|
+
function getComponentsArray(msg) {
|
|
195
|
+
return msg.components ?? msg.updates ?? [];
|
|
196
|
+
}
|
|
171
197
|
var MessageParseError = class extends Error {
|
|
172
198
|
constructor(message) {
|
|
173
199
|
super(message);
|
|
@@ -200,6 +226,17 @@ function validateBeginRendering(msg) {
|
|
|
200
226
|
throw new MessageParseError("beginRendering.style must be an object if provided");
|
|
201
227
|
}
|
|
202
228
|
}
|
|
229
|
+
function validateComponentUpdate(update, context) {
|
|
230
|
+
if (!isObject(update)) {
|
|
231
|
+
throw new MessageParseError(`${context}[] must be objects`);
|
|
232
|
+
}
|
|
233
|
+
if (!isNonEmptyString(update.id)) {
|
|
234
|
+
throw new MessageParseError(`${context}[].id must be a non-empty string`);
|
|
235
|
+
}
|
|
236
|
+
if (typeof update.component !== "string" && !isObject(update.component)) {
|
|
237
|
+
throw new MessageParseError(`${context}[].component must be a string (v0.9) or object (legacy)`);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
203
240
|
function validateSurfaceUpdate(msg) {
|
|
204
241
|
if (!isObject(msg)) {
|
|
205
242
|
throw new MessageParseError("surfaceUpdate must be an object");
|
|
@@ -207,19 +244,12 @@ function validateSurfaceUpdate(msg) {
|
|
|
207
244
|
if (!isNonEmptyString(msg.surfaceId)) {
|
|
208
245
|
throw new MessageParseError("surfaceUpdate.surfaceId must be a non-empty string");
|
|
209
246
|
}
|
|
210
|
-
|
|
211
|
-
|
|
247
|
+
const components = msg.updates ?? msg.components;
|
|
248
|
+
if (!isArray(components)) {
|
|
249
|
+
throw new MessageParseError("surfaceUpdate.updates (or .components) must be an array");
|
|
212
250
|
}
|
|
213
|
-
for (const update of
|
|
214
|
-
|
|
215
|
-
throw new MessageParseError("surfaceUpdate.updates[] must be objects");
|
|
216
|
-
}
|
|
217
|
-
if (!isNonEmptyString(update.id)) {
|
|
218
|
-
throw new MessageParseError("surfaceUpdate.updates[].id must be a non-empty string");
|
|
219
|
-
}
|
|
220
|
-
if (!isObject(update.component)) {
|
|
221
|
-
throw new MessageParseError("surfaceUpdate.updates[].component must be an object");
|
|
222
|
-
}
|
|
251
|
+
for (const update of components) {
|
|
252
|
+
validateComponentUpdate(update, "surfaceUpdate.updates");
|
|
223
253
|
}
|
|
224
254
|
}
|
|
225
255
|
function validateDataModelUpdate(msg) {
|
|
@@ -283,15 +313,7 @@ function validateUpdateComponents(msg) {
|
|
|
283
313
|
throw new MessageParseError("updateComponents.components must be an array");
|
|
284
314
|
}
|
|
285
315
|
for (const component of msg.components) {
|
|
286
|
-
|
|
287
|
-
throw new MessageParseError("updateComponents.components[] must be objects");
|
|
288
|
-
}
|
|
289
|
-
if (!isNonEmptyString(component.id)) {
|
|
290
|
-
throw new MessageParseError("updateComponents.components[].id must be a non-empty string");
|
|
291
|
-
}
|
|
292
|
-
if (!isObject(component.component)) {
|
|
293
|
-
throw new MessageParseError("updateComponents.components[].component must be an object");
|
|
294
|
-
}
|
|
316
|
+
validateComponentUpdate(component, "updateComponents.components");
|
|
295
317
|
}
|
|
296
318
|
}
|
|
297
319
|
function validateUpdateDataModel(msg) {
|
|
@@ -622,15 +644,9 @@ function processMessage(message, store) {
|
|
|
622
644
|
data: {}
|
|
623
645
|
});
|
|
624
646
|
} else if (isUpdateComponentsMessage(message) || isSurfaceUpdateMessage(message)) {
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
surfaceId = message.updateComponents.surfaceId;
|
|
629
|
-
componentUpdates = message.updateComponents.components;
|
|
630
|
-
} else {
|
|
631
|
-
surfaceId = message.surfaceUpdate.surfaceId;
|
|
632
|
-
componentUpdates = message.surfaceUpdate.updates;
|
|
633
|
-
}
|
|
647
|
+
const payload = isUpdateComponentsMessage(message) ? message.updateComponents : message.surfaceUpdate;
|
|
648
|
+
const surfaceId = payload.surfaceId;
|
|
649
|
+
const componentUpdates = getComponentsArray(payload);
|
|
634
650
|
const surface = store.getSurface(surfaceId);
|
|
635
651
|
if (!surface) {
|
|
636
652
|
console.warn(`Surface not found for update: ${surfaceId}`);
|
|
@@ -638,7 +654,8 @@ function processMessage(message, store) {
|
|
|
638
654
|
}
|
|
639
655
|
const updatedComponents = { ...surface.components };
|
|
640
656
|
for (const update of componentUpdates) {
|
|
641
|
-
|
|
657
|
+
const component = normalizeComponentUpdate(update);
|
|
658
|
+
updatedComponents[update.id] = component;
|
|
642
659
|
}
|
|
643
660
|
surface.components = updatedComponents;
|
|
644
661
|
store.setSurface(surfaceId, { ...surface });
|
|
@@ -6054,7 +6071,7 @@ var AnimatedTextOverride = {
|
|
|
6054
6071
|
type: "Text",
|
|
6055
6072
|
render: ({ component, data }) => {
|
|
6056
6073
|
const className = styleClasses6[component.style || "body"] || styleClasses6.body;
|
|
6057
|
-
let content = component
|
|
6074
|
+
let content = getTextContent(component);
|
|
6058
6075
|
if (component.dataPath) {
|
|
6059
6076
|
const value = data.get(component.dataPath);
|
|
6060
6077
|
if (value !== void 0) {
|
|
@@ -7541,8 +7558,9 @@ var ImageRenderer = {
|
|
|
7541
7558
|
var TextRenderer = {
|
|
7542
7559
|
type: "Text",
|
|
7543
7560
|
render: ({ component, data }) => {
|
|
7544
|
-
const {
|
|
7545
|
-
const
|
|
7561
|
+
const { style = "body", dataPath } = component;
|
|
7562
|
+
const textContent = getTextContent(component);
|
|
7563
|
+
const displayContent = dataPath ? data.get(dataPath) ?? textContent : textContent;
|
|
7546
7564
|
const styleMap = {
|
|
7547
7565
|
h1: { tag: "h1", className: "text-4xl font-bold" },
|
|
7548
7566
|
h2: { tag: "h2", className: "text-3xl font-semibold" },
|
|
@@ -7586,7 +7604,7 @@ var TextRenderer = {
|
|
|
7586
7604
|
component: {
|
|
7587
7605
|
type: "Text",
|
|
7588
7606
|
id: "h1",
|
|
7589
|
-
|
|
7607
|
+
text: "Heading 1 - Large Title",
|
|
7590
7608
|
style: "h1"
|
|
7591
7609
|
}
|
|
7592
7610
|
},
|
|
@@ -7595,7 +7613,7 @@ var TextRenderer = {
|
|
|
7595
7613
|
component: {
|
|
7596
7614
|
type: "Text",
|
|
7597
7615
|
id: "h2",
|
|
7598
|
-
|
|
7616
|
+
text: "Heading 2 - Section Title",
|
|
7599
7617
|
style: "h2"
|
|
7600
7618
|
}
|
|
7601
7619
|
},
|
|
@@ -7604,7 +7622,7 @@ var TextRenderer = {
|
|
|
7604
7622
|
component: {
|
|
7605
7623
|
type: "Text",
|
|
7606
7624
|
id: "h3",
|
|
7607
|
-
|
|
7625
|
+
text: "Heading 3 - Subsection",
|
|
7608
7626
|
style: "h3"
|
|
7609
7627
|
}
|
|
7610
7628
|
},
|
|
@@ -7613,7 +7631,7 @@ var TextRenderer = {
|
|
|
7613
7631
|
component: {
|
|
7614
7632
|
type: "Text",
|
|
7615
7633
|
id: "h4",
|
|
7616
|
-
|
|
7634
|
+
text: "Heading 4 - Component Title",
|
|
7617
7635
|
style: "h4"
|
|
7618
7636
|
}
|
|
7619
7637
|
},
|
|
@@ -7622,7 +7640,7 @@ var TextRenderer = {
|
|
|
7622
7640
|
component: {
|
|
7623
7641
|
type: "Text",
|
|
7624
7642
|
id: "h5",
|
|
7625
|
-
|
|
7643
|
+
text: "Heading 5 - Small Title",
|
|
7626
7644
|
style: "h5"
|
|
7627
7645
|
}
|
|
7628
7646
|
},
|
|
@@ -7631,7 +7649,7 @@ var TextRenderer = {
|
|
|
7631
7649
|
component: {
|
|
7632
7650
|
type: "Text",
|
|
7633
7651
|
id: "body",
|
|
7634
|
-
|
|
7652
|
+
text: "Body text paragraph with regular content and default styling.",
|
|
7635
7653
|
style: "body"
|
|
7636
7654
|
}
|
|
7637
7655
|
},
|
|
@@ -7640,7 +7658,7 @@ var TextRenderer = {
|
|
|
7640
7658
|
component: {
|
|
7641
7659
|
type: "Text",
|
|
7642
7660
|
id: "caption",
|
|
7643
|
-
|
|
7661
|
+
text: "Caption text - muted and small",
|
|
7644
7662
|
style: "caption"
|
|
7645
7663
|
}
|
|
7646
7664
|
}
|