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

@abduljebar/text-editor

v3.0.1

Published

A professional React text editor with export, save, and validation features

Readme

@abduljebar/text-editor

A clean, minimal React rich-text editor inspired by Asana's text editor. Ships in two modes: a simple 8-button toolbar by default, and an advanced 28-button toolbar when you need full formatting power.

Features

Simple mode (default)

  • Bold, Italic, Underline, Strikethrough
  • Heading (H1)
  • Bullet list, Numbered list
  • Link insertion
  • Clean, focus-preserving toolbar (editor never loses selection on button click)

Advanced mode (advanced prop)

  • Everything in simple mode, plus:
  • H2, Paragraph, Code block, Blockquote
  • Indent / Outdent
  • Align Left / Center / Right
  • Superscript, Subscript
  • Image upload (drag & drop, file picker, paste)
  • Save / Export / Clear action buttons
  • Unsaved changes indicator

Always included

  • Correct word and character count (HTML tags excluded)
  • Placeholder that correctly reappears after clearing
  • Debounced onChange
  • Read-only mode
  • Ref API for programmatic control
  • TypeScript types

Installation

npm install @abduljebar/text-editor
# or
yarn add @abduljebar/text-editor
# or
pnpm add @abduljebar/text-editor

Quick Start

import '@abduljebar/text-editor/dist/index.css';
import { TextEditor } from "@abduljebar/text-editor";

function App() {
  return (
    <TextEditor
      height="500px"
      onChange={(html) => console.log(html)}
    />
  );
}

Usage

Simple mode

import '@abduljebar/text-editor/dist/index.css';
import { TextEditor } from "@abduljebar/text-editor";

function MyEditor() {
  return (
    <TextEditor
      height="500px"
      placeholder="Write something..."
      initialContent="<p>Hello world</p>"
      onChange={(html) => {
        // called on every change, debounced by 300ms
        console.log(html);
      }}
    />
  );
}

Advanced mode

import '@abduljebar/text-editor/dist/index.css';
import { TextEditor } from "@abduljebar/text-editor";

function AdvancedEditor() {
  const handleImageUpload = async (file: File): Promise<string> => {
    const formData = new FormData();
    formData.append('image', file);
    const res = await fetch('/api/upload', { method: 'POST', body: formData });
    const data = await res.json();
    return data.url;
  };

  return (
    <TextEditor
      advanced
      showButtons
      showStatusBar
      height="600px"
      onSave={(html) => console.log('saved:', html)}
      onExport={(html) => console.log('exported:', html)}
      onImageUpload={handleImageUpload}
      onChange={(html) => console.log('changed:', html)}
    />
  );
}

Read-only mode

<TextEditor
  readOnly
  initialContent="<h1>Published Article</h1><p>This content cannot be edited.</p>"
  showStatusBar
/>

API Reference

TextEditor Props

| Prop | Type | Default | Description | |------|------|---------|-------------| | initialContent | string | "" | Initial HTML content | | onChange | (html: string) => void | undefined | Called on every content change (debounced) | | readOnly | boolean | false | Disables editing | | height | string | "500px" | Editor height — any valid CSS value (e.g. "400px", "100%") | | className | string | "" | Extra CSS class on the root element | | placeholder | string | "Start typing here..." | Shown when editor is empty | | autoFocus | boolean | false | Focus editor on mount | | onInit | (editor: HTMLDivElement) => void | undefined | Called after editor mounts | | debounceDelay | number | 300 | onChange debounce delay in ms. Set to 0 to disable | | showStatusBar | boolean | false | Show word/character count bar | | advanced | boolean | false | Enable 28-button advanced toolbar | | showButtons | boolean | false | Show Save / Export / Clear buttons (advanced mode only) | | onSave | (html: string) => void | undefined | Called when Save is clicked (advanced mode) | | onExport | (html: string) => void | undefined | Called when Export is clicked; also triggers .html download (advanced mode) | | onImageUpload | (file: File) => Promise<string> | undefined | Upload handler — receives the file, returns the final URL (advanced mode). If omitted, images are embedded as data URLs | | allowedImageTypes | string[] | ["image/jpeg","image/png","image/gif","image/webp","image/svg+xml"] | Accepted image MIME types (advanced mode) | | maxImageSize | number | 5242880 (5 MB) | Max image size in bytes (advanced mode) |

TextEditorRef

Access the ref API with useRef<TextEditorRef>():

interface TextEditorRef {
  getContent: () => string;        // raw innerHTML of the editor
  getHTML: () => string;           // same as getContent
  clear: () => void;               // clears all content
  focus: () => void;               // focuses editor, cursor at end
  insertText: (text: string) => void;
  insertHTML: (html: string) => void;
  setHTML: (html: string) => void; // replace all content programmatically
  executeCommand: (command: string, value?: string) => void;
}

Example:

import { useRef } from 'react';
import { TextEditor, TextEditorRef } from "@abduljebar/text-editor";

function EditorWithRef() {
  const editorRef = useRef<TextEditorRef>(null);

  return (
    <>
      <TextEditor ref={editorRef} height="400px" />
      <button onClick={() => editorRef.current?.executeCommand('bold')}>
        Bold
      </button>
      <button onClick={() => {
        const html = editorRef.current?.getHTML();
        console.log(html);
      }}>
        Get HTML
      </button>
      <button onClick={() => editorRef.current?.clear()}>
        Clear
      </button>
      <button onClick={() => editorRef.current?.setHTML('<p>New content</p>')}>
        Reset
      </button>
    </>
  );
}

Supported Commands (executeCommand)

| Command | Value | Description | |---------|-------|-------------| | bold | — | Toggle bold | | italic | — | Toggle italic | | underline | — | Toggle underline | | strikeThrough | — | Toggle strikethrough | | formatBlock | "h1", "h2", "p", "pre", "blockquote" | Change block type | | insertUnorderedList | — | Toggle bullet list | | insertOrderedList | — | Toggle numbered list | | indent | — | Indent | | outdent | — | Outdent | | justifyLeft | — | Align left | | justifyCenter | — | Align center | | justifyRight | — | Align right | | superscript | — | Superscript | | subscript | — | Subscript | | createLink | URL string | Insert/wrap link | | insertImage | URL string | Insert image | | undo | — | Undo | | redo | — | Redo |

Styling

Import the bundled CSS before using the component:

import '@abduljebar/text-editor/dist/index.css';

Override styles with the className prop:

<TextEditor className="my-editor" />
.my-editor {
  border: 2px solid #4f46e5;
  border-radius: 12px;
}

/* Target the contenteditable area */
.my-editor .editor-content h1 {
  color: #4f46e5;
}

Integration with form libraries

import { useForm } from 'react-hook-form';
import { TextEditor } from "@abduljebar/text-editor";

function ArticleForm() {
  const { handleSubmit, setValue } = useForm<{ body: string }>();

  return (
    <form onSubmit={handleSubmit((data) => console.log(data))}>
      <TextEditor
        height="400px"
        onChange={(html) => setValue('body', html)}
      />
      <button type="submit">Submit</button>
    </form>
  );
}

Browser Support

Chrome 60+, Firefox 55+, Safari 12+, Edge 79+

Troubleshooting

| Problem | Fix | |---------|-----| | Styles missing | Import @abduljebar/text-editor/dist/index.css | | Toolbar loses selection on click | Update to v3.0.0+ — fixed via onMouseDown approach | | Word count includes HTML tags | Update to v3.0.0+ — fixed, count is text-only | | Placeholder doesn't reappear after clear | Update to v3.0.0+ — fixed, placeholder is based on text content | | onChange fires with wrong signature | Update call to (html: string) — title and content params removed in v3.0.0 |

Changelog

v3.0.0

  • Breaking: onChange signature simplified from (content, html, title?) to (html)
  • Breaking: removed showSaveTitle, imageUploadEndpoint props
  • New: advanced prop — opt into full 28-button toolbar
  • Fix: toolbar buttons now use onMouseDown + e.preventDefault() — editor never loses selection on click
  • Fix: active-format detection replaced stale-closure pattern with single atomic state update
  • Fix: word/character count now strips HTML before counting
  • Fix: placeholder correctly reappears when editor is cleared to <br> or <div><br></div>
  • Fix: external initialContent prop changes no longer blocked by user edits
  • Fix: react/react-dom removed from dependencies (were duplicating peer deps)
  • Removed: lodash runtime dependency

v2.8.0

  • Added setHTML ref API
  • Refactored component styles with modular CSS classes

v2.x

  • Image upload with drag & drop, paste, file picker
  • Pending images tracking
  • Export to HTML with embedded styles
  • Ref API for programmatic control

v1.0.0

  • Initial release

Built with by AbdulJebar Sani

GitHub · npm · Issues