Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion renderers/lit/src/0.8/ui/multiple-choice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ export class MultipleChoice extends Root {
}

.chip.selected:hover {
background: var(--md-sys-color-secondary-container-high, #e8def8);
background: var(--md-sys-color-secondary-container-high);
}

.chip-icon {
Expand Down
27 changes: 27 additions & 0 deletions renderers/lit/src/0.8/ui/text-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export class TextField extends Root {
@property()
accessor inputType: Types.ResolvedTextField["type"] | null = null;

@property()
accessor validationRegexp: string | null = null;

static styles = [
structuralStyles,
css`
Expand All @@ -52,6 +55,17 @@ export class TextField extends Root {
display: block;
width: 100%;
}

input:invalid {
border-color: var(--color-error);
color: var(--color-error);
outline-color: var(--color-error);
}

input:invalid:focus {
border-color: var(--color-error);
outline-color: var(--color-error);
}

label {
display: block;
Expand Down Expand Up @@ -101,12 +115,25 @@ export class TextField extends Root {
return;
}

this.dispatchEvent(
new CustomEvent("a2ui-validation-input", {
bubbles: true,
composed: true,
detail: {
componentId: this.id,
value: evt.target.value,
valid: evt.target.checkValidity(),
},
})
);

this.#setBoundValue(evt.target.value);
}}
name="data"
id="data"
.value=${value}
.placeholder=${"Please enter a value"}
pattern=${this.validationRegexp || nothing}
type=${this.inputType === "number" ? "number" : "text"}
/>
</section>`;
Expand Down
2 changes: 2 additions & 0 deletions samples/agent/adk/component_gallery/__main__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

"""Main entry point for the Component Gallery agent."""
import logging
import os
Expand All @@ -14,6 +15,7 @@
from starlette.staticfiles import StaticFiles
from dotenv import load_dotenv


from agent_executor import ComponentGalleryExecutor

load_dotenv()
Expand Down
9 changes: 9 additions & 0 deletions samples/agent/adk/component_gallery/gallery_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ def add_demo_surface(surface_id, component_def):
}
})

# 1b. TextField (Regex)
add_demo_surface("demo-text-regex", {
"TextField": {
"label": { "literalString": "Enter exactly 5 digits" },
"text": { "path": "galleryData/textFieldRegex" },
"validationRegexp": "^\\d{5}$"
}
})

# 2. CheckBox
add_demo_surface("demo-checkbox", {
"CheckBox": {
Expand Down
4 changes: 2 additions & 2 deletions samples/client/lit/component_gallery/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { v0_8 } from "@a2ui/lit";

import { registerContactComponents } from "./ui/custom-components/register-components.js";
type A2TextPayload = {
kind: "text";
text: string;
Expand Down Expand Up @@ -72,4 +72,4 @@ export class A2UIClient {
throw new Error(error.error);
}
}

registerContactComponents();
26 changes: 26 additions & 0 deletions samples/client/lit/component_gallery/component-gallery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface DemoItem {

const DEMO_ITEMS: DemoItem[] = [
{ id: "demo-text", title: "TextField", description: "Allows user to enter text. Supports binding to data model.", actionButton: true },
{ id: "demo-text-regex", title: "TextField (Regex)", description: "TextField with 5-digit regex validation.", actionButton: true },
{ id: "demo-checkbox", title: "CheckBox", description: "A binary toggle.", actionButton: true },
{ id: "demo-slider", title: "Slider", description: "Select a value from a range.", actionButton: true },
{ id: "demo-date", title: "DateTimeInput", description: "Pick a date or time.", actionButton: true },
Expand Down Expand Up @@ -185,16 +186,40 @@ export class A2UIComponentGallery extends SignalWatcher(LitElement) {

async connectedCallback() {
super.connectedCallback();
this.addEventListener("a2ui-validation-input", this.#handleValidationInput);
await this.#initiateSession();
}

override disconnectedCallback() {
super.disconnectedCallback();
this.removeEventListener("a2ui-validation-input", this.#handleValidationInput);
}

async #initiateSession() {
const message: v0_8.Types.A2UIClientEventMessage = {
request: "START_GALLERY"
};
await this.#sendAndProcessMessage(message);
}

// Debug Validation Events
#handleValidationInput = (e: Event) => {
const detail = (e as CustomEvent).detail;
Comment on lines +206 to +207
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The type assertion (e as CustomEvent) is unsafe. If an event with the same name but a different type is dispatched, this could lead to runtime errors. It's safer to use a type guard with instanceof to ensure the event is a CustomEvent before accessing its detail property.

  #handleValidationInput = (e: Event) => {
    if (!(e instanceof CustomEvent)) {
      return;
    }
    const detail = e.detail;


// Log to Console
console.log(
`%c[Validation] Component: ${detail.componentId} | Valid: ${detail.valid} | Value: "${detail.value}"`,
detail.valid ? "color: green" : "color: red; font-weight: bold"
);

// Log to Debug Panel
this.#log(
"info",
`Validation: ${detail.componentId} is ${detail.valid ? "VALID" : "INVALID"}`,
detail
);
};

render() {
return html`
<header>
Expand Down Expand Up @@ -260,6 +285,7 @@ export class A2UIComponentGallery extends SignalWatcher(LitElement) {
// Map item IDs to data paths based on knowledge of gallery_examples.py
const pathMap: Record<string, string> = {
"demo-text": "galleryData/textField",
"demo-text-regex": "galleryData/textFieldRegex",
"demo-checkbox": "galleryData/checkbox",
"demo-slider": "galleryData/slider",
"demo-date": "galleryData/date",
Expand Down
3 changes: 3 additions & 0 deletions samples/client/lit/component_gallery/theme/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,9 @@ export const theme: v0_8.Types.Theme = {
display: "flex",
flexDirection: "column",
},
MultipleChoice: {
"--md-sys-color-secondary-container-high": "#e8def8",
},
},
components: {
AudioPlayer: {},
Expand Down
1 change: 1 addition & 0 deletions samples/client/lit/shell/theme/default-theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ export const theme: v0_8.Types.Theme = {
},
TextField: {
"--p-0": "light-dark(var(--n-0), #1e293b)",
"--color-error": "#B3261E",
},
},
components: {
Expand Down
Loading
Loading