npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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

This enables:

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

Installation

pnpm add @onexapis/editor-core

Usage

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

<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 input
  • textarea, html, liquid - Textarea
  • number - Number input
  • range - Range slider
  • select, radio - Dropdown select
  • checkbox, boolean - Checkbox
  • color, 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-check

License

MIT