diff --git a/vendor/a2ui/renderers/lit/src/0.8/types/types.ts b/vendor/a2ui/renderers/lit/src/0.8/types/types.ts index 1e1f6686cd..5deb7a6e9d 100644 --- a/vendor/a2ui/renderers/lit/src/0.8/types/types.ts +++ b/vendor/a2ui/renderers/lit/src/0.8/types/types.ts @@ -1,25 +1,20 @@ /* Copyright 2025 Google LLC - Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - https://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - export { type ClientToServerMessage as A2UIClientEventMessage, type ClientCapabilitiesDynamic, } from "./client-event.js"; export { type Action } from "./components.js"; - import { AudioPlayer, Button, @@ -35,12 +30,10 @@ import { Video, } from "./components"; import { StringValue } from "./primitives"; - export type MessageProcessor = { getSurfaces(): ReadonlyMap; clearSurfaces(): void; processMessages(messages: ServerToClientMessage[]): void; - /** * Retrieves the data for a given component node and a relative path string. * This correctly handles the special `.` path, which refers to the node's @@ -51,17 +44,14 @@ export type MessageProcessor = { relativePath: string, surfaceId: string ): DataValue | null; - setData( node: AnyComponentNode | null, relativePath: string, value: DataValue, surfaceId: string ): void; - resolvePath(path: string, dataContextPath?: string): string; }; - export type Theme = { components: { AudioPlayer: Record; @@ -193,7 +183,6 @@ export type Theme = { Video?: Record; }; }; - /** * Represents a user-initiated action, sent from the client to the server. */ @@ -219,7 +208,6 @@ export interface UserAction { [k: string]: unknown; }; } - /** A recursive type for any valid JSON-like value in the data model. */ export type DataValue = | string @@ -232,19 +220,16 @@ export type DataValue = export type DataObject = { [key: string]: DataValue }; export type DataMap = Map; export type DataArray = DataValue[]; - /** A template for creating components from a list in the data model. */ export interface ComponentArrayTemplate { componentId: string; dataBinding: string; } - /** Defines a list of child components, either explicitly or via a template. */ export interface ComponentArrayReference { explicitList?: string[]; template?: ComponentArrayTemplate; } - /** Represents the general shape of a component's properties. */ export type ComponentProperties = { // Allow any property, but define known structural ones for type safety. @@ -252,31 +237,26 @@ export type ComponentProperties = { child?: string; [k: string]: unknown; }; - /** A raw component instance from a SurfaceUpdate message. */ export interface ComponentInstance { id: string; weight?: number; component?: ComponentProperties; } - export interface BeginRenderingMessage { surfaceId: string; root: string; styles?: Record; } - export interface SurfaceUpdateMessage { surfaceId: string; components: ComponentInstance[]; } - export interface DataModelUpdate { surfaceId: string; path?: string; contents: ValueMap[]; } - // ValueMap is a type of DataObject for passing to the data model. export type ValueMap = DataObject & { key: string; @@ -286,18 +266,15 @@ export type ValueMap = DataObject & { valueBoolean?: boolean; valueMap?: ValueMap[]; }; - export interface DeleteSurfaceMessage { surfaceId: string; } - export interface ServerToClientMessage { beginRendering?: BeginRenderingMessage; surfaceUpdate?: SurfaceUpdateMessage; dataModelUpdate?: DataModelUpdate; deleteSurface?: DeleteSurfaceMessage; } - /** * A recursive type for any value that can appear within a resolved component * tree. This is the main type that makes the recursive resolution possible. @@ -310,13 +287,10 @@ export type ResolvedValue = | AnyComponentNode | ResolvedMap | ResolvedArray; - /** A generic map where each value has been recursively resolved. */ export type ResolvedMap = { [key: string]: ResolvedValue }; - /** A generic array where each item has been recursively resolved. */ export type ResolvedArray = ResolvedValue[]; - /** * A base interface that all component nodes share. */ @@ -326,103 +300,83 @@ interface BaseComponentNode { dataContextPath?: string; slotName?: string; } - export interface TextNode extends BaseComponentNode { type: "Text"; properties: ResolvedText; } - export interface ImageNode extends BaseComponentNode { type: "Image"; properties: ResolvedImage; } - export interface IconNode extends BaseComponentNode { type: "Icon"; properties: ResolvedIcon; } - export interface VideoNode extends BaseComponentNode { type: "Video"; properties: ResolvedVideo; } - export interface AudioPlayerNode extends BaseComponentNode { type: "AudioPlayer"; properties: ResolvedAudioPlayer; } - export interface RowNode extends BaseComponentNode { type: "Row"; properties: ResolvedRow; } - export interface ColumnNode extends BaseComponentNode { type: "Column"; properties: ResolvedColumn; } - export interface ListNode extends BaseComponentNode { type: "List"; properties: ResolvedList; } - export interface CardNode extends BaseComponentNode { type: "Card"; properties: ResolvedCard; } - export interface TabsNode extends BaseComponentNode { type: "Tabs"; properties: ResolvedTabs; } - export interface DividerNode extends BaseComponentNode { type: "Divider"; properties: ResolvedDivider; } - export interface ModalNode extends BaseComponentNode { type: "Modal"; properties: ResolvedModal; } - export interface ButtonNode extends BaseComponentNode { type: "Button"; properties: ResolvedButton; } - export interface CheckboxNode extends BaseComponentNode { type: "CheckBox"; properties: ResolvedCheckbox; } - export interface TextFieldNode extends BaseComponentNode { type: "TextField"; properties: ResolvedTextField; } - export interface DateTimeInputNode extends BaseComponentNode { type: "DateTimeInput"; properties: ResolvedDateTimeInput; } - export interface MultipleChoiceNode extends BaseComponentNode { type: "MultipleChoice"; properties: ResolvedMultipleChoice; } - export interface SliderNode extends BaseComponentNode { type: "Slider"; properties: ResolvedSlider; } - export interface CustomNode extends BaseComponentNode { type: string; // For custom nodes, properties are just a map of string keys to any resolved value. properties: CustomNodeProperties; } - /** * The complete discriminated union of all possible resolved component nodes. * A renderer would use this type for any given node in the component tree. @@ -447,7 +401,6 @@ export type AnyComponentNode = | MultipleChoiceNode | SliderNode | CustomNode; - // These components do not contain other components can reuse their // original interfaces. export type ResolvedText = Text; @@ -461,7 +414,6 @@ export type ResolvedTextField = TextField; export type ResolvedDateTimeInput = DateTimeInput; export type ResolvedMultipleChoice = MultipleChoice; export type ResolvedSlider = Slider; - export interface ResolvedRow { children: AnyComponentNode[]; distribution?: @@ -473,7 +425,6 @@ export interface ResolvedRow { | "spaceEvenly"; alignment?: "start" | "center" | "end" | "stretch"; } - export interface ResolvedColumn { children: AnyComponentNode[]; distribution?: @@ -485,43 +436,34 @@ export interface ResolvedColumn { | "spaceEvenly"; alignment?: "start" | "center" | "end" | "stretch"; } - export interface ResolvedButton { child: AnyComponentNode; action: Button["action"]; } - export interface ResolvedList { children: AnyComponentNode[]; direction?: "vertical" | "horizontal"; alignment?: "start" | "center" | "end" | "stretch"; } - export interface ResolvedCard { child: AnyComponentNode; children: AnyComponentNode[]; } - export interface ResolvedTabItem { title: StringValue; child: AnyComponentNode; } - export interface ResolvedTabs { tabItems: ResolvedTabItem[]; } - export interface ResolvedModal { entryPointChild: AnyComponentNode; contentChild: AnyComponentNode; } - export interface CustomNodeProperties { [k: string]: ResolvedValue; } - export type SurfaceID = string; - /** The complete state of a single UI surface. */ export interface Surface { rootComponentId: string | null; diff --git a/vendor/a2ui/renderers/lit/src/0.8/ui/root.ts b/vendor/a2ui/renderers/lit/src/0.8/ui/root.ts index c52ad50ede..24494fd1d3 100644 --- a/vendor/a2ui/renderers/lit/src/0.8/ui/root.ts +++ b/vendor/a2ui/renderers/lit/src/0.8/ui/root.ts @@ -1,19 +1,15 @@ /* Copyright 2025 Google LLC - Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - https://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - import { SignalWatcher } from "@lit-labs/signals"; import { consume } from "@lit/context"; import { @@ -34,48 +30,36 @@ import { Theme, AnyComponentNode, SurfaceID } from "../types/types.js"; import { themeContext } from "./context/theme.js"; import { structuralStyles } from "./styles.js"; import { componentRegistry } from "./component-registry.js"; - type NodeOfType = Extract< AnyComponentNode, { type: T } >; - // This is the base class all the components will inherit @customElement("a2ui-root") export class Root extends (SignalWatcher(LitElement) as typeof LitElement) { @property() accessor surfaceId: SurfaceID | null = null; - @property() accessor component: AnyComponentNode | null = null; - @consume({ context: themeContext }) accessor theme!: Theme; - @property({ attribute: false }) accessor childComponents: AnyComponentNode[] | null = null; - @property({ attribute: false }) accessor processor: A2uiMessageProcessor | null = null; - @property() accessor dataContextPath: string = ""; - @property() accessor enableCustomElements = false; - @property() set weight(weight: string | number) { this.#weight = weight; this.style.setProperty("--weight", `${weight}`); } - get weight() { return this.#weight; } - #weight: string | number = 1; - static styles = [ structuralStyles, css` @@ -87,44 +71,36 @@ export class Root extends (SignalWatcher(LitElement) as typeof LitElement) { } `, ]; - /** * Holds the cleanup function for our effect. * We need this to stop the effect when the component is disconnected. */ #lightDomEffectDisposer: null | (() => void) = null; - protected willUpdate(changedProperties: PropertyValues): void { if (changedProperties.has("childComponents")) { if (this.#lightDomEffectDisposer) { this.#lightDomEffectDisposer(); } - // This effect watches the A2UI Children signal and updates the Light DOM. this.#lightDomEffectDisposer = effect(() => { // 1. Read the signal to create the subscription. const allChildren = this.childComponents ?? null; - // 2. Generate the template for the children. const lightDomTemplate = this.renderComponentTree(allChildren); - // 3. Imperatively render that template into the component itself. render(lightDomTemplate, this, { host: this }); }); } } - /** * Clean up the effect when the component is removed from the DOM. */ disconnectedCallback(): void { super.disconnectedCallback(); - if (this.#lightDomEffectDisposer) { this.#lightDomEffectDisposer(); } } - /** * Turns the SignalMap into a renderable TemplateResult for Lit. */ @@ -134,18 +110,15 @@ export class Root extends (SignalWatcher(LitElement) as typeof LitElement) { if (!components) { return nothing; } - if (!Array.isArray(components)) { return nothing; } - return html` ${map(components, (component) => { // 1. Check if there is a registered custom component or override. if (this.enableCustomElements) { const registeredCtor = componentRegistry.get(component.type); // We also check customElements.get for non-registered but defined elements const elCtor = registeredCtor || customElements.get(component.type); - if (elCtor) { const node = component as AnyComponentNode; const el = new elCtor() as Root; @@ -158,7 +131,6 @@ export class Root extends (SignalWatcher(LitElement) as typeof LitElement) { el.processor = this.processor; el.surfaceId = this.surfaceId; el.dataContextPath = node.dataContextPath ?? "/"; - for (const [prop, val] of Object.entries(component.properties)) { // @ts-expect-error We're off the books. el[prop] = val; @@ -166,7 +138,6 @@ export class Root extends (SignalWatcher(LitElement) as typeof LitElement) { return html`${el}`; } } - // 2. Fallback to standard components. switch (component.type) { case "List": { @@ -184,7 +155,6 @@ export class Root extends (SignalWatcher(LitElement) as typeof LitElement) { .enableCustomElements=${this.enableCustomElements} >`; } - case "Card": { const node = component as NodeOfType<"Card">; let childComponents: AnyComponentNode[] | null = @@ -192,7 +162,6 @@ export class Root extends (SignalWatcher(LitElement) as typeof LitElement) { if (!childComponents && node.properties.child) { childComponents = [node.properties.child]; } - return html``; } - case "Column": { const node = component as NodeOfType<"Column">; return html``; } - case "Row": { const node = component as NodeOfType<"Row">; return html``; } - case "Image": { const node = component as NodeOfType<"Image">; return html``; } - case "Icon": { const node = component as NodeOfType<"Icon">; return html``; } - case "AudioPlayer": { const node = component as NodeOfType<"AudioPlayer">; return html``; } - case "Button": { const node = component as NodeOfType<"Button">; return html``; } - case "Text": { const node = component as NodeOfType<"Text">; return html``; } - case "CheckBox": { const node = component as NodeOfType<"CheckBox">; return html``; } - case "DateTimeInput": { const node = component as NodeOfType<"DateTimeInput">; return html``; } - case "Divider": { // TODO: thickness, axis and color. const node = component as NodeOfType<"Divider">; @@ -371,7 +330,6 @@ export class Root extends (SignalWatcher(LitElement) as typeof LitElement) { .enableCustomElements=${this.enableCustomElements} >`; } - case "MultipleChoice": { // TODO: maxAllowedSelections and selections. const node = component as NodeOfType<"MultipleChoice">; @@ -389,7 +347,6 @@ export class Root extends (SignalWatcher(LitElement) as typeof LitElement) { .enableCustomElements=${this.enableCustomElements} >`; } - case "Slider": { const node = component as NodeOfType<"Slider">; return html``; } - case "TextField": { // TODO: type and validationRegexp. const node = component as NodeOfType<"TextField">; @@ -425,7 +381,6 @@ export class Root extends (SignalWatcher(LitElement) as typeof LitElement) { .enableCustomElements=${this.enableCustomElements} >`; } - case "Video": { const node = component as NodeOfType<"Video">; return html``; } - case "Tabs": { const node = component as NodeOfType<"Tabs">; const titles: StringValue[] = []; @@ -451,7 +405,6 @@ export class Root extends (SignalWatcher(LitElement) as typeof LitElement) { childComponents.push(item.child); } } - return html``; } - case "Modal": { const node = component as NodeOfType<"Modal">; const childComponents: AnyComponentNode[] = [ node.properties.entryPointChild, node.properties.contentChild, ]; - node.properties.entryPointChild.slotName = "entry"; - return html``; } - default: { return this.renderCustomComponent(component); } } })}`; } - private renderCustomComponent(component: AnyComponentNode) { if (!this.enableCustomElements) { return; } - const node = component as AnyComponentNode; const registeredCtor = componentRegistry.get(component.type); const elCtor = registeredCtor || customElements.get(component.type); - if (!elCtor) { return html`Unknown element ${component.type}`; } - const el = new elCtor() as Root; el.id = node.id; if (node.slotName) { @@ -518,14 +463,12 @@ export class Root extends (SignalWatcher(LitElement) as typeof LitElement) { el.processor = this.processor; el.surfaceId = this.surfaceId; el.dataContextPath = node.dataContextPath ?? "/"; - for (const [prop, val] of Object.entries(component.properties)) { // @ts-expect-error We're off the books. el[prop] = val; } return html`${el}`; } - render(): TemplateResult | typeof nothing { return html``; } diff --git a/vendor/a2ui/specification/0.8/eval/src/validator.ts b/vendor/a2ui/specification/0.8/eval/src/validator.ts index e00e876c1c..89f41d3981 100644 --- a/vendor/a2ui/specification/0.8/eval/src/validator.ts +++ b/vendor/a2ui/specification/0.8/eval/src/validator.ts @@ -1,22 +1,17 @@ /* Copyright 2025 Google LLC - Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - https://www.apache.org/licenses/LICENSE-2.0 - Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ - import { SurfaceUpdateSchemaMatcher } from "./surface_update_schema_matcher"; import { SchemaMatcher } from "./schema_matcher"; - export function validateSchema( data: any, schemaName: string, @@ -36,7 +31,6 @@ export function validateSchema( "A2UI Protocol message must have one of: surfaceUpdate, dataModelUpdate, beginRendering, deleteSurface.", ); } - if (matchers) { for (const matcher of matchers) { const result = matcher.validate(data); @@ -45,10 +39,8 @@ export function validateSchema( } } } - return errors; } - function validateDeleteSurface(data: any, errors: string[]) { if (data.surfaceId === undefined) { errors.push("DeleteSurface must have a 'surfaceId' property."); @@ -60,7 +52,6 @@ function validateDeleteSurface(data: any, errors: string[]) { } } } - function validateSurfaceUpdate(data: any, errors: string[]) { if (data.surfaceId === undefined) { errors.push("SurfaceUpdate must have a 'surfaceId' property."); @@ -69,7 +60,6 @@ function validateSurfaceUpdate(data: any, errors: string[]) { errors.push("SurfaceUpdate must have a 'components' array."); return; } - const componentIds = new Set(); for (const c of data.components) { if (c.id) { @@ -79,29 +69,24 @@ function validateSurfaceUpdate(data: any, errors: string[]) { componentIds.add(c.id); } } - for (const component of data.components) { validateComponent(component, componentIds, errors); } } - function validateDataModelUpdate(data: any, errors: string[]) { if (data.surfaceId === undefined) { errors.push("DataModelUpdate must have a 'surfaceId' property."); } - const allowedTopLevel = ["surfaceId", "path", "contents"]; for (const key in data) { if (!allowedTopLevel.includes(key)) { errors.push(`DataModelUpdate has unexpected property: ${key}`); } } - if (!Array.isArray(data.contents)) { errors.push("DataModelUpdate must have a 'contents' array."); return; } - const validateValueProperty = ( item: any, itemErrors: string[], @@ -127,7 +112,6 @@ function validateDataModelUpdate(data: any, errors: string[]) { ); return; } - if (foundValueProp === "valueMap") { if (!Array.isArray(item.valueMap)) { itemErrors.push(`${prefix} 'valueMap' must be an array.`); @@ -162,7 +146,6 @@ function validateDataModelUpdate(data: any, errors: string[]) { }); } }; - data.contents.forEach((item: any, index: number) => { if (!item.key) { errors.push( @@ -190,7 +173,6 @@ function validateDataModelUpdate(data: any, errors: string[]) { } }); } - function validateBeginRendering(data: any, errors: string[]) { if (data.surfaceId === undefined) { errors.push("BeginRendering message must have a 'surfaceId' property."); @@ -199,7 +181,6 @@ function validateBeginRendering(data: any, errors: string[]) { errors.push("BeginRendering message must have a 'root' property."); } } - function validateBoundValue( prop: any, propName: string, @@ -232,7 +213,6 @@ function validateBoundValue( ); } } - function validateComponent( component: any, allIds: Set, @@ -246,7 +226,6 @@ function validateComponent( errors.push(`Component '${component.id}' is missing 'component'.`); return; } - const componentTypes = Object.keys(component.component); if (componentTypes.length !== 1) { errors.push( @@ -254,10 +233,8 @@ function validateComponent( ); return; } - const componentType = componentTypes[0]; const properties = component.component[componentType]; - const checkRequired = (props: string[]) => { for (const prop of props) { if (properties[prop] === undefined) { @@ -267,7 +244,6 @@ function validateComponent( } } }; - const checkRefs = (ids: (string | undefined)[]) => { for (const id of ids) { if (id && !allIds.has(id)) { @@ -277,7 +253,6 @@ function validateComponent( } } }; - switch (componentType) { case "Heading": checkRequired(["text"]);