npm package: @owomark/core
Description: Framework-agnostic core engine for the OwoMark editor.
Latest version: 0.1.4

# @owomark/core

Framework-agnostic Markdown editor engine. Provides document model, incremental rendering, input handling, commands, and selection management for a single-layer `contenteditable` editing experience.

Install the official package: `@owomark/core`.

## Install

```bash
npm install @owomark/core
```

## Quick Start

### Pure Logic Core (no DOM)

```ts
import { createOwoMarkCore } from '@owomark/core';

const core = createOwoMarkCore({ initialMarkdown: '# Hello' });

// Intent-driven input API
const result = core.applyBeforeInput(
  { inputType: 'insertText', data: 'X' },
  { anchor: 7, focus: 7 },
);
// result.action: 'handled' | 'allow-native' | 'composition-sync'

console.log(core.getMarkdown()); // '# HelloX'
core.destroy();
```

### With DOM (use @owomark/view)

```ts
import { createOwoMarkCore } from '@owomark/core';
import { createOwoMarkView } from '@owomark/view';

const core = createOwoMarkCore({ initialMarkdown: '# Hello\n\nStart typing...' });
const view = createOwoMarkView(core, document.getElementById('editor')!);

core.onChange((markdown) => {
  console.log('Content changed:', markdown);
});

// Cleanup
view.destroy();
core.destroy();
```

The standalone DOM editor API is also available through `@owomark/view`:

```ts
import { createOwoMarkVanillaEditor } from '@owomark/view';

const editor = createOwoMarkVanillaEditor();
editor.setMarkdown('# Hello');
editor.mount(document.getElementById('editor')!);
editor.destroy();
```

## Core API

### State

| Method | Description |
|--------|-------------|
| `getMarkdown(): string` | Get current Markdown content |
| `getDocument(): OwoMarkDocument` | Get immutable document model |
| `getSelection(): OwoMarkSelection \| null` | Current selection as linear offsets |
| `getVirtualSelection()` | Current selection as `{ blockId, offset }` pairs |
| `getComposition()` | Current IME composition state |
| `getSnapshot()` | Full state snapshot |

### Input Handling

| Method | Description |
|--------|-------------|
| `applyBeforeInput(intent, selection)` | Handle `beforeinput` events |
| `applyKeyDown(intent, selection)` | Handle `keydown` events |
| `applyPaste(intent, selection)` | Handle `paste` events |
| `handleCompositionStart(selection)` | Enter IME composition mode |
| `handleCompositionEnd(selection)` | Exit IME composition mode |
| `syncText(text, selection)` | Sync DOM text back to model (IME fallback) |

### Content Mutation

| Method | Description |
|--------|-------------|
| `setMarkdown(md: string)` | Replace content (resets history) |
| `replaceMarkdown(md, selection)` | Replace content with explicit selection (supports undo) |
| `dispatch(command, payload?)` | Dispatch a named command |

### Events

| Method | Description |
|--------|-------------|
| `onChange(cb)` | Subscribe to content changes |
| `onSelectionChange(cb)` | Subscribe to selection changes |
| `onCompositionStateChange(cb)` | Subscribe to IME composition state |
| `onDocumentChange(cb)` | Subscribe to document model changes (sovereign mode) |
| `onStateChange(cb)` | Subscribe to any state change |

## Pure Function Exports

The package also exports pure functions that can be used independently:

### Commands

```ts
import {
  handleMarkdownEnter,
  applyMarkdownIndent,
  handleCharInput,
  handleSmartBackspace,
  toggleBold,
  toggleItalic,
  insertLink,
  insertCodeFence,
} from '@owomark/core';
```

### Parser

```ts
import { tokenizeBlock, tokenizeInline } from '@owomark/core';
```

### Model

```ts
import {
  parseMarkdownToDocument,
  serializeDocument,
  getBlockAtOffset,
} from '@owomark/core';
```

### Clipboard

```ts
import { normalizeMarkdownPaste } from '@owomark/core';
```

## Supported Markdown

- Headings (`# ` through `###### `)
- Bold (`**text**`) and italic (`*text*`)
- Inline code (`` `code` ``)
- Links (`[text](url)`)
- Unordered and ordered lists
- Blockquotes (`> `)
- Fenced code blocks (`` ``` ``)
- Thematic breaks (`---`)

## Preview Projection

Convert a parsed document into preview-ready blocks for incremental rendering:

```ts
import {
  createSharedStateStore,
  projectToPreviewBlocks,
  computePreviewDirtyRange,
  deriveBlockId,
} from '@owomark/core';
```

### Shared State Store

```ts
const store = createSharedStateStore({ initialMarkdown: '# Hello' });

// Connect an editor instance
const disconnect = store.connectEditor(editorInstance);

// Subscribe to state changes (consumed by preview engines)
const unsubscribe = store.subscribe((state) => {
  console.log('Version:', state.version);
  console.log('Preview blocks:', state.previewBlocks.length);
});

// Cleanup
unsubscribe();
disconnect();
```

### Block Projection

```ts
const blocks = projectToPreviewBlocks(document, 'vitesse-light');
const dirtyRange = computePreviewDirtyRange(previousBlocks, blocks);
```

- Adjacent same-type container blocks (lists, blockquotes) are grouped into a single `PreviewBlock`
- Each block has a content-based `blockId` (`{renderKey}#{occurrenceIndex}`) for stable DOM reconciliation
- `renderKey` (djb2 hash of `kind:theme:raw`) drives cache invalidation

### Preview Types

- `PreviewBlock` — block identity, kind, raw content, line range, render key
- `PreviewBlockKind` — `'paragraph' | 'heading' | 'code-fence' | ...`
- `PreviewDirtyReason` — `'text-edit' | 'structure-edit' | 'theme-change' | 'renderer-change' | 'custom-block-change' | 'full-rebuild'`
- `OwoMarkSharedState` — full shared state snapshot (document, preview blocks, selection, version)
- `OwoMarkSharedStateStore` — subscribe/getState store interface
- `OwoMarkSharedStateController` — store + `setMarkdown()`, `connectEditor()`, `updateVisibleBlockIds()`, `setThemeKey()`, `notifyRendererChange()`, `notifyCustomBlockChange()`

## Design Principles

- **Input-first**: IME composition is never interrupted by rendering. Patches only run after `compositionend`.
- **Incremental**: Only dirty blocks are re-parsed and re-rendered. No full DOM rebuilds on each keystroke.
- **Framework-free**: Zero runtime dependencies. Works with any UI framework or vanilla JS.