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