npm package: @onexapis/editor-core
Description: Reusable editor engine core for Onex theme development
Author: Onex Team
Latest version: 0.1.0

# @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 points
```

This enables:

- **Headless editor** (AI, testing)
- **Custom UI** (white-label)
- **CLI editor** (`onex dev`)
- **Full product** (apps/editor)

## Installation

```bash
pnpm add @onexapis/editor-core
```

## Usage

### Headless Usage (AI, Testing, CLI)

```typescript
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)

```tsx
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

```tsx
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)

```tsx
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

```tsx
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

```tsx
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:**

```typescript
new EditorState(config: EditorConfig, theme: ThemeBundle)
```

**Methods:**

- `addSection(section, index?)` - Add a section
- `updateSection(id, updates)` - Update a section
- `removeSection(id)` - Remove a section
- `moveSection(fromIndex, toIndex)` - Move a section
- `duplicateSection(id)` - Duplicate a section
- `toggleSection(id)` - Toggle disabled state
- `undo()` - Undo last change
- `redo()` - Redo last undone change
- `save()` - Call onSave callback
- `subscribe(listener)` - Subscribe to changes
- `toJSON()` - Export to JSON
- `fromJSON(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.

```tsx
<EditorProvider config={config} theme={theme}>
  {children}
</EditorProvider>
```

#### `useEditor()`

Hook to access editor state and methods.

```tsx
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.

```tsx
<SectionRenderer section={sectionInstance} />
```

#### `SectionListRenderer`

Renders a list of sections.

```tsx
<SectionListRenderer sections={sections} />
```

#### `InspectorOverlay`

Visual overlay for hover and click detection.

```tsx
<InspectorOverlay
  active={true}
  selected={selectedElement}
  onSelect={(element) => setSelected(element)}
/>
```

#### `SectionForm`

Renders a form for editing section settings.

```tsx
<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 input
- `textarea`, `html`, `liquid` - Textarea
- `number` - Number input
- `range` - Range slider
- `select`, `radio` - Dropdown select
- `checkbox`, `boolean` - Checkbox
- `color`, `color_background` - Color picker

```tsx
<FieldRenderer
  field={settingDefinition}
  value={currentValue}
  onChange={(newValue) => handleChange(newValue)}
/>
```

#### `DevEditor`

Minimal editor UI for CLI usage.

```tsx
<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:

```typescript
// 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

```bash
# Run tests
pnpm test

# Run tests in watch mode
pnpm test:watch

# Build package
pnpm build

# Type check
pnpm type-check
```

## License

MIT