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

@qalma/editor

v1.0.0

Published

Angular-first headless rich text editor toolkit built on ProseMirror.

Readme

@qalma/editor

Angular-first, headless rich text editor toolkit built on ProseMirror.

Qalma gives you a typed editor controller, signal-based state, and a small set of unstyled Angular primitives (<qalma-editor>, <qalma-content>, <qalma-toolbar>, qalmaCommand). Everything else — toolbar UI, styling, menus, popovers — stays in your app. You choose the plugins, you own the markup.

Documentation & live demo → qalma.dev

Status: pre-1.0 (0.x). The public API is stabilizing but may still change before 1.0.

Installation

npm install @qalma/editor

@angular/core >=21 <23 is a peer dependency. @angular/forms is an optional peer dependency used only by the @qalma/editor/forms entrypoint.

Quick start

import { createQalmaEditor, HistoryPlugin, TextFormattingKit } from '@qalma/editor';

const editor = createQalmaEditor({
  content: '<p>Hello world</p>',
  plugins: [
    ...TextFormattingKit,
    HistoryPlugin.configure({
      depth: 200,
      newGroupDelay: 750,
    }),
  ],
});
<qalma-editor [editor]="editor">
  <qalma-toolbar>
    <button qalmaCommand="toggleBold">Bold</button>
    <button qalmaCommand="undo">Undo</button>
    <button qalmaCommand="redo">Redo</button>
  </qalma-toolbar>

  <qalma-content />
</qalma-editor>

Core concepts

createQalmaEditor(options)

Builds a QalmaEditorController, the headless API your components bind to:

| Member | Description | | ------------------------------- | ---------------------------------------------------------------------- | | html: Signal<string> | Serialized HTML of the current document, kept in sync with every edit. | | editable: Signal<boolean> | Whether the document can be edited. | | execute(command, value?) | Runs a registered command, e.g. editor.execute('toggleBold'). | | canExecute(command, value?) | Whether a command would currently succeed. | | isCommandActive(command) | Whether a toggleable command is active for the current selection. | | query<T>(name) | Reads plugin-provided state, e.g. the link or image under the cursor. | | getCoordinatesAtPosition(pos) | Reads viewport coordinates for a document position, or null. | | setHtml(html) | Replaces the document content from an HTML string. | | getJSON() | Serializes the document to ProseMirror's native, lossless JSON. | | setJSON(doc) | Replaces the document content from a QalmaDocument JSON object. | | getStoredDocument() | Wraps lossless JSON with storage, schema, and plugin versions. | | setStoredDocument(doc) | Validates and migrates a versioned stored document. | | getMarkdown() | Serializes the document to Markdown (CommonMark + GFM). | | setMarkdown(markdown) | Imports Markdown when MarkdownPlugin is configured. | | setEditable(editable) | Toggles editability at runtime. | | focus() | Focuses the editor view. |

options.content accepts an initial HTML string, and options.plugins accepts the list of QalmaPlugins that define the schema, commands, and behavior available to the editor. Set schemaVersion and migrations when persisted documents must evolve across schema changes. Use options.contentLimits to lower the default HTML/Markdown length and JSON/live document node, depth, and text ceilings for short-form products.

Serializing content

The controller can read and write the document in three formats:

  • HTML — html() (a live signal) and setHtml(). Best for rendering and interop with existing HTML content.
  • JSON — getJSON() / setJSON() for raw interop, or getStoredDocument() / setStoredDocument() for versioned persistence. Both preserve ProseMirror's native document model losslessly.
  • Markdown — getMarkdown() and optional setMarkdown(). CommonMark plus GFM (tables, task lists, strikethrough). Marks Markdown cannot express (underline, text color, highlight, sub/superscript, mentions) fall back to inline HTML so no content is dropped on export. Import lives in the optional @qalma/editor/markdown entrypoint, so apps that do not need it do not ship a Markdown parser.
const stored = editor.getStoredDocument(); // persist this envelope
editor.setStoredDocument(stored); // validate and migrate it later

const markdown = editor.getMarkdown(); // export to Markdown
import { MarkdownPlugin } from '@qalma/editor/markdown';

const editor = createQalmaEditor({ plugins: [MarkdownPlugin] });
editor.setMarkdown('# Imported');

Security boundary

First-party plugins validate persisted attributes across HTML, commands, JSON, paste, and serialization. Custom plugins remain trusted code: validate every attribute with AttributeSpec.validate, build fresh allowlisted DOM attribute objects, and never merge untrusted attribute bags. Keep Angular's normal sanitizer when rendering stored HTML outside the editor; do not apply bypassSecurityTrustHtml() to user-authored content. File MIME types are client-provided hints and require server-side validation. See the repository SECURITY.md and dated audit for the complete model.

Components and directives

  • <qalma-editor [editor]="editor"> — root container that shares the controller with its content.
  • <qalma-content /> — mounts the ProseMirror view. Style its .qalma-content / .ProseMirror descendants from your app's CSS.
  • <qalma-toolbar> — an accessible (role="toolbar") wrapper for your toolbar controls. Purely structural.
  • button[qalmaCommand] — binds a button to a command by name. It calls execute() on click, reflects isCommandActive() via .qalma-command-active and aria-pressed, and disables itself when canExecute() is false. Pass a command argument with [qalmaCommandValue].
  • QalmaControlValueAccessor from @qalma/editor/forms — optional Angular forms adapter for formControl, formControlName, and ngModel.

Plugins and kits

A QalmaPlugin contributes schema nodes/marks, commands, command-state queries, content parsers, shortcuts, and ProseMirror plugins. A kit (e.g. TextFormattingKit) is just a readonly QalmaPlugin[] bundling related plugins — spread it into plugins like any other entry.

Configurable plugins expose a .configure(options) method that returns a new plugin instance with merged options, e.g. HistoryPlugin.configure({ depth: 200 }).

The broad EssentialsKit is intentionally imported from @qalma/editor/essentials. Keeping the composite in a secondary entrypoint prevents its eager imports from retaining every included plugin in smaller main-entrypoint bundles.

Available plugins

| Plugin | Commands | | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | | BoldPlugin, ItalicPlugin, UnderlinePlugin, StrikePlugin (TextFormattingKit) | toggleBold, toggleItalic, toggleUnderline, toggleStrike | | InlineCodePlugin | toggleInlineCode (Mod-e, single-backtick input rule) | | MonospacePlugin | toggleMonospace | | SubscriptSuperscriptPlugin | toggleSubscript, toggleSuperscript | | HeadingsPlugin | setParagraph, toggleHeading1…toggleHeading6 (configurable levels) | | BlockquotePlugin | toggleBlockquote | | TablePlugin from @qalma/editor/table | insertTable, addRow*/addColumn*, deleteRow/deleteColumn/deleteTable, mergeCells/splitCell, toggleHeader* | | HorizontalRulePlugin | insertHorizontalRule | | ListsPlugin | toggleBulletList, toggleOrderedList, splitListItem, liftListItem, sinkListItem | | TaskListPlugin | toggleTaskList, toggleTaskItemChecked, setTaskItemChecked, splitTaskItem, liftTaskItem, sinkTaskItem | | CodeBlockPlugin | toggleCodeBlock, setCodeBlockLanguage | | LinkPlugin | setLink, selectLink, unsetLink | | ImagePlugin | insertImage, updateImage | | MentionPlugin | insertMention | | SlashCommandPlugin | deleteSlashCommand, dismissSlashCommand | | DragHandlePlugin | selectBlock, deleteBlock, duplicateBlock, moveBlockTo, moveBlockUp, moveBlockDown | | SelectionPlugin | None (query('selection'), qalma-selection-update) | | ColorPlugin | setTextColor, unsetTextColor, setBackgroundColor, unsetBackgroundColor | | HighlightPlugin | setHighlight, unsetHighlight | | TextAlignPlugin | alignment commands for configured node types | | ClearFormattingPlugin | clearFormatting | | HardBreakPlugin | insertHardBreak | | HistoryPlugin | undo, redo (Mod-z, Shift-Mod-z, Mod-y) | | PasteRulesPlugin | normalizes pasted content | | PlaceholderPlugin | shows placeholder text in an empty document | | TrailingParagraphPlugin | keeps a trailing empty paragraph at the end of the document | | CharacterCountPlugin | None (query('characterCount'); optional hard limit) | | FileHandlerPlugin | None (typed paste/drop file callbacks) | | FindReplacePlugin | setFindQuery, findNext, findPrevious, replaceCurrent, replaceAll, clearFind | | UniqueIdPlugin | None (query('uniqueId'); stable configured-node IDs) |

Read each plugin's source under src/lib/plugins for configuration options (e.g. HeadingsPlugin.configure({ levels: [1, 2, 3] }), InlineCodePlugin.configure({ inputRules: false }), MentionPlugin.configure({ trigger: '@' }), SlashCommandPlugin.configure({ trigger: '/' }), LinkPlugin.configure({ allowedProtocols: [...], onClick })).

Learn more

The repository also ships a full example app (apps/sandbox) — a toolbar, link popover, mention menu, image upload, and code block highlighting — built entirely from the public @qalma/editor API. See CONTRIBUTING.md to run it locally.