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

@mdaemon/html-editor-react

v1.0.2

Published

React wrapper for MDHTMLEditor

Downloads

255

Readme

MDHTMLEditor React

A React wrapper for MDHTMLEditor — a WYSIWYG HTML editor built on TipTap. Provides a drop-in replacement for @tinymce/tinymce-react with no license key required.

Installation

npm install @mdaemon/html-editor-react @mdaemon/html-editor

Peer dependencies: react and react-dom (v18 or v19).

Styles

You must import the editor stylesheet for the toolbar and UI to render correctly:

import '@mdaemon/html-editor/dist/styles.css';

Quick Start

import { useRef } from 'react';
import { Editor } from '@mdaemon/html-editor-react';
import type { EditorRef } from '@mdaemon/html-editor-react';
import '@mdaemon/html-editor/dist/styles.css';

function App() {
  const editorRef = useRef<EditorRef>(null);

  return (
    <Editor
      ref={editorRef}
      config={{ height: 400 }}
      initialValue="<p>Hello World</p>"
      onChange={(html) => console.log('Content:', html)}
      onInit={(editor) => console.log('Ready!', editor)}
    />
  );
}

<Editor> Component

The primary way to use the editor. It accepts a config object and event callbacks, and exposes imperative methods via a ref.

Props

| Prop | Type | Required | Description | |------|------|----------|-------------| | config | EditorConfig | Yes | Configuration object passed to the underlying HTMLEditor. See Configuration. | | body | string | No | Initial HTML content. Takes precedence over initialValue. | | initialValue | string | No | Initial HTML content (alias for body). | | name | string | No | When set, renders a hidden <input> with this name containing the editor content — useful for form submission. | | disabled | boolean | No | Visually disables the editor (pointer-events off, reduced opacity). | | onChange | (content: string) => void | No | Called when content changes (debounced). | | onDirty | (dirty: boolean) => void | No | Called when the dirty state changes. | | onInit | (editor: HTMLEditor) => void | No | Called when the editor finishes initialization. Receives the editor instance. | | onFocus | () => void | No | Called when the editor receives focus. | | onBlur | () => void | No | Called when the editor loses focus. | | translate | (key: string) => string | No | Sets a global translation function for all editors on the page. | | getFileSrc | (path: string) => string | No | Sets a global file path resolver (e.g., for CDN prefixing). |

Ref Methods (EditorRef)

Attach a ref to access imperative methods:

const editorRef = useRef<EditorRef>(null);

// Later...
const html = editorRef.current?.getContent();
editorRef.current?.setContent('<p>New content</p>');
editorRef.current?.insertContent('<p>Inserted at cursor</p>');
editorRef.current?.focus();

// Access the underlying HTMLEditor instance directly
const rawEditor = editorRef.current?.getEditor();

| Method | Signature | Description | |--------|-----------|-------------| | getEditor | () => HTMLEditor \| null | Access the raw HTMLEditor instance for advanced usage. | | getContent | () => string | Get current HTML content. | | setContent | (html: string) => void | Replace the editor content. | | insertContent | (html: string) => void | Insert HTML at the current cursor position. | | focus | () => void | Focus the editor. |

Full Example

import { useRef, useState } from 'react';
import { Editor } from '@mdaemon/html-editor-react';
import type { EditorRef } from '@mdaemon/html-editor-react';
import '@mdaemon/html-editor/dist/styles.css';

function EmailComposer() {
  const editorRef = useRef<EditorRef>(null);
  const [dirty, setDirty] = useState(false);

  const handleSave = () => {
    const content = editorRef.current?.getContent() ?? '';
    console.log('Saving:', content);
  };

  return (
    <div>
      <Editor
        ref={editorRef}
        config={{
          height: 500,
          basicEditor: false,
          skin: 'oxide',
          fontName: 'Arial',
          fontSize: '12pt',
        }}
        initialValue="<p>Dear recipient,</p>"
        onChange={(html) => console.log('Changed:', html)}
        onDirty={(d) => setDirty(d)}
        onInit={() => console.log('Editor ready')}
      />
      <button onClick={handleSave} disabled={!dirty}>
        Save
      </button>
    </div>
  );
}

useEditor Hook

For more programmatic control, use the useEditor hook. You provide a container element via the returned containerRef.

Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | config | EditorConfig | {} | Editor configuration. | | content | string | '' | Initial HTML content. | | onUpdate | (html: string) => void | — | Called on content change. |

Return Value

| Property | Type | Description | |----------|------|-------------| | editor | HTMLEditor \| null | The raw editor instance (null until initialized). | | containerRef | RefObject<HTMLDivElement \| null> | Attach this to your container div. | | ready | boolean | true once the editor has fired its init event. | | getContent | () => string | Get current HTML content. | | setContent | (html: string) => void | Replace the editor content. | | insertContent | (html: string) => void | Insert HTML at the cursor. | | focus | () => void | Focus the editor. | | isDirty | () => boolean | Check if the editor has unsaved changes. |

Example

import { useState } from 'react';
import { useEditor } from '@mdaemon/html-editor-react';
import '@mdaemon/html-editor/dist/styles.css';

function NotesEditor() {
  const [lastSaved, setLastSaved] = useState('');
  const { containerRef, ready, getContent, setContent, isDirty } = useEditor({
    config: { height: 300, basicEditor: true },
    content: '<p>Start taking notes...</p>',
    onUpdate: (html) => console.log('Updated:', html),
  });

  const handleSave = () => {
    setLastSaved(getContent());
  };

  const handleReset = () => {
    setContent('<p>Start taking notes...</p>');
  };

  return (
    <div>
      <div ref={containerRef} />
      {ready && (
        <div>
          <button onClick={handleSave}>Save</button>
          <button onClick={handleReset}>Reset</button>
          <span>{isDirty() ? 'Unsaved changes' : 'Saved'}</span>
        </div>
      )}
    </div>
  );
}

Global Convenience Functions

For single-editor pages, you can get and set content without a ref:

import { getEditorContent, setEditorContent } from '@mdaemon/html-editor-react';

// Get content from the active editor
const html = getEditorContent();

// Set content on the active editor
setEditorContent('<p>New content</p>');

Note: These functions operate on the most recently mounted <Editor> instance. They are intended for pages with a single editor.

Configuration

The config prop (or useEditor's config option) accepts an EditorConfig object. All options from @mdaemon/html-editor are supported:

| Option | Type | Default | Description | |--------|------|---------|-------------| | basicEditor | boolean | false | Use a simplified toolbar (no images, tables, code blocks). | | height | string \| number | 300 | Editor height. | | language | string | 'en' | UI language code. 31 languages built in. | | skin | 'oxide' \| 'oxide-dark' | 'oxide' | Toolbar and dialog theme. | | content_css | 'default' \| 'dark' | 'default' | Content area theme. | | content_style | string | — | Custom CSS injected into the editing surface. | | fontName | string | — | Default font family. | | fontSize | string | — | Default font size. | | font_family_formats | string | (TinyMCE defaults) | Semicolon-delimited font list (Name=family,...). | | font_size_formats | string | '8pt 9pt 10pt 12pt 14pt 18pt 24pt 36pt' | Space-delimited size options. | | directionality | 'ltr' \| 'rtl' | 'ltr' | Text direction. | | toolbar | string | (preset) | Custom toolbar layout string. | | toolbar_mode | 'sliding' \| 'floating' \| 'wrap' | 'wrap' | Toolbar overflow behavior. | | toolbar_sticky | boolean | true | Pin toolbar at top when scrolling. | | auto_focus | string | — | Auto-focus on init. | | browser_spellcheck | boolean | true | Enable browser spell check. | | entity_encoding | 'raw' \| 'named' \| 'numeric' | 'raw' | HTML entity encoding mode. | | includeTemplates | boolean | false | Show the template dropdown. | | templates | Template[] | [] | Predefined HTML templates. | | dropbox | boolean | false | Enable Dropbox integration. | | images_upload_url | string | — | Server endpoint for image uploads. | | images_upload_credentials | boolean | true | Include credentials with upload requests. | | images_upload_base_path | string | '/' | Prefix for uploaded image URLs. | | images_upload_max_size | number | 10485760 | Max upload size in bytes (10 MB). | | images_upload_headers | Record<string, string> | — | Extra headers for upload requests. | | setup | (editor) => void | — | Pre-init callback for registering custom toolbar buttons. |

Dark Theme

<Editor
  config={{
    skin: 'oxide-dark',
    content_css: 'dark',
  }}
/>

Custom Content Styles

<Editor
  config={{
    content_style: 'body { font-family: Georgia, serif; font-size: 16px; line-height: 1.6; }',
  }}
/>

Templates

Enable the template dropdown and provide a templates array:

import type { Template } from '@mdaemon/html-editor-react';

const templates: Template[] = [
  {
    title: 'Greeting',
    description: 'A friendly greeting',
    content: '<p>Hello! Thank you for reaching out.</p>',
  },
  {
    title: 'Signature',
    content: '<p>Best regards,<br/>Your Name</p>',
  },
];

<Editor
  config={{
    includeTemplates: true,
    templates,
  }}
/>

The Template interface:

interface Template {
  id?: number | string;
  title: string;
  description?: string;
  content: string;
}

Custom Toolbar Buttons

Register custom buttons via the setup callback in config:

<Editor
  config={{
    setup: (editor) => {
      editor.ui.registry.addButton('myButton', {
        tooltip: 'Insert greeting',
        text: 'Greet',
        onAction: (api) => {
          editor.insertContent('<p>Hello from a custom button!</p>');
        },
        onSetup: (api) => {
          // api.isEnabled(), api.setEnabled(bool)
          // api.isActive(), api.setActive(bool)
        },
      });
    },
    toolbar: 'bold italic | myButton',
  }}
/>

Button Options

| Option | Type | Description | |--------|------|-------------| | tooltip | string | Hover text. | | text | string | Button label. | | icon | string | Image URL (used instead of text). | | disabled | boolean | Initial disabled state. | | onSetup | (api) => void \| (() => void) | Called on creation; may return a teardown function. | | onAction | (api) => void | Called on click. |

Button API

The api object passed to onSetup and onAction:

| Method | Description | |--------|-------------| | isEnabled() | Returns current enabled state. | | setEnabled(enabled) | Enable or disable the button. | | isActive() | Returns active/pressed state. | | setActive(active) | Toggle active/pressed visual style. |

Custom Toolbar Layout

Provide a toolbar string to control which buttons appear and in what order. Use | to group buttons and || to create a collapsible overflow section:

<Editor
  config={{
    toolbar: 'bold italic underline | fontfamily fontsize || forecolor backcolor | undo redo',
  }}
/>

Buttons after || begin collapsed behind a toggle (...) button.

Available Toolbar Buttons

| Button | Action | |--------|--------| | bold | Toggle bold | | italic | Toggle italic | | underline | Toggle underline | | strikethrough | Toggle strikethrough | | bullist | Bullet list | | numlist | Numbered list | | outdent | Decrease indent | | indent | Increase indent | | blockquote | Toggle block quote | | fontfamily | Font family dropdown | | fontsize | Font size dropdown | | lineheight | Line height dropdown | | template | Template dropdown (requires includeTemplates: true) | | alignleft | Left align | | aligncenter | Center align | | alignright | Right align | | alignjustify | Justify | | forecolor | Text color picker | | backcolor | Highlight color picker | | removeformat | Strip all formatting | | copy | Copy selection | | cut | Cut selection | | paste | Paste from clipboard | | undo | Undo | | redo | Redo | | image | Insert image (upload or URL) | | charmap | Special character picker | | emoticons | Emoji picker with search | | code | Toggle code block | | link | Insert/edit hyperlink | | codesample | Toggle code sample | | fullscreen | Toggle fullscreen | | preview | Preview in new window | | searchreplace | Find & Replace dialog | | ltr | Left-to-right direction | | rtl | Right-to-left direction |

Image Upload

The image toolbar button opens a dialog supporting drag-and-drop upload or direct URL entry. Supported formats: JPEG, PNG, GIF, WebP, SVG.

When images_upload_url is configured, files are posted as multipart/form-data. The server must return JSON with a location, url, or link field. Without an upload URL, images are embedded as base64 data URIs.

SVGs are automatically sanitized (script tags, event handlers, and dangerous elements are stripped).

<Editor
  config={{
    images_upload_url: '/api/upload',
    images_upload_credentials: true,
    images_upload_base_path: '/files/',
    images_upload_max_size: 5 * 1024 * 1024,
    images_upload_headers: {
      'X-CSRF-Token': csrfToken,
    },
  }}
/>

Localization

Set Language at Init

<Editor config={{ language: 'de' }} />

Custom Translation Function

import { setTranslate } from '@mdaemon/html-editor-react';

setTranslate((key) => myTranslations[key] ?? key);

// Or via the Editor prop:
<Editor translate={(key) => myTranslations[key] ?? key} config={{}} />

Change Language at Runtime

const editorRef = useRef<EditorRef>(null);

// Switch to French
editorRef.current?.getEditor()?.setLanguage('fr');

Supported Languages

| Code | Language | Code | Language | |------|----------|------|----------| | en | English | nl | Nederlands | | ar | العربية | nb | Norsk bokmal | | ca | Catala | pl | Polski | | zh | Chinese | pt | Portugues | | cs | Cesky | ro | Romana | | da | Dansk | ru | Russian | | en-gb | English (UK) | sr | Srpski | | fi | Suomi | sl | Slovenscina | | fr | Francais | es | Espanol | | fr-ca | Canadien francais | sv | Svenska | | de | Deutsch | zh-tw | Chinese (Taiwan) | | el | Greek | th | Thai | | hu | Magyar | tr | Turkce | | id | Bahasa Indonesia | vi | Tieng Viet | | it | Italiano | | | | ja | Japanese | | | | ko | Korean | | |

File Source Resolver

Transform image src attributes globally, useful for CDN prefixing or relative path resolution:

import { setGetFileSrc } from '@mdaemon/html-editor-react';

setGetFileSrc((path) => `https://cdn.example.com${path}`);

// Or via the Editor prop:
<Editor getFileSrc={(path) => `https://cdn.example.com${path}`} config={{}} />

Keyboard Shortcuts

| Shortcut | Action | |----------|--------| | Ctrl/Cmd + B | Bold | | Ctrl/Cmd + I | Italic | | Ctrl/Cmd + U | Underline | | Ctrl/Cmd + Z | Undo | | Ctrl/Cmd + Shift + Z | Redo | | Ctrl/Cmd + F | Find & Replace |

Form Integration

Use the name prop to render a hidden <input> containing the editor content, useful for traditional form submission:

<form onSubmit={handleSubmit}>
  <Editor
    config={{}}
    name="email_body"
    initialValue="<p>Draft content</p>"
  />
  <button type="submit">Send</button>
</form>

Exports

// Components
import { Editor, MDEditor } from '@mdaemon/html-editor-react'; // MDEditor is an alias

// Hook
import { useEditor } from '@mdaemon/html-editor-react';

// Global functions
import { getEditorContent, setEditorContent } from '@mdaemon/html-editor-react';

// Utilities (re-exported from @mdaemon/html-editor)
import { fontNames, setTranslate, setGetFileSrc } from '@mdaemon/html-editor-react';

// Types
import type {
  EditorProps,
  EditorRef,
  UseEditorOptions,
  UseEditorReturn,
  EditorConfig,
  EditorEvents,
  Template,
  ToolbarButtonSpec,
  ToolbarButtonAPI,
} from '@mdaemon/html-editor-react';

Migration from @tinymce/tinymce-react

// Before (TinyMCE)
import { Editor } from '@tinymce/tinymce-react';

<Editor
  apiKey="your-key"
  init={{ height: 400, plugins: 'link image table' }}
  initialValue="<p>Hello</p>"
  onEditorChange={(content) => save(content)}
/>

// After (MDHTMLEditor React)
import { Editor } from '@mdaemon/html-editor-react';
import '@mdaemon/html-editor/dist/styles.css';

<Editor
  config={{ height: 400 }}
  initialValue="<p>Hello</p>"
  onChange={(content) => save(content)}
/>

Key differences:

  • No apiKey or licenseKey required
  • init prop is renamed to config
  • onEditorChange is renamed to onChange
  • plugins option is not needed — all features are built in
  • Toolbar customization uses basicEditor: true/false or a toolbar string

Running the Demo

A demo app is included to see the editor in action:

npm run demo

This starts a Vite dev server at http://localhost:5173 with examples of both the <Editor> component and useEditor hook.

License

LGPL-3.0-or-later — MDaemon Technologies, Ltd.