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

@can3p/headless-mde

v0.0.4

Published

UI headless React markdown editor using only textarea

Downloads

408

Readme

Textarea Markdown

This is a fork of https://github.com/Resetand/textarea-markdown-editor

The aim is to trim fix some of the quirks with the scroll position, there will be breaking changes, no compatibility planned.


Textarea Markdown is a simple markdown editor using only <textarea/>. It extends textarea by adding formatting features like shortcuts, list-wrapping, invoked commands and other to make user experience better 🙃

Essentially this library just provides the textarea Component. You can choose any markdown parser, create your own layout, and use your own textarea component that is styled and behaves however you like

Features

  • Lists wrapping
  • Auto formatting pasted links
  • Indent tabulation
  • Keyboard shortcuts handling
  • 17 built-in customizable commands

Usage

import React, { Fragment, useRef, useState } from 'react';
import TextareaMarkdown, { TextareaMarkdownRef } from '@can3p/headless-mde';

function App() {
    const [value, setValue] = useState('');
    const ref = useRef<TextareaMarkdownRef>(null);

    return (
        <Fragment>
            <button onClick={() => ref.current?.trigger('bold')}>Bold</button>
            <br />
            <TextareaMarkdown ref={ref} value={value} onChange={(e) => setValue(e.target.value)} />
        </Fragment>
    );
}

ℹ️ Ref instance provide the trigger function to invoke commands

Custom textarea Component

You can use custom textarea Component. Just wrap it with TextareaMarkdown.Wrapper

import React, { useRef, useState } from 'react';
import TextareaMarkdown, { TextareaMarkdownRef } from '@can3p/headless-mde';
import TextareaAutosize from 'react-textarea-autosize';

function App() {
    const [value, setValue] = useState('');
    const ref = useRef<TextareaMarkdownRef>(null);

    return (
        <TextareaMarkdown.Wrapper ref={ref}>
            <TextareaAutosize value={value} onChange={(e) => setValue(e.target.value)} />
        </TextareaMarkdown.Wrapper>
    );
}

ℹ️ This solution will not create any real dom wrapper

Customize commands

You can specify or overwrite shortcuts for built-in commands or create your own

import React, { useRef, useState } from 'react';
import TextareaMarkdown, { CommandHandler, TextareaMarkdownRef } from '@can3p/headless-mde';

/** Inserts 🙃 at the current position and select it */
const emojiCommandHandler: CommandHandler = ({ cursor }) => {
    // MARKER - means a cursor position, or a selection range if specified two markers
    cursor.insert(`${cursor.MARKER}🙃${cursor.MARKER}`);
};

function App() {
    const [value, setValue] = useState('');
    const ref = useRef<TextareaMarkdownRef>(null);

    return (
        <Fragment>
            <button onClick={() => ref.current?.trigger('insert-emoji')}>Insert 🙃</button>
            <br />
            <TextareaMarkdown
                ref={ref}
                value={value}
                onChange={(e) => setValue(e.target.value)}
                commands={[
                    {
                        name: 'code',
                        shortcut: ['command+/', 'ctrl+/'],
                        shortcutPreventDefault: true,
                    },
                    {
                        name: 'insert-emoji',
                        handler: emojiCommandHandler,
                    },
                ]}
            />
        </Fragment>
    );
}

ℹ️ Note that mutation element.value will not trigger change event on textarea element. Use cursor.setValue(...) or other method of Cursor.

ℹ️ Mousetrap.js is used under the hood for shortcuts handling. It is great solution with simple and intuitive api. You can read more about combination in the documentation

Usage without React

For projects that don't use React, import from the headless entry point:

import { bootstrapTextareaMarkdown } from '@can3p/headless-mde/headless';

const textarea = document.querySelector('textarea'); // element can be obtained from anywhere, this is just an example;

const { trigger, dispose } = bootstrapTextareaMarkdown(textarea, {
    options: {}, // optional options config
    commands: [], // optional commands configs
});

ℹ️ The headless entry point has no React dependencies, so you won't get peer dependency warnings.


👀 You can find more examples here


API

TextareaMarkdownProps

ℹ️ TextareaMarkdown accepts all props which native textarea supports

options TextareaMarkdownOptions

Options config

commands Command[]

Array of commands configuration


Command

| Name | Type | Description | | :-------------------------- | :---------------------------------- | :-------------------------------------------------------------------- | | name | TType | Built-in or custom command name | | shortcut? | string | string[] | Shortcut combinations (Mousetrap.js) | | shortcutPreventDefault? | boolean | Toggle key event prevent default:false | | handler? | CommandHandler | Handler function for custom commands | | enable? | boolean | Toggle command enabling |


CommandHandler

export type CommandHandler = (context: CommandHandlerContext) => void | Promise<void>;

export type CommandHandlerContext = {
    textarea: HTMLTextAreaElement;
    cursor: Cursor;
    keyEvent?: KeyboardEvent;
    clipboardEvent?: ClipboardEvent;
    options: TextareaMarkdownOptions;
};

Built-in commands

| Name | Description | Shortcut | | ------------------ | ------------------------------------------------------------------ | ---------------------- | | bold | Inserts or wraps bold markup | ctrl/command+b | | italic | Inserts or wraps italic markup | ctrl/command+i | | strike-through | Inserts or wraps strike-through markup | ctrl/command+shift+x | | link | Inserts or wraps link markup | | | image | Inserts or wraps image markup | | | unordered-list | Inserts or wraps unordered list markup | | | ordered-list | Inserts or wraps ordered list markup | | | code-block | Inserts or wraps code block markup | | | code-inline | Inserts or wraps inline code markup | | | code | Inserts or wraps inline or block code markup dependent of selected | | | block-quotes | Inserts or wraps block-quotes markup | | | h1 | Inserts h1 headline | | | h2 | Inserts h2 headline | | | h3 | Inserts h3 headline | | | h4 | Inserts h4 headline | | | h5 | Inserts h5 headline | | | h6 | Inserts h6 headline | |


TextareaMarkdownOptions

| Name | Type | Description | | :------------------------------------------ | :------------------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------- | | preferredBoldSyntax | "**" | "__" | Preferred bold wrap syntax default: '**' | | preferredItalicSyntax | "*" | "_" | Preferred italic wrap syntax default: '*' | | preferredUnorderedListSyntax | "-" | "*" | "+" | Preferred unordered list prefix default: '-' | | enableIndentExtension | boolean | Will handle tab and shift+tab keystrokes, on which will insert/remove indentation instead of the default behavior default:true | | enableLinkPasteExtension | boolean | Will handle paste event, on which will wrap pasted with link/image markup if pasted is URL default:true | | enablePrefixWrappingExtension | boolean | Will handle enter keystroke, on which will wrap current list sequence if needed default:true | | enableProperLineRemoveBehaviorExtension | boolean | Will handle command/ctrl+backspace keystrokes, on which will remove only a current line instead of the default behavior default:true | | customPrefixWrapping | (PrefixWrappingConfig | string)[] | Array of custom prefixes, that need to be wrapped. (Will not work with enablePrefixWrappingExtension:false) | | blockQuotesPlaceholder | string | default: 'quote' | | boldPlaceholder | string | default: 'bold' | | codeBlockPlaceholder | string | default: 'code block' | | codeInlinePlaceholder | string | default: 'code' | | headlinePlaceholder | string | (level: number) => string | default: (lvl) => 'headline ' + lvl | | imageTextPlaceholder | string | Used inside default image markup ![<example>](...) default: 'example' | | imageUrlPlaceholder | string | Used inside default image markup ![...](<image.png>) default: 'image.png' | | italicPlaceholder | string | default: 'italic' | | linkTextPlaceholder | string | Used inside default link markup [<example>](...) default: 'example' | | linkUrlPlaceholder | string | Used inside default image markup ![...](<url>) default: 'url' | | orderedListPlaceholder | string | default: 'ordered list' | | strikeThroughPlaceholder | string | default: 'strike through' | | unorderedListPlaceholder | string | default: 'unordered list' |


TextareaMarkdownRef

ℹ️ Extends HTMLTextAreaElement instance

trigger: (command: string) => void;
cursor: Cursor

Development

Publishing Releases

This package is automatically published to npm when a GitHub release is created.

Setup was done by following trusted publisher guide: https://docs.npmjs.com/trusted-publishers

Creating a release

Just create a GitHub release with a version tag (e.g., v1.0.0). The workflow will:

  1. Extract the version from the tag
  2. Update package.json automatically
  3. Run lint, tests, and build
  4. Commit the version bump and move the tag to point to it
  5. Publish to npm

No manual version bumping required! The release tag will always point to the commit with the correct version in package.json.

Acknowledgements

All praise goes to https://github.com/Resetand !