(function(global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('@a2ui/lit/0.8'), require('react/jsx-runtime'), require('clsx'), require('markdown-it'), require('@a2ui/lit')) : typeof define === 'function' && define.amd ? define(['exports', 'react', '@a2ui/lit/0.8', 'react/jsx-runtime', 'clsx', 'markdown-it', '@a2ui/lit'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.CopilotKitA2UIRenderer = {}), global.React,global.A2UILit,global.React,global.clsx,global.markdownit,global.A2UILit)); })(this, function(exports, react, _a2ui_lit_0_8, react_jsx_runtime, clsx, markdown_it, _a2ui_lit) { Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); //#region \0rolldown/runtime.js var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) { key = keys[i]; if (!__hasOwnProp.call(to, key) && key !== except) { __defProp(to, key, { get: ((k) => from[k]).bind(null, key), enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } } } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod)); //#endregion react = __toESM(react); markdown_it = __toESM(markdown_it); //#region src/react-renderer/theme/litTheme.ts /** * Default theme for A2UI React components. * * This theme uses the same CSS class conventions as the Lit renderer, * ensuring visual consistency between React and Lit implementations. * * IMPORTANT: This theme must be kept in sync with the Lit renderer's internal * styling. If Lit components change their class maps, this file must be updated * to match. Ideally, Lit would export its default theme for direct import. * * Requires the structural styles to be injected: * @example * ```tsx * import { A2UIProvider } from '@a2ui/react'; * import { injectStyles } from '@a2ui/react/styles'; * * // Inject structural CSS at app startup * injectStyles(); * * function App() { * return ( * * * * ); * } * ``` */ const elementA = { "typography-f-sf": true, "typography-fs-n": true, "typography-w-500": true, "layout-as-n": true, "layout-dis-iflx": true, "layout-al-c": true, "typography-td-none": true, "color-c-p40": true }; const elementAudio = { "layout-w-100": true }; const elementBody = { "typography-f-s": true, "typography-fs-n": true, "typography-w-400": true, "layout-mt-0": true, "layout-mb-2": true, "typography-sz-bm": true, "color-c-n10": true }; const elementButton = { "typography-f-sf": true, "typography-fs-n": true, "typography-w-500": true, "layout-pt-3": true, "layout-pb-3": true, "layout-pl-5": true, "layout-pr-5": true, "layout-mb-1": true, "border-br-16": true, "border-bw-0": true, "border-c-n70": true, "border-bs-s": true, "color-bgc-s30": true, "behavior-ho-80": true }; const elementHeading = { "typography-f-sf": true, "typography-fs-n": true, "typography-w-500": true, "layout-mt-0": true, "layout-mb-2": true }; const elementIframe = { "behavior-sw-n": true }; const elementInput = { "typography-f-sf": true, "typography-fs-n": true, "typography-w-400": true, "layout-pl-4": true, "layout-pr-4": true, "layout-pt-2": true, "layout-pb-2": true, "border-br-6": true, "border-bw-1": true, "color-bc-s70": true, "border-bs-s": true, "layout-as-n": true, "color-c-n10": true }; const elementP = { "typography-f-s": true, "typography-fs-n": true, "typography-w-400": true, "layout-m-0": true, "typography-sz-bm": true, "layout-as-n": true, "color-c-n10": true }; const elementList = { "typography-f-s": true, "typography-fs-n": true, "typography-w-400": true, "layout-m-0": true, "typography-sz-bm": true, "layout-as-n": true, "color-c-n10": true }; const elementPre = { "typography-f-c": true, "typography-fs-n": true, "typography-w-400": true, "typography-sz-bm": true, "typography-ws-p": true, "layout-as-n": true }; const elementTextarea = { ...elementInput, "layout-r-none": true, "layout-fs-c": true }; const elementVideo = { "layout-el-cv": true }; const litTheme = { additionalStyles: { Button: { background: "var(--primary, oklch(0.205 0 0))", color: "var(--primary-foreground, oklch(0.985 0 0))", "border-radius": "calc(var(--radius, 0.625rem) - 2px)", cursor: "pointer", width: "100%", "--n-10": "var(--primary-foreground, oklch(0.985 0 0))", "--n-35": "var(--primary-foreground, oklch(0.985 0 0))", "--n-60": "var(--primary-foreground, oklch(0.985 0 0))" }, Card: { background: "var(--card, oklch(1 0 0))", border: "1px solid var(--border, oklch(0.922 0 0))", "border-radius": "var(--radius, 0.625rem)" }, TextField: { "background-color": "var(--background, oklch(1 0 0))", "border-color": "var(--input, oklch(0.922 0 0))", color: "var(--foreground, oklch(0.145 0 0))", "border-radius": "var(--radius, 0.625rem)" }, CheckBox: { "--p-100": "var(--background, oklch(1 0 0))", "--p-60": "var(--input, oklch(0.922 0 0))", "--n-30": "var(--foreground, oklch(0.145 0 0))" }, DateTimeInput: { "background-color": "var(--background, oklch(1 0 0))", "border-color": "var(--input, oklch(0.922 0 0))", color: "var(--foreground, oklch(0.145 0 0))", "border-radius": "var(--radius, 0.625rem)" }, Modal: { "--p-100": "var(--card, oklch(1 0 0))", "--p-80": "var(--border, oklch(0.922 0 0))", "border-radius": "var(--radius, 0.625rem)" }, Text: { color: "var(--foreground, oklch(0.145 0 0))" } }, components: { AudioPlayer: {}, Divider: {}, Icon: {}, Image: { all: { "border-br-5": true, "layout-el-cv": true, "layout-w-100": true, "layout-h-100": true }, avatar: { "is-avatar": true }, header: {}, icon: {}, largeFeature: {}, mediumFeature: {}, smallFeature: {} }, Text: { all: { "layout-w-100": true, "layout-g-2": true }, h1: { "typography-f-sf": true, "typography-v-r": true, "typography-w-400": true, "layout-m-0": true, "layout-p-0": true, "typography-sz-hs": true }, h2: { "typography-f-sf": true, "typography-v-r": true, "typography-w-400": true, "layout-m-0": true, "layout-p-0": true, "typography-sz-tl": true }, h3: { "typography-f-sf": true, "typography-v-r": true, "typography-w-400": true, "layout-m-0": true, "layout-p-0": true, "typography-sz-tl": true }, h4: { "typography-f-sf": true, "typography-v-r": true, "typography-w-400": true, "layout-m-0": true, "layout-p-0": true, "typography-sz-bl": true }, h5: { "typography-f-sf": true, "typography-v-r": true, "typography-w-400": true, "layout-m-0": true, "layout-p-0": true, "typography-sz-bm": true }, body: {}, caption: {} }, Video: { "border-br-5": true, "layout-el-cv": true }, Card: { "border-br-9": true, "layout-p-4": true, "color-bgc-n100": true }, Column: { "layout-g-2": true }, List: { "layout-g-4": true, "layout-p-2": true }, Modal: { backdrop: { "color-bbgc-p60_20": true }, element: { "border-br-2": true, "color-bgc-p100": true, "layout-p-4": true, "border-bw-1": true, "border-bs-s": true, "color-bc-p80": true } }, Row: { "layout-g-4": true }, Tabs: { container: {}, controls: { all: {}, selected: {} }, element: {} }, Button: { "layout-pt-2": true, "layout-pb-2": true, "layout-pl-3": true, "layout-pr-3": true, "border-bw-0": true, "border-bs-s": true, "typography-w-400": true }, CheckBox: { container: { "layout-dsp-iflex": true, "layout-al-c": true }, element: { "layout-m-0": true, "layout-mr-2": true, "layout-p-2": true, "border-br-2": true, "border-bw-1": true, "border-bs-s": true, "color-bgc-p100": true, "color-bc-p60": true, "color-c-n30": true }, label: { "typography-f-sf": true, "typography-v-r": true, "typography-w-400": true, "layout-flx-1": true, "typography-sz-ll": true } }, DateTimeInput: { container: { "typography-sz-bm": true, "layout-w-100": true, "layout-g-2": true, "layout-dsp-flexvert": true }, label: { "color-c-p30": true, "typography-sz-bm": true }, element: { "layout-pt-2": true, "layout-pb-2": true, "layout-pl-3": true, "layout-pr-3": true, "border-bw-1": true, "border-bs-s": true } }, MultipleChoice: { container: {}, label: {}, element: {} }, Slider: { container: {}, label: {}, element: {} }, TextField: { container: { "typography-sz-bm": true, "layout-w-100": true, "layout-g-2": true, "layout-dsp-flexvert": true }, label: { "layout-flx-0": true }, element: { "typography-sz-bm": true, "layout-pt-2": true, "layout-pb-2": true, "layout-pl-3": true, "layout-pr-3": true, "border-bw-1": true, "border-bs-s": true } } }, elements: { a: elementA, audio: elementAudio, body: elementBody, button: elementButton, h1: elementHeading, h2: elementHeading, h3: elementHeading, h4: elementHeading, h5: elementHeading, iframe: elementIframe, input: elementInput, p: elementP, pre: elementPre, textarea: elementTextarea, video: elementVideo }, markdown: { p: Object.keys(elementP), h1: Object.keys(elementHeading), h2: Object.keys(elementHeading), h3: Object.keys(elementHeading), h4: Object.keys(elementHeading), h5: Object.keys(elementHeading), ul: Object.keys(elementList), ol: Object.keys(elementList), li: Object.keys(elementList), a: Object.keys(elementA), strong: [], em: ["typography-fs-n"] } }; /** * Alias for litTheme - the default theme for A2UI React components. * @see litTheme */ const defaultTheme = litTheme; //#endregion //#region src/react-renderer/theme/ThemeContext.tsx /** * React context for the A2UI theme. */ const ThemeContext = (0, react.createContext)(void 0); /** * Provider component that makes the A2UI theme available to descendant components. */ function ThemeProvider({ theme, children }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThemeContext.Provider, { value: theme !== null && theme !== void 0 ? theme : defaultTheme, children }); } /** * Hook to access the current A2UI theme. * * @returns The current theme * @throws If used outside of a ThemeProvider */ function useTheme() { const theme = (0, react.useContext)(ThemeContext); if (!theme) throw new Error("useTheme must be used within a ThemeProvider or A2UIProvider"); return theme; } /** * Hook to optionally access the current A2UI theme. * * @returns The current theme, or undefined if not within a provider */ function useThemeOptional() { return (0, react.useContext)(ThemeContext); } //#endregion //#region src/react-renderer/core/A2UIProvider.tsx /** * Context for stable actions (never changes reference, prevents re-renders). * Components that only need to dispatch actions or read data won't re-render. */ const A2UIActionsContext = (0, react.createContext)(null); /** * Context for reactive state (changes trigger re-renders). * Only components that need to react to state changes subscribe to this. */ const A2UIStateContext = (0, react.createContext)(null); /** * Provider component that sets up the A2UI context for descendant components. * * This provider uses a two-context architecture for performance: * - A2UIActionsContext: Stable actions that never change (no re-renders) * - A2UIStateContext: Reactive state that triggers re-renders when needed * * @example * ```tsx * function App() { * const handleAction = async (message) => { * const response = await fetch('/api/a2ui', { * method: 'POST', * body: JSON.stringify(message) * }); * const newMessages = await response.json(); * }; * * return ( * * * * ); * } * ``` */ function A2UIProvider({ onAction, theme, children }) { const processorRef = (0, react.useRef)(null); if (!processorRef.current) processorRef.current = _a2ui_lit_0_8.Data.createSignalA2uiMessageProcessor(); const processor = processorRef.current; const [version, setVersion] = (0, react.useState)(0); const onActionRef = (0, react.useRef)(onAction !== null && onAction !== void 0 ? onAction : null); onActionRef.current = onAction !== null && onAction !== void 0 ? onAction : null; const actionsRef = (0, react.useRef)(null); if (!actionsRef.current) actionsRef.current = { processMessages: (messages) => { processor.processMessages(messages); setVersion((v) => v + 1); }, setData: (node, path, value, surfaceId) => { processor.setData(node, path, value, surfaceId); setVersion((v) => v + 1); }, dispatch: (message) => { if (onActionRef.current) onActionRef.current(message); }, clearSurfaces: () => { processor.clearSurfaces(); setVersion((v) => v + 1); }, getSurface: (surfaceId) => { return processor.getSurfaces().get(surfaceId); }, getSurfaces: () => { return processor.getSurfaces(); }, getData: (node, path, surfaceId) => { return processor.getData(node, path, surfaceId); }, resolvePath: (path, dataContextPath) => { return processor.resolvePath(path, dataContextPath); } }; const actions = actionsRef.current; const stateValue = (0, react.useMemo)(() => ({ version }), [version]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIActionsContext.Provider, { value: actions, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIStateContext.Provider, { value: stateValue, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThemeProvider, { theme, children }) }) }); } /** * Hook to access stable A2UI actions (won't cause re-renders). * Use this when you only need to dispatch actions or read data. * * @returns Stable actions object * @throws If used outside of an A2UIProvider */ function useA2UIActions() { const actions = (0, react.useContext)(A2UIActionsContext); if (!actions) throw new Error("useA2UIActions must be used within an A2UIProvider"); return actions; } /** * Hook to subscribe to A2UI state changes. * Components using this will re-render when state changes. * * @returns Current version number * @throws If used outside of an A2UIProvider */ function useA2UIState() { const state = (0, react.useContext)(A2UIStateContext); if (!state) throw new Error("useA2UIState must be used within an A2UIProvider"); return state; } /** * Hook to access the full A2UI context (actions + state). * Components using this will re-render when state changes. * * @returns The A2UI context value * @throws If used outside of an A2UIProvider */ function useA2UIContext() { const actions = useA2UIActions(); const state = useA2UIState(); return (0, react.useMemo)(() => ({ ...actions, processor: null, version: state.version, onAction: null }), [actions, state.version]); } /** * @deprecated Use useA2UIContext instead. This alias exists for backward compatibility only. */ const useA2UIStore = useA2UIContext; /** * @deprecated This selector pattern does not provide performance benefits with React Context. * Components will re-render on any context change regardless of what you select. * Use useA2UIContext() or useA2UI() directly instead. * * @param selector - Function to select a slice of state * @returns The selected state */ function useA2UIStoreSelector(selector) { return selector(useA2UIContext()); } //#endregion //#region src/react-renderer/hooks/useA2UI.ts /** * Main API hook for A2UI. Provides methods to process messages * and access surface state. * * Note: This hook subscribes to state changes. Components using this * will re-render when the A2UI state changes. For action-only usage * (no re-renders), use useA2UIActions() instead. * * @returns Object with message processing and surface access methods * * @example * ```tsx * function ChatApp() { * const { processMessages, getSurface } = useA2UI(); * * useEffect(() => { * const ws = new WebSocket('wss://agent.example.com'); * ws.onmessage = (event) => { * const messages = JSON.parse(event.data); * processMessages(messages); * }; * return () => ws.close(); * }, [processMessages]); * * return ; * } * ``` */ function useA2UI() { const actions = useA2UIActions(); const state = useA2UIState(); return { processMessages: actions.processMessages, getSurface: actions.getSurface, getSurfaces: actions.getSurfaces, clearSurfaces: actions.clearSurfaces, version: state.version }; } //#endregion //#region src/react-renderer/registry/ComponentRegistry.ts /** * Registry for A2UI components. Allows registration of custom components * and supports lazy loading for code splitting. * * @example * ```tsx * const registry = new ComponentRegistry(); * * // Register a component directly * registry.register('Text', { component: Text }); * * // Register with lazy loading * registry.register('Modal', { * component: () => import('./components/Modal'), * lazy: true * }); * * // Use with A2UIRenderer * * ``` */ var ComponentRegistry = class ComponentRegistry { constructor() { this.registry = /* @__PURE__ */ new Map(); this.lazyCache = /* @__PURE__ */ new Map(); } /** * Get the singleton instance of the registry. * Use this for the default global registry. */ static getInstance() { if (!ComponentRegistry._instance) ComponentRegistry._instance = new ComponentRegistry(); return ComponentRegistry._instance; } /** * Reset the singleton instance. * Useful for testing. */ static resetInstance() { ComponentRegistry._instance = null; } /** * Register a component type. * * @param type - The A2UI component type name (e.g., 'Text', 'Button') * @param registration - The component registration */ register(type, registration) { this.registry.set(type, registration); } /** * Unregister a component type. * * @param type - The component type to unregister */ unregister(type) { this.registry.delete(type); this.lazyCache.delete(type); } /** * Check if a component type is registered. * * @param type - The component type to check * @returns True if the component is registered */ has(type) { return this.registry.has(type); } /** * Get a component by type. If the component is registered with lazy loading, * returns a React.lazy wrapped component. * * @param type - The component type to get * @returns The React component, or null if not found */ get(type) { const registration = this.registry.get(type); if (!registration) return null; if (registration.lazy && typeof registration.component === "function") { const cached = this.lazyCache.get(type); if (cached) return cached; const lazyComponent = (0, react.lazy)(registration.component); this.lazyCache.set(type, lazyComponent); return lazyComponent; } return registration.component; } /** * Get all registered component types. * * @returns Array of registered type names */ getRegisteredTypes() { return Array.from(this.registry.keys()); } /** * Clear all registrations. */ clear() { this.registry.clear(); this.lazyCache.clear(); } }; ComponentRegistry._instance = null; //#endregion //#region src/react-renderer/core/ComponentNode.tsx /** Memoized loading fallback to avoid recreating on each render */ const LoadingFallback = (0, react.memo)(function LoadingFallback() { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-loading", style: { padding: "8px", opacity: .5 }, children: "Loading..." }); }); /** * ComponentNode - dynamically renders an A2UI component based on its type. * * Looks up the component in the registry and renders it with the appropriate props. * Supports lazy-loaded components via React.Suspense. * * No wrapper div is rendered - the component's root div (e.g., .a2ui-image) is the * direct flex child, exactly matching Lit's structure where the :host element IS * the flex item. Each component handles --weight CSS variable on its root div. * * Memoized to prevent unnecessary re-renders when parent updates but node hasn't changed. */ const ComponentNode = (0, react.memo)(function ComponentNode({ node, surfaceId, registry }) { const actualRegistry = registry !== null && registry !== void 0 ? registry : ComponentRegistry.getInstance(); const nodeType = node && typeof node === "object" && "type" in node ? node.type : null; const Component = (0, react.useMemo)(() => nodeType ? actualRegistry.get(nodeType) : null, [actualRegistry, nodeType]); if (!nodeType) { if (node) console.warn("[A2UI] Invalid component node (not resolved?):", node); return null; } if (!Component) { console.warn(`[A2UI] Unknown component type: ${nodeType}`); return null; } return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Suspense, { fallback: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LoadingFallback, {}), children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, { node, surfaceId }) }); }); //#endregion //#region src/react-renderer/theme/utils.ts /** * Converts a theme class map (Record) to a className string. * * @param classMap - An object where keys are class names and values are booleans * @returns A space-separated string of class names where the value is true * * @example * classMapToString({ 'a2ui-button': true, 'a2ui-button--primary': true, 'disabled': false }) * // Returns: 'a2ui-button a2ui-button--primary' */ function classMapToString(classMap) { if (!classMap) return ""; return Object.entries(classMap).filter(([, enabled]) => enabled).map(([className]) => className).join(" "); } /** * Converts an additional styles object (Record) to a React style object. * * @param styles - An object with CSS property names as keys and values as strings * @returns A React-compatible style object, or undefined if no styles * * @example * stylesToObject({ 'background-color': 'red', 'font-size': '16px', '--custom-var': 'blue' }) * // Returns: { backgroundColor: 'red', fontSize: '16px', '--custom-var': 'blue' } */ function stylesToObject(styles) { if (!styles || Object.keys(styles).length === 0) return void 0; const result = {}; for (const [key, value] of Object.entries(styles)) if (key.startsWith("--")) result[key] = value; else { const camelKey = key.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase()); result[camelKey] = value; } return result; } //#endregion //#region src/react-renderer/lib/utils.ts /** * Utility function to merge class names. * Combines clsx for conditional classes. * * @param inputs - Class values to merge * @returns Merged class name string * * @example * cn('base-class', condition && 'conditional-class', { 'object-class': true }) */ function cn(...inputs) { return (0, clsx.clsx)(inputs); } /** * Merges multiple class maps into a single class map. * Uses Lit's Styles.merge() function directly for consistency. * * Lit's merge handles prefix conflicts: if you have 'layout-p-2' and 'layout-p-4', * only the latter is kept (same prefix 'layout-p-' means they conflict). * * @param maps - Class maps to merge * @returns A merged class map */ function mergeClassMaps(...maps) { const validMaps = maps.filter((m) => m !== void 0); if (validMaps.length === 0) return {}; return _a2ui_lit_0_8.Styles.merge(...validMaps); } //#endregion //#region src/react-renderer/core/A2UIRenderer.tsx /** Default loading fallback - memoized to prevent recreation */ const DefaultLoadingFallback = (0, react.memo)(function DefaultLoadingFallback() { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-loading", style: { padding: "16px", opacity: .5 }, children: "Loading..." }); }); /** * A2UIRenderer - renders an A2UI surface. * * This is the main entry point for rendering A2UI content in your React app. * It reads the surface state from the A2UI store and renders the component tree. * * Memoized to prevent unnecessary re-renders when props haven't changed. * * @example * ```tsx * function App() { * return ( * * * * ); * } * ``` */ const A2UIRenderer = (0, react.memo)(function A2UIRenderer({ surfaceId, className, fallback = null, loadingFallback, registry }) { const { getSurface, version } = useA2UI(); const surface = getSurface(surfaceId); const surfaceStyles = (0, react.useMemo)(() => { if (!(surface === null || surface === void 0 ? void 0 : surface.styles)) return {}; const styles = {}; for (const [key, value] of Object.entries(surface.styles)) switch (key) { case "primaryColor": styles["--p-100"] = "#ffffff"; styles["--p-99"] = `color-mix(in srgb, ${value} 2%, white 98%)`; styles["--p-98"] = `color-mix(in srgb, ${value} 4%, white 96%)`; styles["--p-95"] = `color-mix(in srgb, ${value} 10%, white 90%)`; styles["--p-90"] = `color-mix(in srgb, ${value} 20%, white 80%)`; styles["--p-80"] = `color-mix(in srgb, ${value} 40%, white 60%)`; styles["--p-70"] = `color-mix(in srgb, ${value} 60%, white 40%)`; styles["--p-60"] = `color-mix(in srgb, ${value} 80%, white 20%)`; styles["--p-50"] = String(value); styles["--p-40"] = `color-mix(in srgb, ${value} 80%, black 20%)`; styles["--p-35"] = `color-mix(in srgb, ${value} 70%, black 30%)`; styles["--p-30"] = `color-mix(in srgb, ${value} 60%, black 40%)`; styles["--p-25"] = `color-mix(in srgb, ${value} 50%, black 50%)`; styles["--p-20"] = `color-mix(in srgb, ${value} 40%, black 60%)`; styles["--p-15"] = `color-mix(in srgb, ${value} 30%, black 70%)`; styles["--p-10"] = `color-mix(in srgb, ${value} 20%, black 80%)`; styles["--p-5"] = `color-mix(in srgb, ${value} 10%, black 90%)`; styles["--p-0"] = "#000000"; break; case "font": styles["--font-family"] = String(value); styles["--font-family-flex"] = String(value); break; } return styles; }, [surface === null || surface === void 0 ? void 0 : surface.styles]); if (!surface || !surface.componentTree) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: fallback }); const actualLoadingFallback = loadingFallback !== null && loadingFallback !== void 0 ? loadingFallback : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultLoadingFallback, {}); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: cn("a2ui-surface", className), style: surfaceStyles, "data-surface-id": surfaceId, "data-version": version, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Suspense, { fallback: actualLoadingFallback, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComponentNode, { node: surface.componentTree, surfaceId, registry }) }) }); }); //#endregion //#region src/react-renderer/hooks/useA2UIComponent.ts /** * Base hook for A2UI components. Provides data binding, theme access, * and action dispatching. * * @param node - The component node from the A2UI message processor * @param surfaceId - The surface ID this component belongs to * @returns Object with theme, data binding helpers, and action dispatcher * * @example * ```tsx * function TextField({ node, surfaceId }: A2UIComponentProps) { * const { theme, resolveString, setValue } = useA2UIComponent(node, surfaceId); * * const label = resolveString(node.properties.label); * const value = resolveString(node.properties.text) ?? ''; * * return ( *
* * setValue(node.properties.text?.path!, e.target.value)} * /> *
* ); * } * ``` */ function useA2UIComponent(node, surfaceId) { const actions = useA2UIActions(); const theme = useTheme(); const baseId = (0, react.useId)(); useA2UIState(); /** * Resolve a StringValue to its actual string value. * Checks literalString, literal, then path in that order. * Note: This reads from data model via stable actions reference. */ const resolveString = (0, react.useCallback)((value) => { if (!value) return null; if (typeof value !== "object") return null; if (value.literalString !== void 0) return value.literalString; if (value.literal !== void 0) return String(value.literal); if (value.path) { const data = actions.getData(node, value.path, surfaceId); return data !== null ? String(data) : null; } return null; }, [ actions, node, surfaceId ]); /** * Resolve a NumberValue to its actual number value. */ const resolveNumber = (0, react.useCallback)((value) => { if (!value) return null; if (typeof value !== "object") return null; if (value.literalNumber !== void 0) return value.literalNumber; if (value.literal !== void 0) return Number(value.literal); if (value.path) { const data = actions.getData(node, value.path, surfaceId); return data !== null ? Number(data) : null; } return null; }, [ actions, node, surfaceId ]); /** * Resolve a BooleanValue to its actual boolean value. */ const resolveBoolean = (0, react.useCallback)((value) => { if (!value) return null; if (typeof value !== "object") return null; if (value.literalBoolean !== void 0) return value.literalBoolean; if (value.literal !== void 0) return Boolean(value.literal); if (value.path) { const data = actions.getData(node, value.path, surfaceId); return data !== null ? Boolean(data) : null; } return null; }, [ actions, node, surfaceId ]); /** * Set a value in the data model for two-way binding. */ const setValue = (0, react.useCallback)((path, value) => { actions.setData(node, path, value, surfaceId); }, [ actions, node, surfaceId ]); /** * Get a value from the data model. */ const getValue = (0, react.useCallback)((path) => { return actions.getData(node, path, surfaceId); }, [ actions, node, surfaceId ]); /** * Dispatch a user action to the server. * Resolves all context bindings before dispatching. */ const sendAction = (0, react.useCallback)((action) => { const actionContext = {}; if (action.context) { for (const item of action.context) if (item.value.literalString !== void 0) actionContext[item.key] = item.value.literalString; else if (item.value.literalNumber !== void 0) actionContext[item.key] = item.value.literalNumber; else if (item.value.literalBoolean !== void 0) actionContext[item.key] = item.value.literalBoolean; else if (item.value.path) { const resolvedPath = actions.resolvePath(item.value.path, node.dataContextPath); actionContext[item.key] = actions.getData(node, resolvedPath, surfaceId); } } actions.dispatch({ userAction: { name: action.name, sourceComponentId: node.id, surfaceId, timestamp: (/* @__PURE__ */ new Date()).toISOString(), context: actionContext } }); }, [ actions, node, surfaceId ]); /** * Generate a unique ID for accessibility purposes. * Uses React's useId() for SSR and Concurrent Mode compatibility. */ const getUniqueId = (0, react.useCallback)((prefix) => { return `${prefix}${baseId}`; }, [baseId]); return (0, react.useMemo)(() => ({ theme, resolveString, resolveNumber, resolveBoolean, setValue, getValue, sendAction, getUniqueId }), [ theme, resolveString, resolveNumber, resolveBoolean, setValue, getValue, sendAction, getUniqueId ]); } //#endregion //#region src/react-renderer/components/content/Text.tsx function isHintedStyles(styles) { if (typeof styles !== "object" || !styles || Array.isArray(styles)) return false; return [ "h1", "h2", "h3", "h4", "h5", "caption", "body" ].some((v) => v in styles); } /** * Markdown-it instance for rendering markdown text. * Uses synchronous import to ensure availability at first render (matches Lit renderer). * * Configuration matches Lit's markdown directive (uses MarkdownIt defaults): * - html: false (default) - Security: disable raw HTML * - linkify: false (default) - Don't auto-convert URLs/emails to links * - breaks: false (default) - Don't convert \n to
* - typographer: false (default) - Don't use smart quotes/dashes */ const markdownRenderer = new markdown_it.default(); /** * Apply theme classes to markdown HTML elements. * Replaces default element tags with themed versions. */ function applyMarkdownTheme(html, markdownTheme) { if (!markdownTheme) return html; const replacements = []; for (const [element, classes] of Object.entries(markdownTheme)) { if (!classes || Array.isArray(classes) && classes.length === 0) continue; const classString = Array.isArray(classes) ? classes.join(" ") : classMapToString(classes); if (!classString) continue; const tagRegex = new RegExp(`<${element}(?=\\s|>|/>)`, "gi"); replacements.push([tagRegex, `<${element} class="${classString}"`]); } let result = html; for (const [regex, replacement] of replacements) result = result.replace(regex, replacement); return result; } /** * Text component - renders text content with markdown support. * * Structure mirrors Lit's Text component: *
← :host equivalent *
← theme classes *

...

← rendered markdown content *
*
* * Text is parsed as markdown and rendered as HTML (matches Lit renderer behavior). * Supports usageHint values: h1, h2, h3, h4, h5, caption, body * * Markdown features supported: * - **Bold** and *italic* text * - Lists (ordered and unordered) * - `inline code` and code blocks * - [Links](url) (auto-linkified URLs too) * - Blockquotes * - Horizontal rules * * Note: Raw HTML is disabled for security. */ const Text = (0, react.memo)(function Text({ node, surfaceId }) { var _theme$additionalStyl2; const { theme, resolveString } = useA2UIComponent(node, surfaceId); const props = node.properties; const textValue = resolveString(props.text); const usageHint = props.usageHint; const classes = mergeClassMaps(theme.components.Text.all, usageHint ? theme.components.Text[usageHint] : {}); const additionalStyles = (0, react.useMemo)(() => { var _theme$additionalStyl; const textStyles = (_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.Text; if (!textStyles) return void 0; if (isHintedStyles(textStyles)) return stylesToObject(textStyles[usageHint !== null && usageHint !== void 0 ? usageHint : "body"]); return stylesToObject(textStyles); }, [(_theme$additionalStyl2 = theme.additionalStyles) === null || _theme$additionalStyl2 === void 0 ? void 0 : _theme$additionalStyl2.Text, usageHint]); const renderedContent = (0, react.useMemo)(() => { if (textValue === null || textValue === void 0) return null; let markdownText = textValue; switch (usageHint) { case "h1": markdownText = `# ${markdownText}`; break; case "h2": markdownText = `## ${markdownText}`; break; case "h3": markdownText = `### ${markdownText}`; break; case "h4": markdownText = `#### ${markdownText}`; break; case "h5": markdownText = `##### ${markdownText}`; break; case "caption": markdownText = `*${markdownText}*`; break; default: break; } return { __html: applyMarkdownTheme(markdownRenderer.render(markdownText), theme.markdown) }; }, [ textValue, theme.markdown, usageHint ]); if (!renderedContent) return null; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-text", style: node.weight !== void 0 ? { "--weight": node.weight } : {}, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", { className: classMapToString(classes), style: additionalStyles, dangerouslySetInnerHTML: renderedContent }) }); }); //#endregion //#region src/react-renderer/components/content/Image.tsx /** * Image component - renders an image from a URL with optional sizing and fit modes. * * Supports usageHint values: icon, avatar, smallFeature, mediumFeature, largeFeature, header * Supports fit values: contain, cover, fill, none, scale-down (maps to object-fit via CSS variable) */ const Image = (0, react.memo)(function Image({ node, surfaceId }) { var _ref, _theme$additionalStyl; const { theme, resolveString } = useA2UIComponent(node, surfaceId); const props = node.properties; const rawUrl = resolveString(props.url); const url = rawUrl === null || rawUrl === void 0 ? void 0 : rawUrl.replace("//via.placeholder.com/", "//placehold.co/"); const usageHint = props.usageHint; const fit = (_ref = props.fit) !== null && _ref !== void 0 ? _ref : "fill"; const classes = mergeClassMaps(theme.components.Image.all, usageHint ? theme.components.Image[usageHint] : {}); const style = { ...stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.Image), "--object-fit": fit }; if (!url) return null; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-image", style: node.weight !== void 0 ? { "--weight": node.weight } : {}, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", { className: classMapToString(classes), style, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", { src: url, alt: "" }) }) }); }); //#endregion //#region src/react-renderer/components/content/Icon.tsx /** * Convert camelCase to snake_case for Material Symbols font. * e.g., "shoppingCart" -> "shopping_cart" * This matches the Lit renderer's approach. */ function toSnakeCase(str) { return str.replace(/([A-Z])/g, "_$1").toLowerCase(); } /** * Icon component - renders an icon using Material Symbols Outlined font. * * This matches the Lit renderer's approach using the g-icon class with * Material Symbols Outlined font. * * @example Add Material Symbols font to your HTML: * ```html * * ``` */ const Icon = (0, react.memo)(function Icon({ node, surfaceId }) { var _theme$additionalStyl; const { theme, resolveString } = useA2UIComponent(node, surfaceId); const props = node.properties; const iconName = resolveString(props.name); if (!iconName) return null; const snakeCaseName = toSnakeCase(iconName); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-icon", style: node.weight !== void 0 ? { "--weight": node.weight } : {}, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", { className: classMapToString(theme.components.Icon), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.Icon), children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "g-icon", children: snakeCaseName }) }) }); }); //#endregion //#region src/react-renderer/components/content/Divider.tsx /** * Divider component - renders a visual separator line. * * Structure mirrors Lit's Divider component: *
← :host equivalent *
← internal element *
*/ const Divider = (0, react.memo)(function Divider({ node, surfaceId }) { var _theme$additionalStyl; const { theme } = useA2UIComponent(node, surfaceId); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-divider", style: node.weight !== void 0 ? { "--weight": node.weight } : {}, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("hr", { className: classMapToString(theme.components.Divider), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.Divider) }) }); }); //#endregion //#region src/react-renderer/components/content/Video.tsx /** * Check if a URL is a YouTube URL and extract the video ID. */ function getYouTubeVideoId(url) { for (const pattern of [/(?:youtube\.com\/watch\?v=|youtu\.be\/|youtube\.com\/embed\/)([^&\s?]+)/]) { const match = url.match(pattern); if (match && match.length > 1) return match[1]; } return null; } /** * Video component - renders a video player. * * Supports regular video URLs and YouTube URLs (renders as embedded iframe). */ const Video = (0, react.memo)(function Video({ node, surfaceId }) { var _theme$additionalStyl; const { theme, resolveString } = useA2UIComponent(node, surfaceId); const props = node.properties; const url = resolveString(props.url); if (!url) return null; const youtubeId = getYouTubeVideoId(url); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-video", style: node.weight !== void 0 ? { "--weight": node.weight } : {}, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", { className: classMapToString(theme.components.Video), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.Video), children: youtubeId ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", { src: `https://www.youtube.com/embed/${youtubeId}`, title: "YouTube video player", allow: "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture", allowFullScreen: true, style: { border: "none", width: "100%", aspectRatio: "16/9" } }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", { src: url, controls: true }) }) }); }); //#endregion //#region src/react-renderer/components/content/AudioPlayer.tsx /** * AudioPlayer component - renders an audio player with optional description. */ const AudioPlayer = (0, react.memo)(function AudioPlayer({ node, surfaceId }) { var _props$description, _theme$additionalStyl; const { theme, resolveString } = useA2UIComponent(node, surfaceId); const props = node.properties; const url = resolveString(props.url); const description = resolveString((_props$description = props.description) !== null && _props$description !== void 0 ? _props$description : null); if (!url) return null; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-audio", style: node.weight !== void 0 ? { "--weight": node.weight } : {}, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { className: classMapToString(theme.components.AudioPlayer), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.AudioPlayer), children: [description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: description }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("audio", { src: url, controls: true })] }) }); }); //#endregion //#region src/react-renderer/components/layout/Row.tsx /** * Row component - arranges children horizontally using flexbox. * * Supports distribution (justify-content) and alignment (align-items) properties. */ const Row = (0, react.memo)(function Row({ node, surfaceId }) { var _props$alignment, _props$distribution, _theme$additionalStyl; const { theme } = useA2UIComponent(node, surfaceId); const props = node.properties; const alignment = (_props$alignment = props.alignment) !== null && _props$alignment !== void 0 ? _props$alignment : "stretch"; const distribution = (_props$distribution = props.distribution) !== null && _props$distribution !== void 0 ? _props$distribution : "start"; const children = Array.isArray(props.children) ? props.children : []; const hostStyle = node.weight !== void 0 ? { "--weight": node.weight } : {}; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-row", "data-alignment": alignment, "data-distribution": distribution, style: hostStyle, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", { className: classMapToString(theme.components.Row), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.Row), children: children.map((child, index) => { const childId = typeof child === "object" && child !== null && "id" in child ? child.id : `child-${index}`; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComponentNode, { node: typeof child === "object" && child !== null && "type" in child ? child : null, surfaceId }, childId); }) }) }); }); //#endregion //#region src/react-renderer/components/layout/Column.tsx /** * Column component - arranges children vertically using flexbox. * * Supports distribution (justify-content) and alignment (align-items) properties. */ const Column = (0, react.memo)(function Column({ node, surfaceId }) { var _props$alignment, _props$distribution, _theme$additionalStyl; const { theme } = useA2UIComponent(node, surfaceId); const props = node.properties; const alignment = (_props$alignment = props.alignment) !== null && _props$alignment !== void 0 ? _props$alignment : "stretch"; const distribution = (_props$distribution = props.distribution) !== null && _props$distribution !== void 0 ? _props$distribution : "start"; const children = Array.isArray(props.children) ? props.children : []; const hostStyle = node.weight !== void 0 ? { "--weight": node.weight } : {}; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-column", "data-alignment": alignment, "data-distribution": distribution, style: hostStyle, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", { className: classMapToString(theme.components.Column), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.Column), children: children.map((child, index) => { const childId = typeof child === "object" && child !== null && "id" in child ? child.id : `child-${index}`; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComponentNode, { node: typeof child === "object" && child !== null && "type" in child ? child : null, surfaceId }, childId); }) }) }); }); //#endregion //#region src/react-renderer/components/layout/List.tsx /** * List component - renders a scrollable list of items. * * Supports direction (vertical/horizontal) properties. */ const List = (0, react.memo)(function List({ node, surfaceId }) { var _props$direction, _theme$additionalStyl; const { theme } = useA2UIComponent(node, surfaceId); const props = node.properties; const direction = (_props$direction = props.direction) !== null && _props$direction !== void 0 ? _props$direction : "vertical"; const children = Array.isArray(props.children) ? props.children : []; const hostStyle = node.weight !== void 0 ? { "--weight": node.weight } : {}; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-list", "data-direction": direction, style: hostStyle, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", { className: classMapToString(theme.components.List), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.List), children: children.map((child, index) => { const childId = typeof child === "object" && child !== null && "id" in child ? child.id : `child-${index}`; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComponentNode, { node: typeof child === "object" && child !== null && "type" in child ? child : null, surfaceId }, childId); }) }) }); }); //#endregion //#region src/react-renderer/components/layout/Card.tsx /** * Card component - a container that visually groups content. * * Structure mirrors Lit's Card component: *
← :host equivalent *
← theme classes (border, padding, background) * {children} ← ::slotted(*) equivalent *
*
* * All styles come from componentSpecificStyles CSS, no inline styles needed. */ const Card = (0, react.memo)(function Card({ node, surfaceId }) { var _props$children, _theme$additionalStyl; const { theme } = useA2UIComponent(node, surfaceId); const props = node.properties; const rawChildren = (_props$children = props.children) !== null && _props$children !== void 0 ? _props$children : props.child ? [props.child] : []; const children = Array.isArray(rawChildren) ? rawChildren : []; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-card", style: node.weight !== void 0 ? { "--weight": node.weight } : {}, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", { className: classMapToString(theme.components.Card), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.Card), children: children.map((child, index) => { const childId = typeof child === "object" && child !== null && "id" in child ? child.id : `child-${index}`; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComponentNode, { node: typeof child === "object" && child !== null && "type" in child ? child : null, surfaceId }, childId); }) }) }); }); //#endregion //#region src/react-renderer/components/layout/Tabs.tsx /** * Tabs component - displays content in switchable tabs. */ const Tabs = (0, react.memo)(function Tabs({ node, surfaceId }) { var _props$tabItems, _theme$additionalStyl; const { theme, resolveString } = useA2UIComponent(node, surfaceId); const props = node.properties; const [selectedIndex, setSelectedIndex] = (0, react.useState)(0); const tabItems = (_props$tabItems = props.tabItems) !== null && _props$tabItems !== void 0 ? _props$tabItems : []; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-tabs", style: node.weight !== void 0 ? { "--weight": node.weight } : {}, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { className: classMapToString(theme.components.Tabs.container), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.Tabs), children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { id: "buttons", className: classMapToString(theme.components.Tabs.element), children: tabItems.map((tab, index) => { const title = resolveString(tab.title); const isSelected = index === selectedIndex; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { disabled: isSelected, className: classMapToString(isSelected ? mergeClassMaps(theme.components.Tabs.controls.all, theme.components.Tabs.controls.selected) : theme.components.Tabs.controls.all), onClick: () => setSelectedIndex(index), children: title }, index); }) }), tabItems[selectedIndex] && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComponentNode, { node: tabItems[selectedIndex].child, surfaceId })] }) }); }); //#endregion //#region src/react-renderer/components/layout/Modal.tsx /** * Modal component - displays content in a dialog overlay. * * Matches Lit's rendering approach: * - When closed: renders section with entry point child * - When open: renders dialog with content child (entry point is replaced) * * The dialog is rendered in place (no portal) so it stays inside .a2ui-surface * and CSS selectors work correctly. showModal() handles the top-layer overlay. */ const Modal = (0, react.memo)(function Modal({ node, surfaceId }) { var _theme$additionalStyl; const { theme } = useA2UIComponent(node, surfaceId); const props = node.properties; const [isOpen, setIsOpen] = (0, react.useState)(false); const dialogRef = (0, react.useRef)(null); const openModal = (0, react.useCallback)(() => { setIsOpen(true); }, []); const closeModal = (0, react.useCallback)(() => { setIsOpen(false); }, []); (0, react.useEffect)(() => { const dialog = dialogRef.current; if (!dialog) return; if (isOpen && !dialog.open) dialog.showModal(); const handleClose = () => { setIsOpen(false); }; dialog.addEventListener("close", handleClose); return () => dialog.removeEventListener("close", handleClose); }, [isOpen]); const handleBackdropClick = (0, react.useCallback)((e) => { if (e.target === e.currentTarget) closeModal(); }, [closeModal]); const handleKeyDown = (0, react.useCallback)((e) => { if (e.key === "Escape") closeModal(); }, [closeModal]); const hostStyle = node.weight !== void 0 ? { "--weight": node.weight } : {}; if (!isOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-modal", style: hostStyle, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", { onClick: openModal, style: { cursor: "pointer" }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComponentNode, { node: props.entryPointChild, surfaceId }) }) }); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-modal", style: hostStyle, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dialog", { ref: dialogRef, className: classMapToString(theme.components.Modal.backdrop), onClick: handleBackdropClick, onKeyDown: handleKeyDown, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { className: classMapToString(theme.components.Modal.element), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.Modal), children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { id: "controls", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { onClick: closeModal, "aria-label": "Close modal", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "g-icon", children: "close" }) }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComponentNode, { node: props.contentChild, surfaceId })] }) }) }); }); //#endregion //#region src/react-renderer/components/interactive/Button.tsx /** * Button component - a clickable element that triggers an action. * * Contains a child component (usually Text or Icon) and dispatches * a user action when clicked. */ const Button = (0, react.memo)(function Button({ node, surfaceId }) { var _theme$additionalStyl; const { theme, sendAction } = useA2UIComponent(node, surfaceId); const props = node.properties; const handleClick = (0, react.useCallback)(() => { if (props.action) sendAction(props.action); }, [props.action, sendAction]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-button", style: node.weight !== void 0 ? { "--weight": node.weight } : {}, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: classMapToString(theme.components.Button), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.Button), onClick: handleClick, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ComponentNode, { node: props.child, surfaceId }) }) }); }); //#endregion //#region src/react-renderer/components/interactive/TextField.tsx /** * TextField component - an input field for text entry. * * Supports various input types and two-way data binding. */ const TextField = (0, react.memo)(function TextField({ node, surfaceId }) { var _props$text, _resolveString, _theme$additionalStyl, _theme$additionalStyl2; const { theme, resolveString, setValue, getValue } = useA2UIComponent(node, surfaceId); const props = node.properties; const id = (0, react.useId)(); const label = resolveString(props.label); const textPath = (_props$text = props.text) === null || _props$text === void 0 ? void 0 : _props$text.path; const initialValue = (_resolveString = resolveString(props.text)) !== null && _resolveString !== void 0 ? _resolveString : ""; const fieldType = props.type; const validationRegexp = props.validationRegexp; const [value, setLocalValue] = (0, react.useState)(initialValue); const [_isValid, setIsValid] = (0, react.useState)(true); (0, react.useEffect)(() => { if (textPath) { const externalValue = getValue(textPath); if (externalValue !== null && String(externalValue) !== value) setLocalValue(String(externalValue)); } }, [textPath, getValue]); const handleChange = (0, react.useCallback)((e) => { const newValue = e.target.value; setLocalValue(newValue); if (validationRegexp) setIsValid(new RegExp(validationRegexp).test(newValue)); if (textPath) setValue(textPath, newValue); }, [ validationRegexp, textPath, setValue ]); const inputType = fieldType === "number" ? "number" : fieldType === "date" ? "date" : "text"; const isTextArea = fieldType === "longText"; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-textfield", style: node.weight !== void 0 ? { "--weight": node.weight } : {}, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { className: classMapToString(theme.components.TextField.container), children: [label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { htmlFor: id, className: classMapToString(theme.components.TextField.label), children: label }), isTextArea ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", { id, value, onChange: handleChange, placeholder: "Please enter a value", className: classMapToString(theme.components.TextField.element), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.TextField) }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { type: inputType, id, value, onChange: handleChange, placeholder: "Please enter a value", className: classMapToString(theme.components.TextField.element), style: stylesToObject((_theme$additionalStyl2 = theme.additionalStyles) === null || _theme$additionalStyl2 === void 0 ? void 0 : _theme$additionalStyl2.TextField) })] }) }); }); //#endregion //#region src/react-renderer/components/interactive/CheckBox.tsx /** * CheckBox component - a boolean toggle with a label. * * Supports two-way data binding for the checked state. */ const CheckBox = (0, react.memo)(function CheckBox({ node, surfaceId }) { var _props$value, _resolveBoolean, _props$value3, _theme$additionalStyl; const { theme, resolveString, resolveBoolean, setValue, getValue } = useA2UIComponent(node, surfaceId); const props = node.properties; const id = (0, react.useId)(); const label = resolveString(props.label); const valuePath = (_props$value = props.value) === null || _props$value === void 0 ? void 0 : _props$value.path; const [checked, setChecked] = (0, react.useState)((_resolveBoolean = resolveBoolean(props.value)) !== null && _resolveBoolean !== void 0 ? _resolveBoolean : false); (0, react.useEffect)(() => { if (valuePath) { const externalValue = getValue(valuePath); if (externalValue !== null && Boolean(externalValue) !== checked) setChecked(Boolean(externalValue)); } }, [valuePath, getValue]); (0, react.useEffect)(() => { var _props$value2; if (((_props$value2 = props.value) === null || _props$value2 === void 0 ? void 0 : _props$value2.literalBoolean) !== void 0) setChecked(props.value.literalBoolean); }, [(_props$value3 = props.value) === null || _props$value3 === void 0 ? void 0 : _props$value3.literalBoolean]); const handleChange = (0, react.useCallback)((e) => { const newValue = e.target.checked; setChecked(newValue); if (valuePath) setValue(valuePath, newValue); }, [valuePath, setValue]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-checkbox", style: node.weight !== void 0 ? { "--weight": node.weight } : {}, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { className: classMapToString(theme.components.CheckBox.container), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.CheckBox), children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { type: "checkbox", id, checked, onChange: handleChange, className: classMapToString(theme.components.CheckBox.element) }), label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { htmlFor: id, className: classMapToString(theme.components.CheckBox.label), children: label })] }) }); }); //#endregion //#region src/react-renderer/components/interactive/Slider.tsx /** * Slider component - a numeric value selector with a range. * * Supports two-way data binding for the value. */ const Slider = (0, react.memo)(function Slider({ node, surfaceId }) { var _props$value, _resolveNumber, _props$minValue, _props$maxValue, _props$value3, _theme$additionalStyl; const { theme, resolveNumber, resolveString, setValue, getValue } = useA2UIComponent(node, surfaceId); const props = node.properties; const id = (0, react.useId)(); const valuePath = (_props$value = props.value) === null || _props$value === void 0 ? void 0 : _props$value.path; const initialValue = (_resolveNumber = resolveNumber(props.value)) !== null && _resolveNumber !== void 0 ? _resolveNumber : 0; const minValue = (_props$minValue = props.minValue) !== null && _props$minValue !== void 0 ? _props$minValue : 0; const maxValue = (_props$maxValue = props.maxValue) !== null && _props$maxValue !== void 0 ? _props$maxValue : 0; const [value, setLocalValue] = (0, react.useState)(initialValue); (0, react.useEffect)(() => { if (valuePath) { const externalValue = getValue(valuePath); if (externalValue !== null && Number(externalValue) !== value) setLocalValue(Number(externalValue)); } }, [valuePath, getValue]); (0, react.useEffect)(() => { var _props$value2; if (((_props$value2 = props.value) === null || _props$value2 === void 0 ? void 0 : _props$value2.literalNumber) !== void 0) setLocalValue(props.value.literalNumber); }, [(_props$value3 = props.value) === null || _props$value3 === void 0 ? void 0 : _props$value3.literalNumber]); const handleChange = (0, react.useCallback)((e) => { const newValue = Number(e.target.value); setLocalValue(newValue); if (valuePath) setValue(valuePath, newValue); }, [valuePath, setValue]); const labelValue = props.label; const label = labelValue ? resolveString(labelValue) : ""; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-slider", style: node.weight !== void 0 ? { "--weight": node.weight } : {}, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { className: classMapToString(theme.components.Slider.container), children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { htmlFor: id, className: classMapToString(theme.components.Slider.label), children: label }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { type: "range", id, name: "data", value, min: minValue, max: maxValue, onChange: handleChange, className: classMapToString(theme.components.Slider.element), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.Slider) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: classMapToString(theme.components.Slider.label), children: value }) ] }) }); }); //#endregion //#region src/react-renderer/components/interactive/DateTimeInput.tsx /** * DateTimeInput component - a date and/or time picker. * * Supports enabling date, time, or both. Uses native HTML5 date/time inputs. */ const DateTimeInput = (0, react.memo)(function DateTimeInput({ node, surfaceId }) { var _props$value, _resolveString, _props$enableDate, _props$enableTime, _theme$additionalStyl; const { theme, resolveString, setValue, getValue } = useA2UIComponent(node, surfaceId); const props = node.properties; const id = (0, react.useId)(); const valuePath = (_props$value = props.value) === null || _props$value === void 0 ? void 0 : _props$value.path; const initialValue = (_resolveString = resolveString(props.value)) !== null && _resolveString !== void 0 ? _resolveString : ""; const enableDate = (_props$enableDate = props.enableDate) !== null && _props$enableDate !== void 0 ? _props$enableDate : true; const enableTime = (_props$enableTime = props.enableTime) !== null && _props$enableTime !== void 0 ? _props$enableTime : false; const [value, setLocalValue] = (0, react.useState)(initialValue); (0, react.useEffect)(() => { if (valuePath) { const externalValue = getValue(valuePath); if (externalValue !== null && String(externalValue) !== value) setLocalValue(String(externalValue)); } }, [valuePath, getValue]); const handleChange = (0, react.useCallback)((e) => { const newValue = e.target.value; setLocalValue(newValue); if (valuePath) setValue(valuePath, newValue); }, [valuePath, setValue]); let inputType = "date"; if (enableDate && enableTime) inputType = "datetime-local"; else if (enableTime && !enableDate) inputType = "time"; const getPlaceholderText = () => { if (enableDate && enableTime) return "Date & Time"; else if (enableTime) return "Time"; return "Date"; }; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "a2ui-datetime-input", style: node.weight !== void 0 ? { "--weight": node.weight } : {}, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", { className: classMapToString(theme.components.DateTimeInput.container), children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { htmlFor: id, className: classMapToString(theme.components.DateTimeInput.label), children: getPlaceholderText() }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { type: inputType, id, value, onChange: handleChange, className: classMapToString(theme.components.DateTimeInput.element), style: stylesToObject((_theme$additionalStyl = theme.additionalStyles) === null || _theme$additionalStyl === void 0 ? void 0 : _theme$additionalStyl.DateTimeInput) })] }) }); }); //#endregion //#region src/react-renderer/components/interactive/MultipleChoice.tsx /** * MultipleChoice component - a selection component using a dropdown. * * Renders a