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

@owomark/react

v0.1.4

Published

React bindings and components for the OwoMark editor stack.

Readme

OwoMark

A self-contained Markdown editor built on a single-layer contenteditable surface.

Install the official package: @owomark/react.

Four packages:

| Package | Role | |---|---| | @owomark/core | Framework-agnostic editor engine (document model, input handling, commands, shared state) | | @owomark/view | View engine + preview rendering (DOM events, active block proxy, DOM patching, render cache) | | @owomark/react | React components (OwoMarkEditor, OwoMarkPreview) and shared state hooks | | (theme built into @owomark/view) | Light / dark CSS theme presets and token definitions |

Quick Start (React)

import { OwoMarkEditor } from '@owomark/react';
import '@owomark/view/style.css';

function App() {
  const [md, setMd] = useState('# Hello\n\nStart typing...');

  return (
    <OwoMarkEditor
      value={md}
      onChange={setMd}
      config={{ theme: 'light', enableSideAnnotation: true }}
      placeholder="Write something..."
    />
  );
}

Props

| Prop | Type | Default | Description | |---|---|---|---| | value | string | — | Controlled markdown value | | defaultValue | string | — | Uncontrolled initial value | | onChange | (markdown: string) => void | — | Called on content change | | onSelectionChange | (selection: OwoMarkSelection) => void | — | Cursor/selection updates | | onCompositionStateChange | (active: boolean) => void | — | IME composition state | | onScroll | React.UIEventHandler<HTMLDivElement> | — | Forwards scroll events from the root editor container | | readOnly | boolean | false | Disable editing | | placeholder | string | — | Empty-state placeholder text | | theme | 'light' \| 'dark' \| string | 'light' | Theme preset or custom class | | themeClassName | string | — | Additional theme class | | className | string | — | Extra CSS class on the root element | | commandsRef | Ref<OwoMarkCommands> | — | Imperative command handle | | coreRef | Ref<OwoMarkCore \| null> | — | Direct access to the underlying OwoMarkCore instance | | controller | OwoMarkSharedStateController | — | Auto-connects editor to shared state (recommended for split editor) | | indentMode | 'auto' \| '2' \| '4' | 'auto' | Tab indent width | | config | OwoMarkEditorConfig | — | Unified editor config (indentMode, enableSideAnnotation, enableMath) | | ariaLabel | string | — | Accessibility label |

When config is provided, it becomes the unified configuration surface for editor behavior:

<OwoMarkEditor
  value={md}
  onChange={setMd}
  config={{
    theme: 'light',
    indentMode: '2',
    enableSideAnnotation: true,
    enableMath: true,
  }}
/>

Imperative Commands

const cmds = useRef<OwoMarkCommands>(null);

<OwoMarkEditor commandsRef={cmds} ... />

// Later:
cmds.current.toggleBold();
cmds.current.toggleItalic();
cmds.current.insertLink('https://example.com');
cmds.current.insertCodeFence('ts');
cmds.current.undo();
cmds.current.redo();
cmds.current.getMarkdown();
cmds.current.setMarkdown('# New content');
cmds.current.replaceMarkdown('# New content', { anchor: 13, focus: 13 });
cmds.current.focus();

Use replaceMarkdown() when the host needs to replace the document and also control the resulting selection in one atomic step, such as toolbar-driven inserts.

Shared State + Preview

For a split editor with synchronized incremental preview:

import {
  OwoMarkEditor,
  OwoMarkPreview,
  useOwoMarkSharedState,
} from '@owomark/react';
import type { PreviewBlock, PreviewRenderContext } from '@owomark/react';

function SplitEditor() {
  const controller = useOwoMarkSharedState({ initialMarkdown: '# Hello' });

  return (
    <div style={{ display: 'flex' }}>
      <OwoMarkEditor
        controller={controller}
        theme="light"
        placeholder="Write something..."
      />
      <OwoMarkPreview
        state={controller}
        themeKey="vitesse-light"
        className="preview-panel"
      />
    </div>
  );
}

The controller prop on OwoMarkEditor automatically connects the editor to the shared state controller. The editor drives all state updates (content, selection, composition) directly — no manual onChangesetMarkdown bridging needed.

For hosts that still need onChange callbacks (e.g. for local draft persistence), they work alongside controller:

<OwoMarkEditor
  controller={controller}
  onChange={(md) => saveDraft(md)}
/>

Custom Block Renderer

Provide a host-side rendering function for full Markdown fidelity (GFM, math, syntax highlighting):

async function renderBlock(
  block: PreviewBlock,
  ctx: PreviewRenderContext,
): Promise<string> {
  const result = await myUnifiedPipeline.process(block.raw);
  return result.toString();
}

<OwoMarkPreview state={controller} renderBlock={renderBlock} themeKey="vitesse-light" />

Side Annotation

OwoMark supports right-side annotations for preview rendering and editor workflows. The feature is enabled by default and can be turned off with config.enableSideAnnotation = false.

<OwoMarkEditor
  value={md}
  onChange={setMd}
  config={{ enableSideAnnotation: false }}
/>

With the flag disabled:

  • side-annotation syntax is kept as plain Markdown text
  • preview does not render side annotation layout
  • the /side slash command is hidden
  • pressing Enter after an annotated block does not auto-insert (>+)

Syntax Quick Reference

| Use case | Syntax | |---|---| | Single block annotation | Text (>} note) | | Multi-block continuation (2-3 blocks) | A (>} group) + B (>+) | | Container annotation (4+ blocks) | :::side + (>} note) + blocks + ::: | | Long-form note reference | :::side [>id] ... ::: + [>id]: {type=}} ... |

Type Table

| Symbol | Meaning | Example | |---|---|---| | : | Plain note | (>: note) | | } | Right brace | (>} grouped note) | | { | Left brace | (>{ expanded note) | | ] | Right bracket | (>] range) | | [ | Left bracket | (>[ range) | | | | Vertical line | (>| comment) | | - | Dash | (>- remark) | | -> | Thin arrow | (>-> conclusion) | | => | Fat arrow | (>=> therefore) | | ~> | Wave arrow | (>~> related) | | ! | Warning | (>! caution) | | ? | Question | (>? todo) |

Examples

Single block:

白菜 (>} 都是蔬菜)

Continuation chain:

香蕉 (>} 都是水果)
菠萝 (>+)
苹果 (>+)

Container form:

:::side
(>! 高危操作)

删除数据库前必须先备份。
确认备份可恢复后再执行破坏性操作。
:::

Reference form:

:::side [>cook-tip]
炒菜时油温很重要。
热锅凉油是基本功。
:::

[>cook-tip]: {type=}}
    烹饪小贴士:油温过高会产生油烟,
    日常烹饪建议控制在合适范围内。

Notes

  • Side annotations are right-side only; there is no left-column layout mode.
  • (>...) must appear at the end of the block to be recognized.
  • (>+) is a continuation marker only. It must follow a previous annotated sibling block.
  • On narrow viewports, side annotations fall back to inline callout blocks under the anchor content.

Shared State Hooks

| Hook | Description | |------|-------------| | useOwoMarkSharedState(options?) | Creates a persistent OwoMarkSharedStateController (survives re-renders) | | useSharedStateSnapshot(controller) | Subscribes via useSyncExternalStore, returns current OwoMarkSharedState |

<OwoMarkPreview> Props

| Prop | Type | Description | |------|------|-------------| | state | OwoMarkSharedStateStore | Shared state store (e.g. controller from useOwoMarkSharedState) | | themeKey | string | Theme identifier for rendering and cache keying | | className | string | CSS class for the preview root | | renderBlock | (block, context) => Promise<string> | Custom per-block Markdown renderer | | registry | PreviewRendererRegistry | Custom renderer registry (e.g. Mermaid) | | viewportFirst | boolean | Prioritize rendering visible blocks first | | onContentUpdate | () => void | Called after every DOM mutation — including idle backfill and deferred renders (for scroll sync) | | ariaLabel | string | Accessibility label |

CSS Setup

Minimal (use package presets)

import '@owomark/view/style.css'; // includes mdx-components.css + owomark.css + side-annotation.css + slash-menu.css + light.css + dark.css

Selective imports

import '@owomark/view/owomark.css'; // base editor styles (required)
import '@owomark/view/light.css';   // light preset tokens
// import '@owomark/view/dark.css'; // dark preset tokens

Third-party theme layering

import '@owomark/view/style.css';
import 'owomark-theme-acme/style.css';
<OwoMarkEditor
  value={md}
  onChange={setMd}
  theme="owo-theme-acme"
/>

The theme package should only override --owo-* tokens and optional visual details. It should not duplicate @owomark/view structural CSS. See the @owomark/view README for the authoring contract of owomark-theme-xxx.

Host Theme Mapping

OwoMark uses --owo-* CSS custom properties for all visual styles. To integrate with your design system, map your variables to OwoMark tokens on a wrapper element:

.my-editor-wrapper {
  /* Surface */
  --owo-editor-bg: var(--app-surface);
  --owo-editor-text: var(--app-text-primary);
  --owo-editor-heading: var(--app-text-strong);

  /* Interaction */
  --owo-editor-caret: var(--app-text-strong);
  --owo-editor-link: var(--app-brand);

  /* Border */
  --owo-editor-border: var(--app-border);

  /* Override default chrome if embedding */
  & .owo-editor-root {
    border: none;
    padding: 0;
    background: transparent;
  }
}

See docs/specs/owomark-theme-tokens.md for the full token list.

Standalone Usage (no React)

Use @owomark/core + @owomark/view for a framework-free DOM editor:

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

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

core.onChange((markdown) => console.log(markdown));

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

Or use createOwoMarkCore() directly for pure logic without DOM:

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

const core = createOwoMarkCore({ initialMarkdown: '# Hello' });
console.log(core.getMarkdown());

Building

From the project root:

npm run build:packages

This builds @owomark/core -> @owomark/view -> @owomark/react in dependency order using tsup (ESM + DTS).