editor-ts
v0.0.13
Published
TypeScript library for editing HTML content with JSON representation
Maintainers
Readme
EditorTs
DO NOT USE THIS IN PRODUCTION YET. Early development.
EditorTs is a TypeScript library for editing HTML content while keeping the source of truth in clean, portable JSON (components/styles/assets). The editor runtime (toolbars, permissions, UI layout, event handlers) stays in JavaScript.
Quickstart (run the demo)
bun install
bun run devOpen http://localhost:5021.
The demo UI is index.html and is wired by examples/quickstart.ts.
Core concepts
Data vs runtime config
- JSON (“data”): components, styles/CSS, assets.
- JS (“runtime”): toolbars, UI wiring, event handlers, editor behaviors.
This separation is intentional: the same JSON can be used in different apps with different editor experiences.
Components-first rendering
When both are present:
componentsare the source of truth.Page.getHTML()renders from components.
When only HTML is present:
- HTML can be converted to components when DOM is available.
Usage
Minimal init
import { init, type PageData } from 'editorts'
const editor = init({
iframeId: 'preview-iframe',
data: pageData satisfies PageData,
})Storage adapters
Local storage is the default, but we recommend SQLocal for persistent, browser-native SQLite storage. SQLocal requires cross-origin isolation headers, so the easiest way is to use the Vite examples in examples/localsql or examples/solid.
import { init } from 'editorts'
import { SQLocal } from 'sqlocal'
const sqlocalClient = new SQLocal('editorts.sqlite')
const editor = init({
iframeId: 'preview-iframe',
data: pageData,
storage: {
type: 'sqlocal',
client: sqlocalClient,
// databaseName: 'editorts.sqlite', // when not passing client
},
})Run the SQLocal demos (Vite):
cd examples/localsql
bun install
bun run devcd examples/solid
bun install
bun run devOpen http://localhost:5173.
Toolbars (runtime only)
import { init } from 'editorts'
const editor = init({
iframeId: 'preview-iframe',
data: pageData,
toolbars: {
byId: {
header: {
enabled: true,
actions: [
{ id: 'edit', label: 'Edit', icon: '✏️', enabled: true },
{ id: 'duplicate', label: 'Duplicate', icon: '📋', enabled: true },
],
},
},
},
})UI containers (you own the layout)
EditorTs does not create your sidebar/tabs/layout. You provide containers and init() wires them.
const editor = init({
iframeId: 'preview-iframe',
data: pageData,
ui: {
stats: { containerId: 'stats-container' },
layers: { containerId: 'layers-container' },
selectedInfo: { containerId: 'selected-info' },
viewTabs: {
editorButtonId: 'tab-editor',
codeButtonId: 'tab-code',
defaultView: 'editor',
},
editors: {
js: { containerId: 'js-editor-container' },
css: { containerId: 'css-editor-container' },
json: { containerId: 'json-editor-container' },
jsx: { containerId: 'jsx-editor-container' },
},
},
})Built-in code editors
EditorTs can render editors into your containers:
- Default:
textarea(zero deps) - Optional:
modern-monaco(syntax highlighting)
const editor = init({
iframeId: 'preview-iframe',
data: pageData,
codeEditor: { provider: 'modern-monaco' },
})Notes:
modern-monacois an optional peer dependency.typescriptis an optional peer dependency (used for TSX/JSX parsing).
Component conversions
Components → HTML
const html = editor.page.components.toHTML()HTML → Components
// Requires DOM (browser). Server-side: inject an adapter or it will warn and no-op.
editor.page.components.setFromHTML('<body><div id="root">Hello</div></body>')Components → JSX/TSX
const jsxSource = editor.page.components.toJSX({ pretty: true })toJSX() outputs React-style function components named from attributes.id when possible.
JSX/TSX → Components
// Uses optional peer dependency `typescript`.
await editor.page.components.setFromJSX(`
export function Header() {
return <div id="header">Hello</div>
}
`)Server sync (Bun + Cloudflare)
EditorTs ships lightweight websocket utilities for server-side sync.
Bun server
import { createBunSyncServer, createSyncMessage } from 'editorts'
const server = createBunSyncServer({
port: 8787,
onSync: async (message) => {
console.log('received', message.payload)
},
})
// elsewhere, send a message
const payload = createSyncMessage(pageData)Cloudflare worker
import { createCfSyncWorker } from 'editorts'
export default createCfSyncWorker({
onSync: async (message) => {
console.log('received', message.payload)
},
})Message helpers
import { createSyncMessage, parseSyncEnvelope } from 'editorts'
const message = createSyncMessage(pageData)
const parsed = parseSyncEnvelope(JSON.stringify(message))AI provider (OpenCode)
Optional integration via @opencode-ai/sdk.
import { createOpencodeClient } from '@opencode-ai/sdk'
const editor = init({
iframeId: 'preview-iframe',
data: pageData,
aiProvider: {
provider: 'opencode',
mode: 'client',
baseUrl: 'http://localhost:4096',
// Optional: pass your own client
client: createOpencodeClient({ baseUrl: 'http://localhost:4096' }),
},
})
// Later
const client = await editor.ai?.getClient()Events
The editor emits typed events:
componentSelectcomponentEdit,componentEditJScomponentDuplicate,componentDeletecomponentReorderpageEditCSS,pageEditJSONpageSaved,pageLoaded
See src/types.ts for the full event map.
Development
bun run build
bun run testProject map
- Core entry:
src/core/init.ts - Page model:
src/core/Page.ts - Data managers:
src/core/ComponentManager.ts,src/core/StyleManager.ts,src/core/AssetManager.ts - Storage:
src/core/StorageManager.ts - Demo:
index.html+examples/quickstart.ts - Architecture + workflow:
AGENTS.md
