diff --git a/renderers/lit/fixing.md b/renderers/lit/fixing.md new file mode 100644 index 00000000..dac2f185 --- /dev/null +++ b/renderers/lit/fixing.md @@ -0,0 +1,52 @@ +# Debugging Report + +## Issue 1: Nothing Renders (Fixed) + +**Symptoms:** +The sample app starts, but the screen is blank. Console logs (added during debugging) showed: +`[Surface] #renderSurface. Renderer: false, Node: true` +Followed by: +`Surface cannot render content: {renderer: false, node: 'root-column'}` + +**Root Cause:** +The `` component requires a `renderer` property (an instance of `LitRenderer`) to convert the abstract component tree into Lit templates. The consumer (the sample shell app) was not providing this property, leaving it `null`. + +**Solution:** +I modified `src/0.8/lit/components/surface.ts` to initialize the `renderer` property with a default instance using the `standardLitCatalogImplementation`. This ensures the Surface can render even if the parent application doesn't explicitly inject a renderer. + +```typescript +// src/0.8/lit/components/surface.ts + +// ... imports +import { standardLitCatalogImplementation } from "../standard_catalog_implementation/standard_catalog_lit.js"; + +// ... inside class Surface + @property({ attribute: false }) + accessor renderer: LitRenderer | null = new LitRenderer( + standardLitCatalogImplementation + ); +``` + +--- + +## Issue 2: "(no model)" Output (Pending) + +**Symptoms:** +Components that use data bindings (like `Text`, `Image`, etc.) render the string `(no model)` instead of the actual data. +Logs added to `Text` component confirm: +`Text component missing dependencies: { processor: false, node: true }` + +**Root Cause:** +It appears that the `LitRenderer` (and specifically component renderers like `litTextRenderer`) creates the Lit components (e.g., ``) but does not pass the `processor` property to them. +The rendering chain is `Surface` -> `LitRenderer` -> `ComponentRenderer` -> `LitComponent`. +The `Surface` has the `processor`, but it only calls `renderer.renderNode(node)`. It does not pass the `processor`. Consequently, the leaf components (which inherit from `Root`) have a `null` processor and cannot resolve paths like `text.path`. + +**Solution:** +The robust solution is to use **Lit Context** to provide the `MessageProcessor` from the `Surface` (the provider) to all descendant components (consumers). This avoids needing to thread the `processor` argument through the generic `FrameworkRenderer` interfaces which are not aware of the processor. + +**Steps to implement:** +1. Create a `processorContext` key. +2. Update `Surface` to provide this context. +3. Update `Root` (the base class for all A2UI Lit components) to `@consume` this context. + +This will automatically make `this.processor` available in all components, resolving the `(no model)` issue. diff --git a/renderers/lit/package.json b/renderers/lit/package.json index 6cc360cb..b8e6652a 100644 --- a/renderers/lit/package.json +++ b/renderers/lit/package.json @@ -14,8 +14,8 @@ "default": "./dist/src/0.8/core.js" }, "./ui": { - "types": "./dist/src/0.8/ui/ui.d.ts", - "default": "./dist/src/0.8/ui/ui.js" + "types": "./dist/src/0.8/lit/components/ui.d.ts", + "default": "./dist/src/0.8/lit/components/ui.js" } }, "type": "module", diff --git a/renderers/lit/src/0.8/core.ts b/renderers/lit/src/0.8/core.ts index 9c16e747..f19fbbfe 100644 --- a/renderers/lit/src/0.8/core.ts +++ b/renderers/lit/src/0.8/core.ts @@ -15,21 +15,19 @@ */ export * as Events from "./events/events.js"; -export * as Types from "./types/types.js"; -export * as Primitives from "./types/primitives.js"; +export * as Types from "./core/types/types.js"; +export * as Primitives from "./core/types/primitives.js"; export * as Styles from "./styles/index.js"; -import * as Guards from "./data/guards.js"; +export { CatalogApi } from "./core/types/types.js"; +export { type ComponentApi } from "./core/types/types.js"; +export { standardCatalogApi } from "./core/standard_catalog_api/standard_catalog.js"; +import * as Guards from "./core/data/guards.js"; -import { create as createSignalA2uiMessageProcessor } from "./data/signal-model-processor.js"; -import { A2uiMessageProcessor } from "./data/model-processor.js"; -import A2UIClientEventMessage from "./schemas/server_to_client_with_standard_catalog.json" with { type: "json" }; +import { create as createSignalA2uiMessageProcessor } from "./core/data/signal-model-processor.js"; +import { A2uiMessageProcessor } from "./core/a2ui_message_processor.js"; export const Data = { createSignalA2uiMessageProcessor, A2uiMessageProcessor, Guards, }; - -export const Schemas = { - A2UIClientEventMessage, -}; diff --git a/renderers/lit/src/0.8/data/model-processor.ts b/renderers/lit/src/0.8/core/a2ui_message_processor.ts similarity index 69% rename from renderers/lit/src/0.8/data/model-processor.ts rename to renderers/lit/src/0.8/core/a2ui_message_processor.ts index 3b08f699..aa873a2b 100644 --- a/renderers/lit/src/0.8/data/model-processor.ts +++ b/renderers/lit/src/0.8/core/a2ui_message_processor.ts @@ -31,31 +31,15 @@ import { MessageProcessor, ValueMap, DataObject, -} from "../types/types"; + CatalogApi, + AnyResolvedNode, + ComponentArrayReference, +} from "./types/types.js"; import { isComponentArrayReference, isObject, isPath, - isResolvedAudioPlayer, - isResolvedButton, - isResolvedCard, - isResolvedCheckbox, - isResolvedColumn, - isResolvedDateTimeInput, - isResolvedDivider, - isResolvedIcon, - isResolvedImage, - isResolvedList, - isResolvedModal, - isResolvedMultipleChoice, - isResolvedRow, - isResolvedSlider, - isResolvedTabs, - isResolvedText, - isResolvedTextField, - isResolvedVideo, - isValueMap, -} from "./guards.js"; +} from "./data/guards.js"; /** * Processes and consolidates A2UIProtocolMessage objects into a structured, @@ -69,21 +53,23 @@ export class A2uiMessageProcessor implements MessageProcessor { private setCtor: SetConstructor = Set; private objCtor: ObjectConstructor = Object; private surfaces: Map; + private readonly catalog: CatalogApi; constructor( readonly opts: { - mapCtor: MapConstructor; - arrayCtor: ArrayConstructor; - setCtor: SetConstructor; - objCtor: ObjectConstructor; - } = { mapCtor: Map, arrayCtor: Array, setCtor: Set, objCtor: Object } + mapCtor?: MapConstructor; + arrayCtor?: ArrayConstructor; + setCtor?: SetConstructor; + objCtor?: ObjectConstructor; + catalog: CatalogApi; + } ) { - this.arrayCtor = opts.arrayCtor; - this.mapCtor = opts.mapCtor; - this.setCtor = opts.setCtor; - this.objCtor = opts.objCtor; - - this.surfaces = new opts.mapCtor(); + this.arrayCtor = opts.arrayCtor ?? Array; + this.mapCtor = opts.mapCtor ?? Map; + this.setCtor = opts.setCtor ?? Set; + this.objCtor = opts.objCtor ?? Object; + this.surfaces = new (this.mapCtor)(); + this.catalog = opts.catalog; } getSurfaces(): ReadonlyMap { @@ -129,11 +115,11 @@ export class A2uiMessageProcessor implements MessageProcessor { * own data context. */ getData( - node: AnyComponentNode, + node: AnyResolvedNode, relativePath: string, surfaceId = A2uiMessageProcessor.DEFAULT_SURFACE_ID ): DataValue | null { - const surface = this.getOrCreateSurface(surfaceId); + const surface = this.surfaces.get(surfaceId); if (!surface) return null; let finalPath: string; @@ -151,7 +137,7 @@ export class A2uiMessageProcessor implements MessageProcessor { } setData( - node: AnyComponentNode | null, + node: AnyResolvedNode | null, relativePath: string, value: DataValue, surfaceId = A2uiMessageProcessor.DEFAULT_SURFACE_ID @@ -161,7 +147,7 @@ export class A2uiMessageProcessor implements MessageProcessor { return; } - const surface = this.getOrCreateSurface(surfaceId); + const surface = this.surfaces.get(surfaceId); if (!surface) return; let finalPath: string; @@ -294,9 +280,9 @@ export class A2uiMessageProcessor implements MessageProcessor { if (segments.length === 0) { // Root data can either be a Map or an Object. If we receive an Object, // however, we will normalize it to a proper Map. - if (value instanceof Map || isObject(value)) { + if (this.isMap(value) || isObject(value)) { // Normalize an Object to a Map. - if (!(value instanceof Map) && isObject(value)) { + if (!this.isMap(value) && isObject(value)) { value = new this.mapCtor(Object.entries(value)); } @@ -315,7 +301,7 @@ export class A2uiMessageProcessor implements MessageProcessor { const segment = segments[i]; let target: DataValue | undefined; - if (current instanceof Map) { + if (this.isMap(current)) { target = current.get(segment); } else if (Array.isArray(current) && /^\d+$/.test(segment)) { target = current[parseInt(segment, 10)]; @@ -327,7 +313,7 @@ export class A2uiMessageProcessor implements MessageProcessor { target === null ) { target = new this.mapCtor(); - if (current instanceof this.mapCtor) { + if (this.isMap(current)) { current.set(segment, target); } else if (Array.isArray(current)) { current[parseInt(segment, 10)] = target; @@ -338,7 +324,7 @@ export class A2uiMessageProcessor implements MessageProcessor { const finalSegment = segments[segments.length - 1]; const storedValue = value; - if (current instanceof this.mapCtor) { + if (this.isMap(current)) { current.set(finalSegment, storedValue); } else if (Array.isArray(current) && /^\d+$/.test(finalSegment)) { current[parseInt(finalSegment, 10)] = storedValue; @@ -362,6 +348,16 @@ export class A2uiMessageProcessor implements MessageProcessor { return "/" + segments.filter((s) => s.length > 0).join("/"); } + private isMap(value: unknown): value is DataMap { + return ( + value instanceof this.mapCtor || + (isObject(value) && + typeof (value as any).get === "function" && + typeof (value as any).set === "function" && + typeof (value as any).entries === "function") + ); + } + private getDataByPath(root: DataMap, path: string): DataValue | null { const segments = this.normalizePath(path) .split("/") @@ -371,7 +367,7 @@ export class A2uiMessageProcessor implements MessageProcessor { for (const segment of segments) { if (current === undefined || current === null) return null; - if (current instanceof Map) { + if (this.isMap(current)) { current = current.get(segment) as DataMap; } else if (Array.isArray(current) && /^\d+$/.test(segment)) { current = current[parseInt(segment, 10)]; @@ -497,222 +493,41 @@ export class A2uiMessageProcessor implements MessageProcessor { const unresolvedProperties = componentProps[componentType as keyof typeof componentProps]; - // Manually build the resolvedProperties object by resolving each value in - // the component's properties. - const resolvedProperties: ResolvedMap = new this.objCtor() as ResolvedMap; - if (isObject(unresolvedProperties)) { - for (const [key, value] of Object.entries(unresolvedProperties)) { - resolvedProperties[key] = this.resolvePropertyValue( - value, - surface, - visited, - dataContextPath, - idSuffix - ); - } + const resolver = (value: unknown) => this.resolvePropertyValue( + value, + surface, + visited, + dataContextPath, + idSuffix + ); + + const catalogItem = this.catalog.get(componentType); + if (!catalogItem) { + // Fallback for unknown (custom) components. + return new this.objCtor({ + id: fullId, + dataContextPath, + weight: componentData.weight ?? "initial", + type: componentType, + properties: isObject(unresolvedProperties) ? Object.fromEntries(Object.entries(unresolvedProperties).map(([key, value]) => [key, resolver(value)])) : {}, + }) as AnyComponentNode; } + + const resolvedProperties = catalogItem.resolveProperties( + isObject(unresolvedProperties) ? unresolvedProperties : {}, + resolver + ); + visited.delete(fullId); - // Now that we have the resolved properties in place we can go ahead and - // ensure that they meet expectations in terms of types and so forth, - // casting them into the specific shape for usage. - const baseNode = { + return new this.objCtor({ id: fullId, dataContextPath, weight: componentData.weight ?? "initial", - }; - switch (componentType) { - case "Text": - if (!isResolvedText(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "Text", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "Image": - if (!isResolvedImage(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "Image", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "Icon": - if (!isResolvedIcon(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "Icon", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "Video": - if (!isResolvedVideo(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "Video", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "AudioPlayer": - if (!isResolvedAudioPlayer(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "AudioPlayer", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "Row": - if (!isResolvedRow(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - - return new this.objCtor({ - ...baseNode, - type: "Row", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "Column": - if (!isResolvedColumn(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - - return new this.objCtor({ - ...baseNode, - type: "Column", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "List": - if (!isResolvedList(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "List", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "Card": - if (!isResolvedCard(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "Card", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "Tabs": - if (!isResolvedTabs(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "Tabs", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "Divider": - if (!isResolvedDivider(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "Divider", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "Modal": - if (!isResolvedModal(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "Modal", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "Button": - if (!isResolvedButton(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "Button", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "CheckBox": - if (!isResolvedCheckbox(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "CheckBox", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "TextField": - if (!isResolvedTextField(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "TextField", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "DateTimeInput": - if (!isResolvedDateTimeInput(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "DateTimeInput", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "MultipleChoice": - if (!isResolvedMultipleChoice(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "MultipleChoice", - properties: resolvedProperties, - }) as AnyComponentNode; - - case "Slider": - if (!isResolvedSlider(resolvedProperties)) { - throw new Error(`Invalid data; expected ${componentType}`); - } - return new this.objCtor({ - ...baseNode, - type: "Slider", - properties: resolvedProperties, - }) as AnyComponentNode; - - default: - // Catch-all for other custom component types. - return new this.objCtor({ - ...baseNode, - type: componentType, - properties: resolvedProperties, - }) as AnyComponentNode; - } + type: componentType, + ...resolvedProperties, + }) as AnyComponentNode; } /** @@ -742,7 +557,7 @@ export class A2uiMessageProcessor implements MessageProcessor { // resolve the list and return an array of nodes. if (isComponentArrayReference(value)) { if (value.explicitList) { - return value.explicitList.map((id) => + return value.explicitList.map((id: string) => this.buildNodeRecursive( id, surface, @@ -852,4 +667,4 @@ export class A2uiMessageProcessor implements MessageProcessor { // 5. Otherwise, it's a primitive value. return value as ResolvedValue; } -} +} \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/data/guards.ts b/renderers/lit/src/0.8/core/data/guards.ts new file mode 100644 index 00000000..5a29a98a --- /dev/null +++ b/renderers/lit/src/0.8/core/data/guards.ts @@ -0,0 +1,76 @@ +/* + 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 { StringValue, NumberValue, BooleanValue } from "../types/primitives.js"; +import { + AnyComponentNode, + ComponentArrayReference, + ValueMap, +} from "../types/types.js"; + +export function isValueMap(value: unknown): value is ValueMap { + return isObject(value) && "key" in value; +} + +export function isPath(key: string, value: unknown): value is string { + return key === "path" && typeof value === "string"; +} + +export function isObject(value: unknown): value is Record { + return typeof value === "object" && value !== null && !Array.isArray(value); +} + +export function isComponentArrayReference( + value: unknown +): value is ComponentArrayReference { + if (!isObject(value)) return false; + return "explicitList" in value || "template" in value; +} + +function isStringValue(value: unknown): value is StringValue { + return ( + isObject(value) && + ("path" in value || + ("literal" in value && typeof value.literal === "string") || + "literalString" in value) + ); +} + +function isNumberValue(value: unknown): value is NumberValue { + return ( + isObject(value) && + ("path" in value || + ("literal" in value && typeof value.literal === "number") || + "literalNumber" in value) + ); +} + +function isBooleanValue(value: unknown): value is BooleanValue { + return ( + isObject(value) && + ("path" in value || + ("literal" in value && typeof value.literal === "boolean") || + "literalBoolean" in value) + ); +} + +function isAnyComponentNode(value: unknown): value is AnyComponentNode { + if (!isObject(value)) return false; + const hasBaseKeys = "id" in value && "type" in value && "properties" in value; + if (!hasBaseKeys) return false; + + return true; +} \ No newline at end of file diff --git a/renderers/lit/src/0.8/data/signal-model-processor.ts b/renderers/lit/src/0.8/core/data/signal-model-processor.ts similarity index 85% rename from renderers/lit/src/0.8/data/signal-model-processor.ts rename to renderers/lit/src/0.8/core/data/signal-model-processor.ts index e821c152..d7e2ab37 100644 --- a/renderers/lit/src/0.8/data/signal-model-processor.ts +++ b/renderers/lit/src/0.8/core/data/signal-model-processor.ts @@ -14,18 +14,20 @@ limitations under the License. */ -import { A2uiMessageProcessor } from "./model-processor.js"; +import { A2uiMessageProcessor } from "../a2ui_message_processor.js"; +import { CatalogApi } from "../types/types.js"; import { SignalArray } from "signal-utils/array"; import { SignalMap } from "signal-utils/map"; import { SignalObject } from "signal-utils/object"; import { SignalSet } from "signal-utils/set"; -export function create() { +export function create(catalog: CatalogApi) { return new A2uiMessageProcessor({ arrayCtor: SignalArray as unknown as ArrayConstructor, mapCtor: SignalMap as unknown as MapConstructor, objCtor: SignalObject as unknown as ObjectConstructor, setCtor: SignalSet as unknown as SetConstructor, + catalog, }); } diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/audio_player.ts b/renderers/lit/src/0.8/core/standard_catalog_api/audio_player.ts new file mode 100644 index 00000000..467347ee --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/audio_player.ts @@ -0,0 +1,48 @@ +/* + 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 { + ComponentApi, + BaseResolvedNode, +} from '../types/types.js'; +import { StringValue } from '../types/primitives.js'; + +export interface AudioPlayerNode extends BaseResolvedNode<'AudioPlayer'> { + properties: { + url: StringValue; + /** + * A label, title, or placeholder text. + */ + description?: StringValue; + } +} + +export const audioPlayerApi: ComponentApi<'AudioPlayer', AudioPlayerNode> = { + name: 'AudioPlayer', + + resolveProperties(unresolved, resolver) { + if (!unresolved || typeof unresolved.url !== 'object') { + throw new Error('Invalid properties for AudioPlayer: missing url.'); + } + + return { + properties: { + url: resolver(unresolved.url) as StringValue, + description: resolver(unresolved.description) as StringValue, + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/button.ts b/renderers/lit/src/0.8/core/standard_catalog_api/button.ts new file mode 100644 index 00000000..c204be0a --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/button.ts @@ -0,0 +1,46 @@ +/* + 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 { + ComponentApi, + AnyResolvedNode, + BaseResolvedNode, + Action, +} from '../types/types.js'; + +export interface ButtonNode extends BaseResolvedNode<'Button'> { + properties: { + child: AnyResolvedNode; + action: Action; + } +} + +export const buttonApi: ComponentApi<'Button', ButtonNode> = { + name: 'Button', + + resolveProperties(unresolved, resolver) { + if (!unresolved || typeof unresolved.child !== 'string' || !unresolved.action) { + throw new Error('Invalid properties for Button: missing child or action.'); + } + + return { + properties: { + child: resolver(unresolved.child) as AnyResolvedNode, + action: unresolved.action as Action, + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/card.ts b/renderers/lit/src/0.8/core/standard_catalog_api/card.ts new file mode 100644 index 00000000..2cb28dcd --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/card.ts @@ -0,0 +1,48 @@ +/* + 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 { + ComponentApi, + AnyResolvedNode, + BaseResolvedNode, +} from '../types/types.js'; + +export interface CardNode extends BaseResolvedNode<'Card'> { + properties: { + child: AnyResolvedNode; + children: AnyResolvedNode[]; + } +} + +export const cardApi: ComponentApi<'Card', CardNode> = { + name: 'Card', + + resolveProperties(unresolved, resolver) { + if (!unresolved || (typeof unresolved.child !== 'string' && !Array.isArray(unresolved.children))) { + throw new Error('Invalid properties for Card: missing child or children.'); + } + + const child = resolver(unresolved.child) as AnyResolvedNode; + const children = Array.isArray(unresolved.children) ? resolver(unresolved.children) as AnyResolvedNode[] : []; + + return { + properties: { + child, + children + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/checkbox.ts b/renderers/lit/src/0.8/core/standard_catalog_api/checkbox.ts new file mode 100644 index 00000000..17fa931b --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/checkbox.ts @@ -0,0 +1,45 @@ +/* + 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 { + ComponentApi, + BaseResolvedNode, +} from '../types/types.js'; +import { StringValue, BooleanValue } from '../types/primitives.js'; + +export interface CheckboxNode extends BaseResolvedNode<'CheckBox'> { + properties: { + label: StringValue; + value: BooleanValue; + } +} + +export const checkboxApi: ComponentApi<'CheckBox', CheckboxNode> = { + name: 'CheckBox', + + resolveProperties(unresolved, resolver) { + if (!unresolved || typeof unresolved.label !== 'object' || typeof unresolved.value !== 'object') { + throw new Error('Invalid properties for CheckBox: missing label or value.'); + } + + return { + properties: { + label: resolver(unresolved.label) as StringValue, + value: resolver(unresolved.value) as BooleanValue, + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/column.ts b/renderers/lit/src/0.8/core/standard_catalog_api/column.ts new file mode 100644 index 00000000..38f6aeae --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/column.ts @@ -0,0 +1,53 @@ +/* + 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 { + ComponentApi, + AnyResolvedNode, + BaseResolvedNode, +} from '../types/types.js'; + +export interface ColumnNode extends BaseResolvedNode<'Column'> { + properties: { + children: AnyResolvedNode[]; + distribution?: + | "start" + | "center" + | "end" + | "spaceBetween" + | "spaceAround" + | "spaceEvenly"; + alignment?: "start" | "center" | "end" | "stretch"; + } +} + +export const columnApi: ComponentApi<'Column', ColumnNode> = { + name: 'Column', + + resolveProperties(unresolved, resolver) { + if (!unresolved || !unresolved.children) { + throw new Error('Invalid properties for Column: missing children.'); + } + + return { + properties: { + children: resolver(unresolved.children) as AnyResolvedNode[], + distribution: unresolved.distribution as ColumnNode['properties']['distribution'], + alignment: unresolved.alignment as ColumnNode['properties']['alignment'], + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/datetime_input.ts b/renderers/lit/src/0.8/core/standard_catalog_api/datetime_input.ts new file mode 100644 index 00000000..535cfe2d --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/datetime_input.ts @@ -0,0 +1,49 @@ +/* + 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 { + ComponentApi, + BaseResolvedNode, +} from '../types/types.js'; +import { StringValue } from '../types/primitives.js'; + +export interface DateTimeInputNode extends BaseResolvedNode<'DateTimeInput'> { + properties: { + value: StringValue; + enableDate?: boolean; + enableTime?: boolean; + outputFormat?: string; + } +} + +export const dateTimeInputApi: ComponentApi<'DateTimeInput', DateTimeInputNode> = { + name: 'DateTimeInput', + + resolveProperties(unresolved, resolver) { + if (!unresolved || typeof unresolved.value !== 'object') { + throw new Error('Invalid properties for DateTimeInput: missing value.'); + } + + return { + properties: { + value: resolver(unresolved.value) as StringValue, + enableDate: unresolved.enableDate as boolean, + enableTime: unresolved.enableTime as boolean, + outputFormat: unresolved.outputFormat as string, + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/divider.ts b/renderers/lit/src/0.8/core/standard_catalog_api/divider.ts new file mode 100644 index 00000000..915569f6 --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/divider.ts @@ -0,0 +1,42 @@ +/* + 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 { + ComponentApi, + BaseResolvedNode, +} from '../types/types.js'; + +export interface DividerNode extends BaseResolvedNode<'Divider'> { + properties: { + axis?: "horizontal" | "vertical"; + color?: string; + thickness?: number; + } +} + +export const dividerApi: ComponentApi<'Divider', DividerNode> = { + name: 'Divider', + + resolveProperties(unresolved, _resolver) { + return { + properties: { + axis: unresolved.axis as DividerNode['properties']['axis'], + color: unresolved.color as DividerNode['properties']['color'], + thickness: unresolved.thickness as DividerNode['properties']['thickness'], + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/icon.ts b/renderers/lit/src/0.8/core/standard_catalog_api/icon.ts new file mode 100644 index 00000000..14798539 --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/icon.ts @@ -0,0 +1,43 @@ +/* + 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 { + ComponentApi, + BaseResolvedNode, +} from '../types/types.js'; +import { StringValue } from '../types/primitives.js'; + +export interface IconNode extends BaseResolvedNode<'Icon'> { + properties: { + name: StringValue; + } +} + +export const iconApi: ComponentApi<'Icon', IconNode> = { + name: 'Icon', + + resolveProperties(unresolved, resolver) { + if (!unresolved || typeof unresolved.name !== 'object') { + throw new Error('Invalid properties for Icon: missing name.'); + } + + return { + properties: { + name: resolver(unresolved.name) as StringValue, + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/image.ts b/renderers/lit/src/0.8/core/standard_catalog_api/image.ts new file mode 100644 index 00000000..be902d83 --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/image.ts @@ -0,0 +1,47 @@ +/* + 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 { + ComponentApi, + BaseResolvedNode, +} from '../types/types.js'; +import { StringValue } from '../types/primitives.js'; + +export interface ImageNode extends BaseResolvedNode<'Image'> { + properties: { + url: StringValue; + usageHint: "icon" | "avatar" | "smallFeature" | "mediumFeature" | "largeFeature" | "header"; + fit?: "contain" | "cover" | "fill" | "none" | "scale-down"; + } +} + +export const imageApi: ComponentApi<'Image', ImageNode> = { + name: 'Image', + + resolveProperties(unresolved, resolver) { + if (!unresolved || typeof unresolved.url !== 'object') { + throw new Error('Invalid properties for Image: missing url.'); + } + + return { + properties: { + url: resolver(unresolved.url) as StringValue, + usageHint: unresolved.usageHint as ImageNode['properties']['usageHint'], + fit: unresolved.fit as ImageNode['properties']['fit'], + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/list.ts b/renderers/lit/src/0.8/core/standard_catalog_api/list.ts new file mode 100644 index 00000000..295a2338 --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/list.ts @@ -0,0 +1,47 @@ +/* + 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 { + ComponentApi, + AnyResolvedNode, + BaseResolvedNode, +} from '../types/types.js'; + +export interface ListNode extends BaseResolvedNode<'List'> { + properties: { + children: AnyResolvedNode[]; + direction?: "vertical" | "horizontal"; + alignment?: "start" | "center" | "end" | "stretch"; + } +} + +export const listApi: ComponentApi<'List', ListNode> = { + name: 'List', + + resolveProperties(unresolved, resolver) { + if (!unresolved || !unresolved.children) { + throw new Error('Invalid properties for List: missing children.'); + } + + return { + properties: { + children: resolver(unresolved.children) as AnyResolvedNode[], + direction: unresolved.direction as ListNode['properties']['direction'], + alignment: unresolved.alignment as ListNode['properties']['alignment'], + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/modal.ts b/renderers/lit/src/0.8/core/standard_catalog_api/modal.ts new file mode 100644 index 00000000..2cc18eda --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/modal.ts @@ -0,0 +1,45 @@ +/* + 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 { + ComponentApi, + AnyResolvedNode, + BaseResolvedNode, +} from '../types/types.js'; + +export interface ModalNode extends BaseResolvedNode<'Modal'> { + properties: { + entryPointChild: AnyResolvedNode; + contentChild: AnyResolvedNode; + } +} + +export const modalApi: ComponentApi<'Modal', ModalNode> = { + name: 'Modal', + + resolveProperties(unresolved, resolver) { + if (!unresolved || typeof unresolved.entryPointChild !== 'string' || typeof unresolved.contentChild !== 'string') { + throw new Error('Invalid properties for Modal: missing entryPointChild or contentChild.'); + } + + return { + properties: { + entryPointChild: resolver(unresolved.entryPointChild) as AnyResolvedNode, + contentChild: resolver(unresolved.contentChild) as AnyResolvedNode, + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/multiple_choice.ts b/renderers/lit/src/0.8/core/standard_catalog_api/multiple_choice.ts new file mode 100644 index 00000000..290bacfc --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/multiple_choice.ts @@ -0,0 +1,56 @@ +/* + 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 { + ComponentApi, + BaseResolvedNode, +} from '../types/types.js'; +import { StringValue } from '../types/primitives.js'; + +export interface MultipleChoiceNode extends BaseResolvedNode<'MultipleChoice'> { + properties: { + selections: { + path?: string; + literalArray?: string[]; + }; + options?: { + label: StringValue; + value: string; + }[]; + maxAllowedSelections?: number; + } +} + +export const multipleChoiceApi: ComponentApi<'MultipleChoice', MultipleChoiceNode> = { + name: 'MultipleChoice', + + resolveProperties(unresolved, resolver) { + if (!unresolved || typeof unresolved.selections !== 'object' || !Array.isArray(unresolved.options)) { + throw new Error('Invalid properties for MultipleChoice: missing selections or options.'); + } + + return { + properties: { + selections: resolver(unresolved.selections) as { path?: string; literalArray?: string[] }, + options: (unresolved.options as { label: StringValue, value: string }[]).map(option => ({ + label: resolver(option.label) as StringValue, + value: option.value, + })), + maxAllowedSelections: unresolved.maxAllowedSelections as number, + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/row.ts b/renderers/lit/src/0.8/core/standard_catalog_api/row.ts new file mode 100644 index 00000000..03a7e31e --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/row.ts @@ -0,0 +1,53 @@ +/* + 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 { + ComponentApi, + AnyResolvedNode, + BaseResolvedNode, +} from '../types/types.js'; + +export interface RowNode extends BaseResolvedNode<'Row'> { + properties: { + children: AnyResolvedNode[]; + distribution?: + | "start" + | "center" + | "end" + | "spaceBetween" + | "spaceAround" + | "spaceEvenly"; + alignment?: "start" | "center" | "end" | "stretch"; + } +} + +export const rowApi: ComponentApi<'Row', RowNode> = { + name: 'Row', + + resolveProperties(unresolved, resolver) { + if (!unresolved || !unresolved.children) { + throw new Error('Invalid properties for Row: missing children.'); + } + + return { + properties: { + children: resolver(unresolved.children) as AnyResolvedNode[], + distribution: unresolved.distribution as RowNode['properties']['distribution'], + alignment: unresolved.alignment as RowNode['properties']['alignment'], + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/slider.ts b/renderers/lit/src/0.8/core/standard_catalog_api/slider.ts new file mode 100644 index 00000000..8f67383c --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/slider.ts @@ -0,0 +1,47 @@ +/* + 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 { + ComponentApi, + BaseResolvedNode, +} from '../types/types.js'; +import { NumberValue } from '../types/primitives.js'; + +export interface SliderNode extends BaseResolvedNode<'Slider'> { + properties: { + value: NumberValue; + minValue?: number; + maxValue?: number; + } +} + +export const sliderApi: ComponentApi<'Slider', SliderNode> = { + name: 'Slider', + + resolveProperties(unresolved, resolver) { + if (!unresolved || typeof unresolved.value !== 'object') { + throw new Error('Invalid properties for Slider: missing value.'); + } + + return { + properties: { + value: resolver(unresolved.value) as NumberValue, + minValue: unresolved.minValue as number, + maxValue: unresolved.maxValue as number, + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/standard_catalog.ts b/renderers/lit/src/0.8/core/standard_catalog_api/standard_catalog.ts new file mode 100644 index 00000000..dc4483cf --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/standard_catalog.ts @@ -0,0 +1,56 @@ +/* + 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 { CatalogApi } from '../types/types.js'; +import { audioPlayerApi } from './audio_player.js'; +import { buttonApi } from './button.js'; +import { cardApi } from './card.js'; +import { checkboxApi } from './checkbox.js'; +import { columnApi } from './column.js'; +import { dateTimeInputApi } from './datetime_input.js'; +import { dividerApi } from './divider.js'; +import { iconApi } from './icon.js'; +import { imageApi } from './image.js'; +import { listApi } from './list.js'; +import { modalApi } from './modal.js'; +import { multipleChoiceApi } from './multiple_choice.js'; +import { rowApi } from './row.js'; +import { sliderApi } from './slider.js'; +import { tabsApi } from './tabs.js'; +import { textFieldApi } from './text_field.js'; +import { textApi } from './text.js'; +import { videoApi } from './video.js'; + +export const standardCatalogApi = new CatalogApi([ + audioPlayerApi, + buttonApi, + cardApi, + checkboxApi, + columnApi, + dateTimeInputApi, + dividerApi, + iconApi, + imageApi, + listApi, + modalApi, + multipleChoiceApi, + rowApi, + sliderApi, + tabsApi, + textFieldApi, + textApi, + videoApi, +]); diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/tabs.ts b/renderers/lit/src/0.8/core/standard_catalog_api/tabs.ts new file mode 100644 index 00000000..6def8324 --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/tabs.ts @@ -0,0 +1,49 @@ +/* + 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 { + ComponentApi, + AnyResolvedNode, + BaseResolvedNode, +} from '../types/types.js'; +import { StringValue } from '../types/primitives.js'; + +export interface ResolvedTabItem { + title: StringValue; + child: AnyResolvedNode; +} + +export interface TabsNode extends BaseResolvedNode<'Tabs'> { + properties: { + tabItems: ResolvedTabItem[]; + } +} + +export const tabsApi: ComponentApi<'Tabs', TabsNode> = { + name: 'Tabs', + + resolveProperties(unresolved, resolver) { + if (!unresolved || !Array.isArray(unresolved.tabItems)) { + throw new Error('Invalid properties for Tabs: missing tabItems.'); + } + + return { + properties: { + tabItems: resolver(unresolved.tabItems) as ResolvedTabItem[], + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/text.ts b/renderers/lit/src/0.8/core/standard_catalog_api/text.ts new file mode 100644 index 00000000..852b6505 --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/text.ts @@ -0,0 +1,45 @@ +/* + 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 { + ComponentApi, + BaseResolvedNode, +} from '../types/types.js'; +import { StringValue } from '../types/primitives.js'; + +export interface TextNode extends BaseResolvedNode<'Text'> { + properties: { + text: StringValue; + usageHint: "h1" | "h2" | "h3" | "h4" | "h5" | "caption" | "body"; + } +} + +export const textApi: ComponentApi<'Text', TextNode> = { + name: 'Text', + + resolveProperties(unresolved, resolver) { + if (!unresolved || typeof unresolved.text !== 'object') { + throw new Error('Invalid properties for Text: missing text.'); + } + + return { + properties: { + text: resolver(unresolved.text) as StringValue, + usageHint: unresolved.usageHint as TextNode['properties']['usageHint'], + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/text_field.ts b/renderers/lit/src/0.8/core/standard_catalog_api/text_field.ts new file mode 100644 index 00000000..a64d5c5a --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/text_field.ts @@ -0,0 +1,49 @@ +/* + 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 { + ComponentApi, + BaseResolvedNode, +} from '../types/types.js'; +import { StringValue } from '../types/primitives.js'; + +export interface TextFieldNode extends BaseResolvedNode<'TextField'> { + properties: { + text?: StringValue; + label: StringValue; + type?: "shortText" | "number" | "date" | "longText"; + validationRegexp?: string; + } +} + +export const textFieldApi: ComponentApi<'TextField', TextFieldNode> = { + name: 'TextField', + + resolveProperties(unresolved, resolver) { + if (!unresolved || typeof unresolved.label !== 'object') { + throw new Error('Invalid properties for TextField: missing label.'); + } + + return { + properties: { + label: resolver(unresolved.label) as StringValue, + text: resolver(unresolved.text) as StringValue, + type: unresolved.type as TextFieldNode['properties']['type'], + validationRegexp: unresolved.validationRegexp as string, + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/core/standard_catalog_api/video.ts b/renderers/lit/src/0.8/core/standard_catalog_api/video.ts new file mode 100644 index 00000000..a7759881 --- /dev/null +++ b/renderers/lit/src/0.8/core/standard_catalog_api/video.ts @@ -0,0 +1,43 @@ +/* + 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 { + ComponentApi, + BaseResolvedNode, +} from '../types/types.js'; +import { StringValue } from '../types/primitives.js'; + +export interface VideoNode extends BaseResolvedNode<'Video'> { + properties: { + url: StringValue; + } +} + +export const videoApi: ComponentApi<'Video', VideoNode> = { + name: 'Video', + + resolveProperties(unresolved, resolver) { + if (!unresolved || typeof unresolved.url !== 'object') { + throw new Error('Invalid properties for Video: missing url.'); + } + + return { + properties: { + url: resolver(unresolved.url) as StringValue, + } + }; + }, +}; \ No newline at end of file diff --git a/renderers/lit/src/0.8/types/client-event.ts b/renderers/lit/src/0.8/core/types/client-event.ts similarity index 100% rename from renderers/lit/src/0.8/types/client-event.ts rename to renderers/lit/src/0.8/core/types/client-event.ts diff --git a/renderers/lit/src/0.8/types/colors.ts b/renderers/lit/src/0.8/core/types/colors.ts similarity index 100% rename from renderers/lit/src/0.8/types/colors.ts rename to renderers/lit/src/0.8/core/types/colors.ts diff --git a/renderers/lit/src/0.8/types/primitives.ts b/renderers/lit/src/0.8/core/types/primitives.ts similarity index 100% rename from renderers/lit/src/0.8/types/primitives.ts rename to renderers/lit/src/0.8/core/types/primitives.ts diff --git a/renderers/lit/src/0.8/types/types.ts b/renderers/lit/src/0.8/core/types/types.ts similarity index 61% rename from renderers/lit/src/0.8/types/types.ts rename to renderers/lit/src/0.8/core/types/types.ts index 1e1f6686..80d938dc 100644 --- a/renderers/lit/src/0.8/types/types.ts +++ b/renderers/lit/src/0.8/core/types/types.ts @@ -14,27 +14,166 @@ limitations under the License. */ +import { StringValue, NumberValue, BooleanValue } from "./primitives.js"; export { type ClientToServerMessage as A2UIClientEventMessage, type ClientCapabilitiesDynamic, + type UserAction, } from "./client-event.js"; -export { type Action } from "./components.js"; - -import { - AudioPlayer, - Button, - Checkbox, - DateTimeInput, - Divider, - Icon, - Image, - MultipleChoice, - Slider, - Text, - TextField, - Video, -} from "./components"; -import { StringValue } from "./primitives"; + +// --- Core Action Type --- + +export interface Action { + /** + * A unique name identifying the action (e.g., 'submitForm'). + */ + name: string; + /** + * A key-value map of data bindings to be resolved when the action is triggered. + */ + context?: { + key: string; + /** + * The dynamic value. Define EXACTLY ONE of the nested properties. + */ + value: { + /** + * A data binding reference to a location in the data model (e.g., '/user/name'). + */ + path?: string; + /** + * A fixed, hardcoded string value. + */ + literalString?: string; + literalNumber?: number; + literalBoolean?: boolean; + }; + }[]; +} + +// --- Core Node Types --- + +// Base interface for any resolved component node. +export interface BaseResolvedNode { + id: string; + type: TName; + weight: number | 'initial'; + dataContextPath?: string; + slotName?: string; +} + +// Union type for any possible resolved node. +// Now strictly generic to decouple from specific components. +export type AnyResolvedNode = BaseResolvedNode & { + properties: Record; +}; + +// Interface for a component's definition. +export interface ComponentApi< + TName extends string, + RNode extends BaseResolvedNode +> { + readonly name: TName; + + /** + * Resolves the raw properties from the A2UI message into the typed + * properties required by the final resolved node. This logic is specific + * to each component. + * @param unresolvedProperties The raw properties from the message. + * @param resolver A callback provided by the A2uiMessageProcessor to recursively + * resolve values (e.g., data bindings, child component IDs). + * @returns The resolved properties for the node. + */ + resolveProperties( + unresolvedProperties: Record, + resolver: (value: unknown) => unknown + ): Omit>; +} + +export type AnyComponentApi = ComponentApi; + +export class CatalogApi { + private readonly components: Map; + + constructor(components: AnyComponentApi[]) { + this.components = new Map(components.map(c => [c.name, c])); + } + + public get(componentName: string): AnyComponentApi | undefined { + return this.components.get(componentName); + } +} + +/** + * @template RNode The specific resolved node type this component can render. + * @template RenderOutput The output type of the rendering framework (e.g., TemplateResult for Lit, JSX.Element for React). + */ +export interface ComponentRenderer< + RNode extends AnyResolvedNode, + RenderOutput +> { + readonly componentName: RNode['type']; + + /** + * Renders the resolved component node. + * @param node The fully resolved, typed component node to render. + * @param renderChild A function provided by the framework renderer to + * recursively render child nodes. Container components MUST use this. + * @returns The framework-specific, renderable output. + */ + render( + node: RNode, + renderChild: (child: AnyResolvedNode) => RenderOutput | null + ): RenderOutput; +} +export type AnyComponentRenderer = ComponentRenderer; + +export class CatalogImplementation { + private readonly renderers: Map>; + + /** + * @param catalogApi The API definition for the catalog. + * @param renderers A list of framework-specific renderers. + */ + constructor(catalogApi: CatalogApi, renderers: AnyComponentRenderer[]) { + this.renderers = new Map(renderers.map(r => [r.componentName, r])); + + for (const api of (catalogApi as any)['components'].values()) { + if (!this.renderers.has(api.name)) { + throw new Error(`Missing renderer implementation for component: ${api.name}`); + } + } + } + + public getRenderer(componentName: string): AnyComponentRenderer | undefined { + return this.renderers.get(componentName); + } +} + +export class FrameworkRenderer { + protected readonly catalogImplementation: CatalogImplementation; + + constructor(catalogImplementation: CatalogImplementation) { + this.catalogImplementation = catalogImplementation; + } + + /** + * Renders a resolved node from the A2uiMessageProcessor into the final output. + * This is the entry point for rendering a component tree. + */ + public renderNode(node: AnyResolvedNode): RenderOutput | null { + const renderer = this.catalogImplementation.getRenderer(node.type); + if (!renderer) { + console.warn(`No renderer found for component type: ${node.type}`); + return null; + } + + // The `renderChild` function passed to the component renderer is a bound + // version of this same `renderNode` method, enabling recursion. + return renderer.render(node, this.renderNode.bind(this)); + } +} + export type MessageProcessor = { getSurfaces(): ReadonlyMap; @@ -47,13 +186,13 @@ export type MessageProcessor = { * own data context. */ getData( - node: AnyComponentNode, + node: AnyResolvedNode, relativePath: string, surfaceId: string ): DataValue | null; setData( - node: AnyComponentNode | null, + node: AnyResolvedNode | null, relativePath: string, value: DataValue, surfaceId: string @@ -194,32 +333,6 @@ export type Theme = { }; }; -/** - * Represents a user-initiated action, sent from the client to the server. - */ -export interface UserAction { - /** - * The name of the action, taken from the component's `action.action` - * property. - */ - actionName: string; - /** - * The `id` of the component that triggered the event. - */ - sourceComponentId: string; - /** - * An ISO 8601 timestamp of when the event occurred. - */ - timestamp: string; - /** - * A JSON object containing the key-value pairs from the component's - * `action.context`, after resolving all data bindings. - */ - context?: { - [k: string]: unknown; - }; -} - /** A recursive type for any valid JSON-like value in the data model. */ export type DataValue = | string @@ -307,7 +420,7 @@ export type ResolvedValue = | number | boolean | null - | AnyComponentNode + | AnyResolvedNode | ResolvedMap | ResolvedArray; @@ -317,204 +430,13 @@ 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. - */ -interface BaseComponentNode { - id: string; - weight?: number; - 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 { +export interface CustomNode extends BaseResolvedNode { 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. - */ -export type AnyComponentNode = - | TextNode - | IconNode - | ImageNode - | VideoNode - | AudioPlayerNode - | RowNode - | ColumnNode - | ListNode - | CardNode - | TabsNode - | DividerNode - | ModalNode - | ButtonNode - | CheckboxNode - | TextFieldNode - | DateTimeInputNode - | MultipleChoiceNode - | SliderNode - | CustomNode; - -// These components do not contain other components can reuse their -// original interfaces. -export type ResolvedText = Text; -export type ResolvedIcon = Icon; -export type ResolvedImage = Image; -export type ResolvedVideo = Video; -export type ResolvedAudioPlayer = AudioPlayer; -export type ResolvedDivider = Divider; -export type ResolvedCheckbox = Checkbox; -export type ResolvedTextField = TextField; -export type ResolvedDateTimeInput = DateTimeInput; -export type ResolvedMultipleChoice = MultipleChoice; -export type ResolvedSlider = Slider; - -export interface ResolvedRow { - children: AnyComponentNode[]; - distribution?: - | "start" - | "center" - | "end" - | "spaceBetween" - | "spaceAround" - | "spaceEvenly"; - alignment?: "start" | "center" | "end" | "stretch"; -} - -export interface ResolvedColumn { - children: AnyComponentNode[]; - distribution?: - | "start" - | "center" - | "end" - | "spaceBetween" - | "spaceAround" - | "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 type AnyComponentNode = AnyResolvedNode; export interface CustomNodeProperties { [k: string]: ResolvedValue; @@ -525,8 +447,8 @@ export type SurfaceID = string; /** The complete state of a single UI surface. */ export interface Surface { rootComponentId: string | null; - componentTree: AnyComponentNode | null; + componentTree: AnyResolvedNode | null; dataModel: DataMap; components: Map; styles: Record; -} +} \ No newline at end of file diff --git a/renderers/lit/src/0.8/data/guards.ts b/renderers/lit/src/0.8/data/guards.ts deleted file mode 100644 index 624e8f5e..00000000 --- a/renderers/lit/src/0.8/data/guards.ts +++ /dev/null @@ -1,236 +0,0 @@ -/* - 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 { BooleanValue, NumberValue, StringValue } from "../types/primitives"; -import { - AnyComponentNode, - ComponentArrayReference, - ResolvedAudioPlayer, - ResolvedButton, - ResolvedCard, - ResolvedCheckbox, - ResolvedColumn, - ResolvedDateTimeInput, - ResolvedDivider, - ResolvedIcon, - ResolvedImage, - ResolvedList, - ResolvedModal, - ResolvedMultipleChoice, - ResolvedRow, - ResolvedSlider, - ResolvedTabItem, - ResolvedTabs, - ResolvedText, - ResolvedTextField, - ResolvedVideo, - ValueMap, -} from "../types/types"; - -export function isValueMap(value: unknown): value is ValueMap { - return isObject(value) && "key" in value; -} - -export function isPath(key: string, value: unknown): value is string { - return key === "path" && typeof value === "string"; -} - -export function isObject(value: unknown): value is Record { - return typeof value === "object" && value !== null && !Array.isArray(value); -} - -export function isComponentArrayReference( - value: unknown -): value is ComponentArrayReference { - if (!isObject(value)) return false; - return "explicitList" in value || "template" in value; -} - -function isStringValue(value: unknown): value is StringValue { - return ( - isObject(value) && - ("path" in value || - ("literal" in value && typeof value.literal === "string") || - "literalString" in value) - ); -} - -function isNumberValue(value: unknown): value is NumberValue { - return ( - isObject(value) && - ("path" in value || - ("literal" in value && typeof value.literal === "number") || - "literalNumber" in value) - ); -} - -function isBooleanValue(value: unknown): value is BooleanValue { - return ( - isObject(value) && - ("path" in value || - ("literal" in value && typeof value.literal === "boolean") || - "literalBoolean" in value) - ); -} - -function isAnyComponentNode(value: unknown): value is AnyComponentNode { - if (!isObject(value)) return false; - const hasBaseKeys = "id" in value && "type" in value && "properties" in value; - if (!hasBaseKeys) return false; - - return true; -} - -export function isResolvedAudioPlayer( - props: unknown -): props is ResolvedAudioPlayer { - return isObject(props) && "url" in props && isStringValue(props.url); -} - -export function isResolvedButton(props: unknown): props is ResolvedButton { - return ( - isObject(props) && - "child" in props && - isAnyComponentNode(props.child) && - "action" in props - ); -} - -export function isResolvedCard(props: unknown): props is ResolvedCard { - if (!isObject(props)) return false; - if (!("child" in props)) { - if (!("children" in props)) { - return false; - } else { - return ( - Array.isArray(props.children) && - props.children.every(isAnyComponentNode) - ); - } - } - - return isAnyComponentNode(props.child); -} - -export function isResolvedCheckbox(props: unknown): props is ResolvedCheckbox { - return ( - isObject(props) && - "label" in props && - isStringValue(props.label) && - "value" in props && - isBooleanValue(props.value) - ); -} - -export function isResolvedColumn(props: unknown): props is ResolvedColumn { - return ( - isObject(props) && - "children" in props && - Array.isArray(props.children) && - props.children.every(isAnyComponentNode) - ); -} - -export function isResolvedDateTimeInput( - props: unknown -): props is ResolvedDateTimeInput { - return isObject(props) && "value" in props && isStringValue(props.value); -} - -export function isResolvedDivider(props: unknown): props is ResolvedDivider { - // Dividers can have all optional properties, so just checking if - // it's an object is enough. - return isObject(props); -} - -export function isResolvedImage(props: unknown): props is ResolvedImage { - return isObject(props) && "url" in props && isStringValue(props.url); -} - -export function isResolvedIcon(props: unknown): props is ResolvedIcon { - return isObject(props) && "name" in props && isStringValue(props.name); -} - -export function isResolvedList(props: unknown): props is ResolvedList { - return ( - isObject(props) && - "children" in props && - Array.isArray(props.children) && - props.children.every(isAnyComponentNode) - ); -} - -export function isResolvedModal(props: unknown): props is ResolvedModal { - return ( - isObject(props) && - "entryPointChild" in props && - isAnyComponentNode(props.entryPointChild) && - "contentChild" in props && - isAnyComponentNode(props.contentChild) - ); -} - -export function isResolvedMultipleChoice( - props: unknown -): props is ResolvedMultipleChoice { - return isObject(props) && "selections" in props; -} - -export function isResolvedRow(props: unknown): props is ResolvedRow { - return ( - isObject(props) && - "children" in props && - Array.isArray(props.children) && - props.children.every(isAnyComponentNode) - ); -} - -export function isResolvedSlider(props: unknown): props is ResolvedSlider { - return isObject(props) && "value" in props && isNumberValue(props.value); -} - -function isResolvedTabItem(item: unknown): item is ResolvedTabItem { - return ( - isObject(item) && - "title" in item && - isStringValue(item.title) && - "child" in item && - isAnyComponentNode(item.child) - ); -} - -export function isResolvedTabs(props: unknown): props is ResolvedTabs { - return ( - isObject(props) && - "tabItems" in props && - Array.isArray(props.tabItems) && - props.tabItems.every(isResolvedTabItem) - ); -} - -export function isResolvedText(props: unknown): props is ResolvedText { - return isObject(props) && "text" in props && isStringValue(props.text); -} - -export function isResolvedTextField( - props: unknown -): props is ResolvedTextField { - return isObject(props) && "label" in props && isStringValue(props.label); -} - -export function isResolvedVideo(props: unknown): props is ResolvedVideo { - return isObject(props) && "url" in props && isStringValue(props.url); -} diff --git a/renderers/lit/src/0.8/events/a2ui.ts b/renderers/lit/src/0.8/events/a2ui.ts index 88a41ea7..7e094f26 100644 --- a/renderers/lit/src/0.8/events/a2ui.ts +++ b/renderers/lit/src/0.8/events/a2ui.ts @@ -14,8 +14,8 @@ limitations under the License. */ -import { Action } from "../types/components.js"; -import { AnyComponentNode } from "../types/types.js"; +import { Action } from "../core/types/types.js"; +import { AnyComponentNode } from "../core/types/types.js"; import { BaseEventDetail } from "./base.js"; type Namespace = "a2ui"; diff --git a/renderers/lit/src/0.8/index.ts b/renderers/lit/src/0.8/index.ts index ab41af4b..71f1e0ed 100644 --- a/renderers/lit/src/0.8/index.ts +++ b/renderers/lit/src/0.8/index.ts @@ -15,4 +15,6 @@ */ export * from "./core.js"; -export * as UI from "./ui/ui.js"; +export * as UI from "./lit/components/ui.js"; +export { LitRenderer } from "./lit/lit_renderer.js"; +export { standardLitCatalogImplementation } from "./lit/standard_catalog_implementation/standard_catalog_lit.js"; \ No newline at end of file diff --git a/renderers/lit/src/0.8/ui/audio.ts b/renderers/lit/src/0.8/lit/components/audio.ts similarity index 72% rename from renderers/lit/src/0.8/ui/audio.ts rename to renderers/lit/src/0.8/lit/components/audio.ts index 3465687b..0b5703c0 100644 --- a/renderers/lit/src/0.8/ui/audio.ts +++ b/renderers/lit/src/0.8/lit/components/audio.ts @@ -15,18 +15,16 @@ */ import { html, css, nothing } from "lit"; -import { customElement, property } from "lit/decorators.js"; +import { customElement } from "lit/decorators.js"; import { Root } from "./root.js"; -import { StringValue } from "../types/primitives.js"; import { classMap } from "lit/directives/class-map.js"; -import { A2uiMessageProcessor } from "../data/model-processor.js"; +import { A2uiMessageProcessor } from "../../core/a2ui_message_processor.js"; import { styleMap } from "lit/directives/style-map.js"; import { structuralStyles } from "./styles.js"; +import { AudioPlayerNode } from "../../core/standard_catalog_api/audio_player.js"; @customElement("a2ui-audioplayer") -export class Audio extends Root { - @property() - accessor url: StringValue | null = null; +export class Audio extends Root { static styles = [ structuralStyles, @@ -50,23 +48,24 @@ export class Audio extends Root { ]; #renderAudio() { - if (!this.url) { + const url = this.node.properties.url; + if (!url) { return nothing; } - if (this.url && typeof this.url === "object") { - if ("literalString" in this.url) { - return html``; } )} `; diff --git a/samples/client/lit/contact/middleware/a2a.ts b/samples/client/lit/contact/middleware/a2a.ts index 74813d93..99e583b5 100644 --- a/samples/client/lit/contact/middleware/a2a.ts +++ b/samples/client/lit/contact/middleware/a2a.ts @@ -52,7 +52,7 @@ const createOrGetClient = async () => { if (!client) { // Create a client pointing to the agent's Agent Card URL. client = await A2AClient.fromCardUrl( - "http://localhost:10002/.well-known/agent-card.json", + "http://localhost:10003/.well-known/agent-card.json", { fetchImpl: fetchWithCustomHeader } ); } diff --git a/samples/client/lit/contact/ui/custom-components/org-chart.ts b/samples/client/lit/contact/ui/custom-components/org-chart.ts index 4895ca7d..2ad28b7d 100644 --- a/samples/client/lit/contact/ui/custom-components/org-chart.ts +++ b/samples/client/lit/contact/ui/custom-components/org-chart.ts @@ -16,8 +16,8 @@ import { Root } from '@a2ui/lit/ui'; import { v0_8 } from '@a2ui/lit'; -import { html, css, TemplateResult } from 'lit'; -import { customElement, property } from 'lit/decorators.js'; +import { html, css } from 'lit'; +import { customElement } from 'lit/decorators.js'; import { map } from 'lit/directives/map.js'; // Use aliases for convenience @@ -31,9 +31,6 @@ export interface OrgChartNode { @customElement('org-chart') export class OrgChart extends Root { - @property({ type: Array }) accessor chain: OrgChartNode[] = []; - @property({ type: Object }) accessor action: Action | null = null; - static styles = [ ...Root.styles, css` @@ -103,14 +100,15 @@ export class OrgChart extends Root { `]; render() { - if (!this.chain || this.chain.length === 0) { + const chain = (this.node.properties.chain as OrgChartNode[]) || []; + if (chain.length === 0) { return html`
No hierarchy data
`; } return html`
- ${map(this.chain, (node, index) => { - const isLast = index === this.chain.length - 1; + ${map(chain, (node, index) => { + const isLast = index === chain.length - 1; return html`