Update Documentation for Frontend API and Event Handling Enhancements

- Expanded the README to include CUI integration details in the Frontend API section, clarifying communication methods with the CUI host.
- Added new examples for frontend scripting styles, including Direct and Component styles, to improve user understanding of event handling and form submissions.
- Updated event handling documentation to reflect changes in handler signatures and data structures, enhancing clarity on event data usage.
- Revised component documentation to standardize method naming conventions and improve consistency across examples.
This commit is contained in:
Max 2026-01-03 11:31:15 +08:00
parent e6778e83fd
commit aa01ea216c
5 changed files with 485 additions and 279 deletions

View file

@ -75,7 +75,7 @@ yao sui watch agent
- [Data Binding](docs/data-binding.md) - Built-in variables and functions
- [Event Handling](docs/event-handling.md) - Event binding and state management
- [Internationalization](docs/i18n.md) - Translation and localization
- [Frontend API](docs/frontend-api.md) - Component query, backend calls, render API
- [Frontend API](docs/frontend-api.md) - Component query, backend calls, render API, CUI integration
- [Agent SUI](docs/agent-sui.md) - AI Agent application setup
## Agent SUI

View file

@ -237,16 +237,94 @@ Use standard SUI template syntax:
## Frontend Script
Frontend scripts can be written in two styles:
### Direct Style (Simple Pages)
```typescript
// Runs immediately when script loads
document.addEventListener("DOMContentLoaded", () => {
const form = document.querySelector("#myForm") as HTMLFormElement;
form.addEventListener("submit", async (e) => {
e.preventDefault();
// Handle submission
});
});
// Smooth scrolling
document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
anchor.addEventListener("click", function (e) {
e.preventDefault();
const target = document.querySelector(this.getAttribute("href"));
target?.scrollIntoView({ behavior: "smooth" });
});
});
```
### Component Style (Interactive Pages)
**`/assistants/demo/pages/index/index.ts`**:
```typescript
function index(component: HTMLElement) {
this.root = component;
this.store = new __sui_store(component);
import { $Backend, Component, EventData } from "@yao/sui";
this.handleClick = async (event: Event) => {
const data = await this.backend.ApiGetData();
console.log(data);
};
}
const self = this as Component;
// Event handler bound to s:on-click="HandleClick"
self.HandleClick = async (event: Event, data: EventData) => {
const result = await $Backend().Call("ApiGetData", data.id);
console.log(result);
};
// Form submission
self.HandleSubmit = async (event: Event) => {
event.preventDefault();
const form = event.target as HTMLFormElement;
const formData = new FormData(form);
await $Backend().Call("ApiSubmit", Object.fromEntries(formData));
};
```
## CUI Integration
When Agent SUI pages are embedded in CUI via `/web/` routes, they can communicate with the CUI host.
### Receiving Context
```typescript
window.addEventListener("message", (e) => {
if (e.origin !== window.location.origin) return;
if (e.data.type === "setup") {
const { theme, locale } = e.data.message;
document.documentElement.setAttribute("data-theme", theme);
}
});
```
### Sending Actions
```typescript
// Helper function
const sendAction = (name: string, payload?: any) => {
window.parent.postMessage(
{ type: "action", message: { name, payload } },
window.location.origin
);
};
// Show notification
sendAction("notify.success", { message: "Done!" });
// Navigate
sendAction("navigate", {
route: "/agents/demo/detail",
title: "Details",
});
// Close sidebar
sendAction("event.emit", { key: "app/closeSidebar", value: {} });
```
See [Frontend API - CUI Integration](frontend-api.md#cui-integration) for complete documentation.

View file

@ -203,75 +203,68 @@ Use `<slot name="xxx">` for multiple content areas:
### Structure
```typescript
function componentName(component: HTMLElement) {
// Root element
this.root = component;
import { $Backend, Component, EventData } from "@yao/sui";
// Data store (data-* attributes)
this.store = new __sui_store(component);
const self = this as Component;
// Props (passed attributes)
this.props = new __sui_props(component);
// self.root - Root element (HTMLElement)
// self.store - Data store (data-* attributes)
// self.props - Props (passed attributes)
// self.state - State management
// State management
this.state = new __sui_state(this);
// State watchers
self.watch = {
propertyName: (value: any, state: any) => {
// React to state changes
},
};
// Backend API
this.backend = {
ApiMethod: async (...args) => {
/* ... */
},
};
// State watchers
this.watch = {
propertyName: (value, state) => {
// React to state changes
},
};
// Methods
this.handleClick = (event, data, context) => {
// Handle events
};
}
// Event handlers (bound to s:on-click="HandleClick")
self.HandleClick = async (event: Event, data: EventData) => {
const result = await $Backend().Call("ApiMethod", data.id);
// Handle result
};
```
### Store API
```typescript
import { Component } from "@yao/sui";
const self = this as Component;
// String data
this.store.Get("key");
this.store.Set("key", "value");
self.store.Get("key");
self.store.Set("key", "value");
// JSON data
this.store.GetJSON("items");
this.store.SetJSON("items", [{ id: 1 }]);
self.store.GetJSON("items");
self.store.SetJSON("items", [{ id: 1 }]);
// Component data (from BeforeRender)
this.store.GetData();
self.store.GetData();
```
### Props API
```typescript
// Get single prop
const value = this.props.Get("propName");
const value = self.props.Get("propName");
// Get all props
const props = this.props.List();
const props = self.props.List();
```
### State API
```typescript
// Set state (triggers watchers)
this.state.Set("count", 10);
self.state.Set("count", 10);
// Watch state changes
this.watch = {
count: (value, state) => {
this.root.querySelector(".count").textContent = value;
self.watch = {
count: (value: number, state: any) => {
self.root.querySelector(".count")!.textContent = String(value);
// state.stopPropagation(); // Prevent bubbling to parent
},
};

View file

@ -76,23 +76,21 @@ Use `s:json-*` to pass complex data:
### Handler Signature
```typescript
function Page(component: HTMLElement) {
this.root = component;
import { $Backend, Component, EventData } from "@yao/sui";
this.handleClick = (event: Event, data: any, context: EventContext) => {
// event - The DOM event
// data - Combined data from s:data-* and s:json-*
// context - Event context with element references
};
}
const self = this as Component;
self.HandleClick = (event: Event, data: EventData) => {
// event - The DOM event
// data - Combined data from s:data-* and s:json-*
};
```
### EventContext
### EventData
```typescript
interface EventContext {
rootElement: HTMLElement; // Component root element
targetElement: HTMLElement; // Element that triggered the event
interface EventData {
[key: string]: any; // Data from s:data-* and s:json-* attributes
}
```
@ -103,7 +101,7 @@ interface EventContext {
<div s:for="{{ items }}" s:for-item="item">
<span>{{ item.name }}</span>
<button
s:on-click="deleteItem"
s:on-click="DeleteItem"
s:data-id="{{ item.id }}"
s:json-item="{{ item }}"
>
@ -114,19 +112,19 @@ interface EventContext {
```
```typescript
function ItemList(component: HTMLElement) {
this.root = component;
import { $Backend, Component, EventData } from "@yao/sui";
this.deleteItem = async (event: Event, data: any, context: EventContext) => {
const id = data.id; // String from s:data-id
const item = data.item; // Object from s:json-item
const self = this as Component;
if (confirm(`Delete ${item.name}?`)) {
await this.backend.ApiDeleteItem(id);
context.targetElement.closest(".item").remove();
}
};
}
self.DeleteItem = async (event: Event, data: EventData) => {
const id = data.id; // String from s:data-id
const item = data.item; // Object from s:json-item
if (confirm(`Delete ${item.name}?`)) {
await $Backend().Call("ApiDeleteItem", id);
(event.target as HTMLElement).closest(".item")?.remove();
}
};
```
## State Management
@ -134,13 +132,12 @@ function ItemList(component: HTMLElement) {
### State Object
```typescript
function Counter(component: HTMLElement) {
this.root = component;
this.state = new __sui_state(this);
import { Component } from "@yao/sui";
// Initial state
this.state.Set("count", 0);
}
const self = this as Component;
// Initial state
self.state.Set("count", 0);
```
### State Watchers
@ -148,26 +145,25 @@ function Counter(component: HTMLElement) {
React to state changes with watchers:
```typescript
function Counter(component: HTMLElement) {
this.root = component;
this.state = new __sui_state(this);
import { Component } from "@yao/sui";
// Define watchers
this.watch = {
count: (value: number, state: State) => {
this.root.querySelector(".count").textContent = value;
},
const self = this as Component;
items: (value: any[], state: State) => {
this.renderItems(value);
},
};
// Define watchers
self.watch = {
count: (value: number) => {
self.root.querySelector(".count")!.textContent = String(value);
},
this.increment = () => {
const count = this.state.Get("count") || 0;
this.state.Set("count", count + 1); // Triggers watcher
};
}
items: (value: any[]) => {
renderItems(value);
},
};
self.Increment = () => {
const count = self.state.Get("count") || 0;
self.state.Set("count", count + 1); // Triggers watcher
};
```
### Stop Propagation
@ -175,10 +171,10 @@ function Counter(component: HTMLElement) {
Prevent state changes from bubbling to parent:
```typescript
this.watch = {
localState: (value: any, state: State) => {
self.watch = {
localState: (value: any, state: any) => {
// Handle locally
this.updateUI(value);
updateUI(value);
// Stop propagation to parent components
state.stopPropagation();
@ -193,18 +189,17 @@ Store manages `data-*` attributes on the component:
### Basic Usage
```typescript
function Card(component: HTMLElement) {
this.root = component;
this.store = new __sui_store(component);
import { Component } from "@yao/sui";
// Get/Set string values
const id = this.store.Get("id");
this.store.Set("id", "123");
const self = this as Component;
// Get/Set JSON values
const items = this.store.GetJSON("items");
this.store.SetJSON("items", [{ id: 1 }, { id: 2 }]);
}
// Get/Set string values
const id = self.store.Get("id");
self.store.Set("id", "123");
// Get/Set JSON values
const items = self.store.GetJSON("items");
self.store.SetJSON("items", [{ id: 1 }, { id: 2 }]);
```
### Component Data
@ -213,7 +208,7 @@ Get data from BeforeRender:
```typescript
// Backend returns: { user: { name: "John" }, settings: {...} }
const data = this.store.GetData();
const data = self.store.GetData();
console.log(data.user.name); // "John"
```
@ -222,30 +217,30 @@ console.log(data.user.name); // "John"
### Emit Events
```typescript
function ItemCard(component: HTMLElement) {
this.root = component;
import { Component } from "@yao/sui";
this.selectItem = () => {
const item = this.store.GetJSON("item");
const self = this as Component;
// Emit custom event
this.emit("item:selected", { item });
};
}
self.SelectItem = () => {
const item = self.store.GetJSON("item");
// Emit custom event
self.emit("item:selected", { item });
};
```
### Listen to Events
```typescript
function ItemList(component: HTMLElement) {
this.root = component;
import { Component } from "@yao/sui";
// Listen to child events
this.root.addEventListener("item:selected", (e: CustomEvent) => {
const { item } = e.detail;
console.log("Selected:", item);
});
}
const self = this as Component;
// Listen to child events
self.root.addEventListener("item:selected", (e: CustomEvent) => {
const { item } = e.detail;
console.log("Selected:", item);
});
```
### State Change Events
@ -253,14 +248,14 @@ function ItemList(component: HTMLElement) {
Parent components can listen to state changes:
```typescript
function Parent(component: HTMLElement) {
this.root = component;
import { Component } from "@yao/sui";
this.root.addEventListener("state:change", (e: CustomEvent) => {
const { key, value, target } = e.detail;
console.log(`State ${key} changed to ${value} in`, target);
});
}
const self = this as Component;
self.root.addEventListener("state:change", (e: CustomEvent) => {
const { key, value, target } = e.detail;
console.log(`State ${key} changed to ${value} in`, target);
});
```
## Form Handling
@ -268,7 +263,7 @@ function Parent(component: HTMLElement) {
### Form Submit
```html
<form s:on-submit="handleSubmit">
<form s:on-submit="HandleSubmit">
<input name="email" type="email" required />
<input name="password" type="password" required />
<button type="submit">Login</button>
@ -276,77 +271,77 @@ function Parent(component: HTMLElement) {
```
```typescript
function LoginForm(component: HTMLElement) {
this.root = component;
import { $Backend, Component } from "@yao/sui";
this.handleSubmit = async (event: Event) => {
event.preventDefault();
const self = this as Component;
const form = event.target as HTMLFormElement;
const formData = new FormData(form);
self.HandleSubmit = async (event: Event) => {
event.preventDefault();
const email = formData.get("email");
const password = formData.get("password");
const form = event.target as HTMLFormElement;
const formData = new FormData(form);
try {
await this.backend.ApiLogin(email, password);
window.location.href = "/dashboard";
} catch (error) {
alert("Login failed");
}
};
}
const email = formData.get("email");
const password = formData.get("password");
try {
await $Backend().Call("ApiLogin", email, password);
window.location.href = "/dashboard";
} catch (error) {
alert("Login failed");
}
};
```
### Input Binding
```html
<input type="text" s:on-input="handleInput" s:data-field="name" />
<input type="text" s:on-input="HandleInput" s:data-field="name" />
```
```typescript
function Form(component: HTMLElement) {
this.root = component;
this.formData = {};
import { Component, EventData } from "@yao/sui";
this.handleInput = (event: Event, data: any) => {
const input = event.target as HTMLInputElement;
this.formData[data.field] = input.value;
};
}
const self = this as Component;
const formData: Record<string, string> = {};
self.HandleInput = (event: Event, data: EventData) => {
const input = event.target as HTMLInputElement;
formData[data.field] = input.value;
};
```
## Keyboard Events
```html
<input s:on-keydown="handleKeydown" s:on-keyup="handleKeyup" />
<input s:on-keydown="HandleKeydown" s:on-keyup="HandleKeyup" />
```
```typescript
function Search(component: HTMLElement) {
this.root = component;
import { Component } from "@yao/sui";
this.handleKeydown = (event: KeyboardEvent) => {
if (event.key === "Enter") {
this.search();
}
const self = this as Component;
if (event.key === "Escape") {
this.clear();
}
};
}
self.HandleKeydown = (event: KeyboardEvent) => {
if (event.key === "Enter") {
search();
}
if (event.key === "Escape") {
clear();
}
};
```
## Complete Example
```html
<div class="todo-app">
<form s:on-submit="addTodo">
<form s:on-submit="AddTodo">
<input
name="title"
placeholder="Add todo..."
s:on-keydown="handleKeydown"
s:on-keydown="HandleKeydown"
/>
<button type="submit">Add</button>
</form>
@ -355,53 +350,51 @@ function Search(component: HTMLElement) {
<li s:for="{{ todos }}" s:for-item="todo">
<input
type="checkbox"
s:on-change="toggleTodo"
s:on-change="ToggleTodo"
s:data-id="{{ todo.id }}"
s:attr-checked="{{ todo.completed }}"
/>
<span class="{{ todo.completed ? 'completed' : '' }}">
{{ todo.title }}
</span>
<button s:on-click="deleteTodo" s:data-id="{{ todo.id }}">×</button>
<button s:on-click="DeleteTodo" s:data-id="{{ todo.id }}">×</button>
</li>
</ul>
</div>
```
```typescript
function TodoApp(component: HTMLElement) {
this.root = component;
this.state = new __sui_state(this);
this.store = new __sui_store(component);
import { $Backend, Component, EventData } from "@yao/sui";
this.watch = {
todos: (todos: any[]) => {
this.render("todoList", { todos });
},
};
const self = this as Component;
this.addTodo = async (event: Event) => {
event.preventDefault();
const form = event.target as HTMLFormElement;
const input = form.querySelector("input") as HTMLInputElement;
self.watch = {
todos: (todos: any[]) => {
self.render("todoList", { todos });
},
};
if (input.value.trim()) {
const todo = await this.backend.ApiAddTodo(input.value);
const todos = this.state.Get("todos") || [];
this.state.Set("todos", [...todos, todo]);
input.value = "";
}
};
self.AddTodo = async (event: Event) => {
event.preventDefault();
const form = event.target as HTMLFormElement;
const input = form.querySelector("input") as HTMLInputElement;
this.toggleTodo = async (event: Event, data: any) => {
const checkbox = event.target as HTMLInputElement;
await this.backend.ApiToggleTodo(data.id, checkbox.checked);
};
if (input.value.trim()) {
const todo = await $Backend().Call("ApiAddTodo", input.value);
const todos = self.state.Get("todos") || [];
self.state.Set("todos", [...todos, todo]);
input.value = "";
}
};
this.deleteTodo = async (event: Event, data: any) => {
await this.backend.ApiDeleteTodo(data.id);
const todos = this.state.Get("todos").filter((t) => t.id !== data.id);
this.state.Set("todos", todos);
};
}
self.ToggleTodo = async (event: Event, data: EventData) => {
const checkbox = event.target as HTMLInputElement;
await $Backend().Call("ApiToggleTodo", data.id, checkbox.checked);
};
self.DeleteTodo = async (event: Event, data: EventData) => {
await $Backend().Call("ApiDeleteTodo", data.id);
const todos = self.state.Get("todos").filter((t: any) => t.id !== data.id);
self.state.Set("todos", todos);
};
```

View file

@ -38,19 +38,15 @@ const items = component.queryAll(".item"); // Returns NodeList
## Backend Calls
### Via Component
### Via $Backend
```typescript
function Page(component: HTMLElement) {
this.root = component;
import { $Backend } from "@yao/sui";
this.loadData = async () => {
// Call backend API methods
const users = await this.backend.ApiGetUsers();
const user = await this.backend.ApiGetUser(123);
const result = await this.backend.ApiCreateUser("John", "john@example.com");
};
}
// Call backend API methods
const users = await $Backend().Call("ApiGetUsers");
const user = await $Backend().Call("ApiGetUser", 123);
const result = await $Backend().Call("ApiCreateUser", "John", "john@example.com");
```
### Direct Call
@ -80,22 +76,22 @@ Define render targets in HTML:
### Render Method
```typescript
function Page(component: HTMLElement) {
this.root = component;
import { $Backend, Component } from "@yao/sui";
this.refreshUsers = async () => {
const users = await this.backend.ApiGetUsers();
const self = this as Component;
// Render with data
await this.render("userList", { users });
};
}
self.RefreshUsers = async () => {
const users = await $Backend().Call("ApiGetUsers");
// Render with data
await self.render("userList", { users });
};
```
### Render Options
```typescript
await this.render("targetName", data, {
await self.render("targetName", data, {
replace: true, // Replace content (default: true)
showLoader: true, // Show loading indicator
withPageData: true, // Include page data in render context
@ -292,32 +288,32 @@ api.ClearTokens();
### Emit
```typescript
function Card(component: HTMLElement) {
this.root = component;
import { Component } from "@yao/sui";
this.select = () => {
this.emit("card:selected", { id: this.store.Get("id") });
};
}
const self = this as Component;
self.Select = () => {
self.emit("card:selected", { id: self.store.Get("id") });
};
```
### Listen
```typescript
function CardList(component: HTMLElement) {
this.root = component;
import { Component } from "@yao/sui";
this.root.addEventListener("card:selected", (e: CustomEvent) => {
console.log("Selected:", e.detail.id);
});
}
const self = this as Component;
self.root.addEventListener("card:selected", (e: CustomEvent) => {
console.log("Selected:", e.detail.id);
});
```
### State Change Events
```typescript
// Listen to child state changes
this.root.addEventListener("state:change", (e: CustomEvent) => {
self.root.addEventListener("state:change", (e: CustomEvent) => {
const { key, value, target } = e.detail;
console.log(`${key} = ${value}`);
});
@ -326,54 +322,200 @@ this.root.addEventListener("state:change", (e: CustomEvent) => {
## Complete Example
```typescript
function UserDashboard(component: HTMLElement) {
this.root = component;
this.store = new __sui_store(component);
this.state = new __sui_state(this);
import { $Backend, Component, EventData } from "@yao/sui";
// Initialize API
const api = new OpenAPI({ baseURL: "/api" });
const fileApi = new FileAPI(api);
const self = this as Component;
// State watchers
this.watch = {
users: (users) => this.render("userList", { users }),
loading: (loading) => {
this.root.classList.toggle("loading", loading);
},
};
// Initialize API
const api = new OpenAPI({ baseURL: "/api" });
const fileApi = new FileAPI(api);
// Load users
this.loadUsers = async () => {
this.state.Set("loading", true);
// State watchers
self.watch = {
users: (users: any[]) => self.render("userList", { users }),
loading: (loading: boolean) => {
self.root.classList.toggle("loading", loading);
},
};
const response = await api.Get<User[]>("/users");
if (!api.IsError(response)) {
this.state.Set("users", response.data);
}
// Load users
async function loadUsers() {
self.state.Set("loading", true);
this.state.Set("loading", false);
};
const response = await api.Get<User[]>("/users");
if (!api.IsError(response)) {
self.state.Set("users", response.data);
}
// Create user
this.createUser = async (event: Event, data: any) => {
const response = await this.backend.ApiCreateUser(data.name, data.email);
const users = this.state.Get("users");
this.state.Set("users", [...users, response]);
};
// Upload avatar
this.uploadAvatar = async (event: Event) => {
const input = event.target as HTMLInputElement;
const file = input.files[0];
const response = await fileApi.Upload(file, { path: "avatars" });
if (!api.IsError(response)) {
this.emit("avatar:uploaded", { url: response.data.url });
}
};
// Initialize
this.loadUsers();
self.state.Set("loading", false);
}
// Create user
self.CreateUser = async (event: Event, data: EventData) => {
const response = await $Backend().Call("ApiCreateUser", data.name, data.email);
const users = self.state.Get("users");
self.state.Set("users", [...users, response]);
};
// Upload avatar
self.UploadAvatar = async (event: Event) => {
const input = event.target as HTMLInputElement;
const file = input.files![0];
const response = await fileApi.Upload(file, { path: "avatars" });
if (!api.IsError(response)) {
self.emit("avatar:uploaded", { url: response.data.url });
}
};
// Initialize
loadUsers();
```
## CUI Integration
When SUI pages are embedded in CUI via `/web/` routes, they can communicate with the CUI host.
### URL Parameters
CUI automatically replaces special parameter values:
| Value | Replaced With |
| ---------- | -------------------------------- |
| `__theme` | Current theme (`light` / `dark`) |
| `__locale` | Current locale (e.g., `en-us`) |
> **Note**: Authentication uses secure HTTP-only cookies, no token parameter needed.
### Receiving Messages from CUI
```typescript
window.addEventListener("message", (e) => {
// Only accept messages from same origin
if (e.origin !== window.location.origin) return;
const { type, message } = e.data;
switch (type) {
case "setup":
// Initial context from CUI
document.documentElement.setAttribute("data-theme", message.theme);
console.log("Locale:", message.locale);
break;
case "update":
// Data updates from CUI
handleUpdate(message);
break;
}
});
```
### Sending Actions to CUI
Use the unified Action system to trigger CUI operations:
```typescript
// Helper function
const sendAction = (name: string, payload?: any) => {
window.parent.postMessage(
{ type: "action", message: { name, payload } },
window.location.origin
);
};
// Show notification
sendAction("notify.success", { message: "Operation completed!" });
sendAction("notify.error", { message: "Something went wrong" });
// Navigate to page
sendAction("navigate", {
route: "/agents/my-app/detail",
title: "Details",
query: { id: "123" },
});
// Open in new tab
sendAction("navigate", {
route: "/agents/my-app/report",
target: "_blank",
});
// Refresh menu
sendAction("app.menu.reload");
// Close sidebar
sendAction("event.emit", { key: "app/closeSidebar", value: {} });
```
### Available Actions
| Category | Action | Description | Payload |
| -------- | ----------------- | ------------------------- | ------------------------------------------- |
| Navigate | `navigate` | Open page in sidebar/tab | `{ route, title?, icon?, query?, target? }` |
| | `navigate.back` | Go back in history | - |
| Notify | `notify.success` | Success notification | `{ message, duration?, closable? }` |
| | `notify.error` | Error notification | `{ message, duration?, closable? }` |
| | `notify.warning` | Warning notification | `{ message, duration?, closable? }` |
| | `notify.info` | Info notification | `{ message, duration?, closable? }` |
| App | `app.menu.reload` | Refresh application menu | - |
| Modal | `modal.open` | Open modal dialog | `{ ... }` |
| | `modal.close` | Close modal | - |
| Table | `table.search` | Trigger table search | `{ keywords }` |
| | `table.refresh` | Refresh table data | - |
| Form | `form.submit` | Submit form | - |
| | `form.reset` | Reset form | - |
| Event | `event.emit` | Emit custom event | `{ key, value }` |
| Confirm | `confirm` | Show confirmation dialog | `{ title, content }` |
### Complete Example
```typescript
import { $Backend, Component, EventData } from "@yao/sui";
const self = this as Component;
// Helper: Send action to CUI
const sendAction = (name: string, payload?: any) => {
window.parent.postMessage(
{ type: "action", message: { name, payload } },
window.location.origin
);
};
// Initialize CUI communication
function init() {
window.addEventListener("message", (e) => {
if (e.origin !== window.location.origin) return;
if (e.data.type === "setup") {
const { theme, locale } = e.data.message;
document.documentElement.setAttribute("data-theme", theme);
}
});
(window as any).sendAction = sendAction;
}
init();
// Event handlers
self.HandleSave = async (event: Event, data: EventData) => {
try {
await $Backend().Call("ApiSave", data);
sendAction("notify.success", { message: "Saved successfully!" });
} catch (error: any) {
sendAction("notify.error", { message: error.message });
}
};
self.HandleViewDetail = (event: Event, data: EventData) => {
sendAction("navigate", {
route: `/agents/my-app/detail`,
title: "Details",
query: { id: data.id },
});
};
self.HandleClose = () => {
sendAction("event.emit", { key: "app/closeSidebar", value: {} });
};
```