Lightweight SVG editor by mizchi/luna, like Excalidraw
Dependencies
git clone <repository-url>
cd moonlight
pnpm install# Start development server
pnpm dev
# Type checking
moon check
# Format code
moon fmt
# Build for production
pnpm build| Tool | Shortcut | Description |
|---|---|---|
| Select | V or 1 | Select and move elements |
| Rectangle | R or 2 | Draw rectangles |
| Circle | C or 3 | Draw circles/ellipses |
| Line | L or 4 | Draw lines and arrows |
| Text | T or 5 | Add text elements |
| Shortcut | Action |
|---|---|
| Ctrl+Z / Cmd+Z | Undo |
| Ctrl+Shift+Z / Cmd+Shift+Z | Redo |
| Ctrl+Y / Cmd+Y | Redo (alternative) |
| Delete / Backspace | Delete selected element |
| Escape | Deselect / Cancel operation |
| Shortcut | Action |
|---|---|
| Arrow Keys | Move selected element (5px) |
| Shift + Arrow Keys | Move selected element (1px, precise) |
| Ctrl + Arrow Keys | Move selected element (10px, fast) |
| Shortcut | Action |
|---|---|
| Ctrl+D / Cmd+D | Duplicate selected element |
| [ | Send backward |
| ] | Bring forward |
| Ctrl+[ / Cmd+[ | Send to back |
| Ctrl+] / Cmd+] | Bring to front |
| Shortcut | Action |
|---|---|
| G | Toggle grid visibility |
| Ctrl+G / Cmd+G | Toggle grid snapping |
<script type="module" src="./main.ts"></script><script type="module" src="./embed.ts"></script>
<div id="editor-container"></div>
<script>
const editor = MoonlightEditor.create(
document.getElementById('editor-container'),
{
width: 800,
height: 600,
theme: 'light', // or 'dark'
readonly: false
}
);
// API
const svg = editor.exportSvg();
editor.importSvg(svgString);
editor.clear();
editor.hasFocus();
</script><script type="module" src="./webcomponent.ts"></script>
<moonlight-editor
width="800"
height="600"
theme="light">
</moonlight-editor>
<script>
const editor = document.querySelector('moonlight-editor');
// API (same as embed mode)
const svg = editor.exportSvg();
editor.importSvg(svgString);
editor.clear();
editor.hasFocus();
</script>| Attribute | Type | Default | Description |
|---|---|---|---|
| width | number | 400 | Canvas width in pixels |
| height | number | 300 | Canvas height in pixels |
| theme | string | "light" | Theme: "light" or "dark" |
| readonly | boolean | false | Disable editing |
<moonlight-editor width="800" height="600"></moonlight-editor>
<script src="https://moonlight.mizchi.workers.dev/moonlight-editor.component.js" async></script><moonlight-editor width="800" height="500">
<template>
<svg viewBox="0 0 800 500">
<rect x="100" y="100" width="120" height="80" fill="#4CAF50" stroke="#2E7D32" stroke-width="2"/>
<circle cx="400" cy="200" r="50" fill="#2196F3" stroke="#1565C0" stroke-width="2"/>
<text x="300" y="350" font-size="24" fill="#333">Hello Moonlight!</text>
</svg>
</template>
</moonlight-editor>
<script src="https://moonlight.mizchi.workers.dev/moonlight-editor.component.js" async></script><button onclick="alert(editor.exportSvg())">Export SVG</button>
<button onclick="editor.clear()">Clear</button>
<moonlight-editor id="editor" width="800" height="600" theme="dark"></moonlight-editor>
<script src="https://moonlight.mizchi.workers.dev/moonlight-editor.component.js" async></script>
<script>
const editor = document.getElementById('editor');
// editor.exportSvg() - Get SVG string
// editor.importSvg(svg) - Load SVG
// editor.clear() - Clear canvas
// editor.hasFocus() - Check focus state
</script>| Attribute | Default | Description |
|---|---|---|
| width | 400 | Canvas width (px) |
| height | 300 | Canvas height (px) |
| theme | "light" | "light" or "dark" |
| readonly | - | Add to disable editing |
pnpm build:all # Build component
pnpm deploy # Deploy to Workersinterface EditorHandle {
// Export current canvas as SVG string
exportSvg(): string;
// Import SVG string to canvas
importSvg(svg: string): void;
// Clear all elements
clear(): void;
// Check if editor has focus
hasFocus(): boolean;
// Register change callback
onChange(callback: () => void): void;
}src/
├── main.mbt # Full editor entry point
├── ui.mbt # UI components (sidebar, toolbar)
├── render.mbt # SVG rendering
├── core/
│ └── scene.mbt # Editor state management
├── model/
│ ├── types.mbt # Data models (Element, Style, etc.)
│ ├── command.mbt # Undo/Redo commands
│ └── element_ops.mbt # Pure element operations
├── lib/ # Shared library code
├── embed/ # Embed mode entry point
├── webcomponent/ # Web Component entry point
└── preview/ # Preview modepub struct EmbedOptions {
width : Int
height : Int
doc_width : Int
doc_height : Int
zoom : Double
theme : String
is_readonly : Bool
initial_svg : String?
}fn copy_svg_as_image(svg_str : String, width : Int, height : Int, on_done : () -> Unit, on_error : () -> Unit) -> Unitfn copy_text_to_clipboard(text : String, on_done : () -> Unit, on_error : () -> Unit) -> Unitfn create_textarea_ffi(style : String, initial_value : String, on_commit : (String) -> Unit, on_escape : (String) -> Unit, on_input : (String) -> Unit) -> Anyfn download_file(content : String, filename : String, mime_type : String) -> Unitfn load_from_indexeddb(db_name : String, store_name : String, key : String, on_success : (String) -> Unit, on_not_found : () -> Unit, on_error : () -> Unit) -> Unitfn observe_window_resize_ffi(callback : (Int, Int) -> Unit) -> Unitfn open_file_dialog(accept : String, on_file : (String) -> Unit) -> Unitfn read_clipboard_text(on_success : (String) -> Unit, on_error : () -> Unit) -> Unitfn render_delete_button(state : EditorState, history : History, element_id : String, close_menu : () -> Unit) -> DomNodefn render_layer_operations(state : EditorState, history : History, element_id : String, close_menu : () -> Unit) -> DomNodefn render_style_editor(state : EditorState, history : History, element_id : String, close_menu : () -> Unit) -> DomNodefn render_text_input(state : EditorState, commit : (String) -> Unit, cancel : () -> Unit) -> DomNodefn save_to_indexeddb(db_name : String, store_name : String, key : String, value : String, on_done : () -> Unit, on_error : () -> Unit) -> Unitfn schedule_focus_with_value_ffi(selector : String, value : String) -> Intfn schedule_textarea_focus_ffi(selector : String, value : String) -> IntLightweight SVG editor by mizchi/luna, like Excalidraw
Dependencies