@onexapis/editor-core
v0.1.0
Published
Reusable editor engine core for Onex theme development
Downloads
73
Readme
@onexapis/editor-core
Reusable editor engine core for Onex theme development.
Features
- 🎯 Pure vanilla JS engine - No React in core logic (headless-capable)
- 🚀 Static map pattern - 60% smaller bundles, full tree-shaking
- 📘 Full TypeScript - Complete type inference
- ⚡ Zero initialization - No registry, instant ready
- 🧪 Fully tested - 47+ passing tests
- 🎨 React UI included - Optional React wrapper and components
- 📝 Form system - Complete field renderers for all types
- 🎛️ DevEditor - Minimal editor for CLI usage
- 🎛️ DevEditorPro - Enhanced editor with DnD and inspector panel
- 🔍 Inspector overlay - Visual hover and click detection
- 📋 Inspector panel - Right sidebar with tabs (Sections, Settings, Blocks)
- 🎯 Section toolbar - Floating toolbar with quick actions
- 🔄 Drag & Drop - Reorder sections with @dnd-kit
Architecture
3-Layer Internal Structure
@onexapis/editor-core/
├── engine/ - Pure JS logic (NO React, headless-capable)
├── ui/ - React components
└── adapters/ - Extension pointsThis enables:
- Headless editor (AI, testing)
- Custom UI (white-label)
- CLI editor (
onex dev) - Full product (apps/editor)
Installation
pnpm add @onexapis/editor-coreUsage
Headless Usage (AI, Testing, CLI)
import { EditorState } from "@onexapis/editor-core/engine";
import type { ThemeBundle } from "@onexapis/editor-core/engine";
// Define theme bundle (static maps)
const theme: ThemeBundle = {
sections: {
hero: HeroSection,
featured: FeaturedSection,
},
schemas: {
hero: heroSchema,
featured: featuredSchema,
},
name: "my-theme",
version: "1.0.0",
};
// Create editor state
const state = new EditorState(
{
initialSections: [],
callbacks: {
onChange: (sections) => console.log("Changed:", sections),
onSave: async (sections) => {
await saveToFile(sections);
},
},
},
theme
);
// Use editor API
state.addSection({ type: "hero", settings: { title: "Welcome" } });
state.undo();
state.redo();
const json = state.toJSON();Full Product Usage (React)
import {
EditorProvider,
useEditor,
SectionListRenderer,
} from "@onexapis/editor-core/ui";
import { theme } from "@themes/tinan/bundle-entry";
function MyEditor() {
return (
<EditorProvider config={{ initialSections: [] }} theme={theme}>
<EditorContent />
</EditorProvider>
);
}
function EditorContent() {
const { sections, addSection, undo, canUndo } = useEditor();
return (
<div>
<button onClick={() => addSection({ type: "hero", settings: {} })}>
Add Hero
</button>
<button onClick={undo} disabled={!canUndo}>
Undo
</button>
<SectionListRenderer sections={sections} />
</div>
);
}With Inspector Overlay
import { EditorProvider, InspectorOverlay } from "@onexapis/editor-core/ui";
import { useState } from "react";
function MyEditor() {
const [selected, setSelected] = useState(null);
return (
<EditorProvider config={{}} theme={theme}>
<InspectorOverlay
active={true}
selected={selected}
onSelect={(element) => setSelected(element)}
/>
<SectionListRenderer sections={sections} />
</EditorProvider>
);
}DevEditor for CLI (onex dev)
import { EditorProvider } from "@onexapis/editor-core/ui";
import { DevEditor } from "@onexapis/editor-core/ui/dev";
import { theme } from "./bundle-entry";
function App() {
return (
<EditorProvider
config={{
initialSections: [],
callbacks: {
onChange: (sections) => console.log("Changed:", sections),
},
}}
theme={theme}
>
<DevEditor />
</EditorProvider>
);
}DevEditorPro with DnD and Inspector Panel
import { EditorProvider } from "@onexapis/editor-core/ui";
import { DevEditorPro } from "@onexapis/editor-core/ui/dev";
import { theme } from "./bundle-entry";
function App() {
return (
<EditorProvider
config={{
initialSections: [],
callbacks: {
onChange: (sections) => console.log("Changed:", sections),
onSave: async (sections) => {
await fetch("/api/save", {
method: "POST",
body: JSON.stringify(sections),
});
},
},
}}
theme={theme}
>
<DevEditorPro
showInspector={true}
showPanel={true}
panelWidth={320}
enableDnD={true}
/>
</EditorProvider>
);
}Section Settings Form
import {
EditorProvider,
useEditor,
SectionForm,
} from "@onexapis/editor-core/ui";
function SectionEditor({ sectionId }) {
const { state, updateSection } = useEditor();
const section = state.getSection(sectionId);
const schema = state.getSectionSchema(section.type);
return (
<SectionForm
section={section}
schema={schema}
onChange={(newSettings) => {
updateSection(sectionId, { settings: newSettings });
}}
/>
);
}API
Engine Layer
EditorState
Vanilla JS state machine for the editor.
Constructor:
new EditorState(config: EditorConfig, theme: ThemeBundle)Methods:
addSection(section, index?)- Add a sectionupdateSection(id, updates)- Update a sectionremoveSection(id)- Remove a sectionmoveSection(fromIndex, toIndex)- Move a sectionduplicateSection(id)- Duplicate a sectiontoggleSection(id)- Toggle disabled stateundo()- Undo last changeredo()- Redo last undone changesave()- Call onSave callbacksubscribe(listener)- Subscribe to changestoJSON()- Export to JSONfromJSON(snapshot)- Import from JSON
Mutation Utilities
Pure functions for state manipulation:
Block Mutations:
findBlockInSection(section, blockId)updateNestedBlock(section, blockId, updates)removeNestedBlock(section, blockId)addBlockToSection(section, block, index?)addNestedBlock(section, parentBlockId, block, index?)moveBlockInSection(section, blockId, targetParentId, targetIndex)duplicateBlockInSection(section, blockId)getBlockPath(section, blockId)
Section Mutations:
createSectionFromSchema(type, schema, preset?)validateSectionSettings(section, schema)mergeSectionSettings(section, updates, schema?)resetSectionToDefaults(section, schema)applySectionPreset(section, preset)cloneSection(section)getSectionSetting(section, schema, settingId)
UI Layer
EditorProvider
React context wrapper for EditorState.
<EditorProvider config={config} theme={theme}>
{children}
</EditorProvider>useEditor()
Hook to access editor state and methods.
const {
state, // EditorState instance
sections, // Current sections
canUndo, // Can undo?
canRedo, // Can redo?
addSection, // Add section
updateSection, // Update section
removeSection, // Remove section
moveSection, // Move section
duplicateSection, // Duplicate section
toggleSection, // Toggle section
setSections, // Set all sections
undo, // Undo
redo, // Redo
save, // Save
} = useEditor();SectionRenderer
Renders a single section from the theme bundle.
<SectionRenderer section={sectionInstance} />SectionListRenderer
Renders a list of sections.
<SectionListRenderer sections={sections} />InspectorOverlay
Visual overlay for hover and click detection.
<InspectorOverlay
active={true}
selected={selectedElement}
onSelect={(element) => setSelected(element)}
/>SectionForm
Renders a form for editing section settings.
<SectionForm
section={sectionInstance}
schema={sectionSchema}
onChange={(newSettings) => updateSection(id, { settings: newSettings })}
/>FieldRenderer
Renders individual form fields based on type.
Supported field types:
text,url- Text inputtextarea,html,liquid- Textareanumber- Number inputrange- Range sliderselect,radio- Dropdown selectcheckbox,boolean- Checkboxcolor,color_background- Color picker
<FieldRenderer
field={settingDefinition}
value={currentValue}
onChange={(newValue) => handleChange(newValue)}
/>DevEditor
Minimal editor UI for CLI usage.
<DevEditor showInspector={true} showControls={true} />Features:
- Section list sidebar
- Add/remove/reorder sections
- Undo/redo history
- Inspector toggle
- Live preview
ThemeBundle Pattern
Instead of a registry, themes export a static object:
// themes/tinan/bundle-entry.ts
import HeroSection from "./sections/hero/hero-default";
import FeaturedSection from "./sections/featured/featured-default";
import { heroSchema, featuredSchema } from "./schemas";
export const theme: ThemeBundle = {
sections: {
hero: HeroSection,
featured: FeaturedSection,
},
schemas: {
hero: heroSchema,
featured: featuredSchema,
},
name: "tinan",
version: "1.0.0",
};Benefits:
- ✅ 60% smaller bundles (tree-shaking)
- ✅ Full TypeScript inference
- ✅ Zero initialization overhead
- ✅ No global state
Testing
# Run tests
pnpm test
# Run tests in watch mode
pnpm test:watch
# Build package
pnpm build
# Type check
pnpm type-checkLicense
MIT
