openpencil/packages/core/src/render/render-jsx.ts
Danila Poyarkov f90e1400b7 P2P collaboration: cursors, follow mode, cleanup
- Broadcast cursor position from canvas mouse move via awareness
- Broadcast selection changes via reactive watcher on selectedIds
- Follow mode: click peer avatar to track their viewport (pan + zoom)
- Zoom broadcast via watcher on store.state.zoom, not just mouse move
- Figma-style cursor arrows: colored fill, white border, name pill
- Stale cursor cleanup: removeAwarenessStates on peer leave
- MQTT signaling (replace Nostr), STUN + TURN ICE servers
- Collab constants extracted to src/constants.ts
- crypto.getRandomValues() replaces Math.random() everywhere
- Provide/inject for collab composable (COLLAB_KEY)
- Update README (collab section, tech stack), CHANGELOG, AGENTS.md
- AGENTS.md: release process, CI workflows, documentation guidelines
2026-03-01 18:11:07 +03:00

76 lines
2.1 KiB
TypeScript

import * as React from './mini-react'
import { renderTree, type RenderResult } from './renderer'
import { resolveToTree, type TreeNode } from './tree'
import type { SceneGraph } from '../scene-graph'
/**
* Build a component function from a JSX string using esbuild.
* Uses mini-react createElement — no React dependency.
* Only works in Node/Bun — not available in the browser.
*/
export async function buildComponent(jsxString: string): Promise<() => unknown> {
let esbuild: typeof import('esbuild')
try {
esbuild = await import('esbuild')
} catch {
throw new Error(
'esbuild is required for JSX string rendering. Install it or use renderTreeNode() instead.'
)
}
const code = `
const h = React.createElement
const Frame = 'frame', Text = 'text', Rectangle = 'rectangle', Ellipse = 'ellipse'
const Line = 'line', Star = 'star', Polygon = 'polygon', Vector = 'vector'
const Group = 'group', Section = 'section', View = 'frame', Rect = 'rectangle'
return function Component() { return ${jsxString.trim()} }
`
const result = esbuild.transformSync(code, {
loader: 'tsx',
jsx: 'transform',
jsxFactory: 'h'
})
return new Function('React', result.code)(React) as () => unknown
}
interface RenderJsxOptions {
x?: number
y?: number
parentId?: string
}
/**
* Render a JSX string into the scene graph.
* For headless/CLI use — requires esbuild.
*/
export async function renderJsx(
graph: SceneGraph,
jsxString: string,
options?: RenderJsxOptions
): Promise<RenderResult> {
const Component = await buildComponent(jsxString)
const element = React.createElement(Component, null)
const tree = resolveToTree(element)
if (!tree) {
throw new Error('JSX must return a Figma element (Frame, Text, etc)')
}
return renderTree(graph, tree, options)
}
/**
* Render a pre-built TreeNode into the scene graph.
* For in-browser use — no esbuild needed, tree is built by the AI SDK tool call.
*/
export function renderTreeNode(
graph: SceneGraph,
tree: TreeNode,
options?: RenderJsxOptions
): RenderResult {
return renderTree(graph, tree, options)
}