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 CHANGED
@@ -20,12 +20,31 @@ yarn add a2ui-react
20
20
 
21
21
  ## Setup
22
22
 
23
- Import the default theme CSS in your app entry point:
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
- if (!isArray(msg.updates)) {
211
- throw new MessageParseError("surfaceUpdate.updates must be an array");
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 msg.updates) {
214
- if (!isObject(update)) {
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
- if (!isObject(component)) {
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
- let surfaceId;
626
- let componentUpdates;
627
- if (isUpdateComponentsMessage(message)) {
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
- updatedComponents[update.id] = update.component;
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.content;
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 { content, style = "body", dataPath } = component;
7545
- const displayContent = dataPath ? data.get(dataPath) ?? content : content;
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
- content: "Heading 1 - Large Title",
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
- content: "Heading 2 - Section Title",
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
- content: "Heading 3 - Subsection",
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
- content: "Heading 4 - Component Title",
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
- content: "Heading 5 - Small Title",
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
- content: "Body text paragraph with regular content and default styling.",
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
- content: "Caption text - muted and small",
7661
+ text: "Caption text - muted and small",
7644
7662
  style: "caption"
7645
7663
  }
7646
7664
  }