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

@type-editor/decoration

v0.0.3

Published

Type Editor - Decoration Module

Readme

@type-editor/decoration

A refactored version of ProseMirror's prosemirror-decoration system, providing a powerful way to add visual styling, attributes, and UI elements to the editor view without modifying the underlying document.

Installation

npm install @type-editor/decoration

Overview

Decorations allow you to enhance the visual presentation of your editor content without changing the document model. They are used for features like:

  • Syntax highlighting: Apply styling to code elements
  • Search results: Highlight matching text
  • Collaborative editing: Show other users' cursors and selections
  • Spelling/grammar: Mark errors or suggestions
  • Inline widgets: Add buttons, mentions, or custom UI elements
  • Node styling: Apply classes or attributes to block elements

Decorations are ephemeral—they exist only in the view layer and are managed through the decorations prop. They automatically update when the document changes through a mapping process.

Decoration Types

Inline Decorations

Inline decorations apply styling or attributes to a range of inline content. They render as inline elements (like <span>) wrapping the decorated content.

import { Decoration } from '@type-editor/decoration';

// Highlight text from position 5 to 15
const highlight = Decoration.inline(5, 15, {
  class: 'search-result',
  style: 'background-color: yellow;'
});

// Mark a spelling error with data attributes
const spellError = Decoration.inline(20, 25, {
  class: 'spelling-error',
  'data-suggestion': 'correct'
});

// Control whether decorations expand when typing at boundaries
const expandable = Decoration.inline(30, 40, 
  { class: 'comment' },
  { inclusiveStart: true, inclusiveEnd: true }
);

Options:

  • inclusiveStart: Whether the decoration expands when content is inserted at the start (default: false)
  • inclusiveEnd: Whether the decoration expands when content is inserted at the end (default: false)

Widget Decorations

Widget decorations insert DOM nodes at specific positions without affecting the document model. They're perfect for inline UI elements.

// Insert a button at position 10
const button = Decoration.widget(10, () => {
  const btn = document.createElement('button');
  btn.textContent = 'Click me';
  btn.onclick = () => alert('Clicked!');
  return btn;
});

// Show a collaborative cursor
const cursor = Decoration.widget(50, () => {
  const el = document.createElement('span');
  el.className = 'remote-cursor';
  el.style.borderLeft = '2px solid blue';
  return el;
}, { side: -1 }); // Position before the cursor position

// Add a mention suggestion widget
const mention = Decoration.widget(100, (view, getPos) => {
  const el = document.createElement('span');
  el.className = 'mention-widget';
  el.textContent = '@john';
  return el;
}, { 
  side: 1,
  key: 'mention-john' // Stable identity for efficient updates
});

Options:

  • side: Position relative to the specified position:
    • -1: Before the position
    • 0: At the position (default)
    • 1: After the position
  • key: Stable identifier for the widget (helps with efficient updates)
  • stopEvent: Function to intercept DOM events on the widget
  • ignoreSelection: Whether to ignore selection when positioning

Node Decorations

Node decorations apply styling or attributes to entire block nodes. They wrap the node's DOM representation.

// Highlight a selected paragraph
const selectedPara = Decoration.node(5, 45, {
  class: 'selected-node'
});

// Mark a code block with an error
const errorBlock = Decoration.node(100, 150, {
  class: 'error-block',
  'data-error-type': 'syntax',
  style: 'border-left: 3px solid red;'
});

Important: Node decorations must span exactly one non-text node. The from position must be at the start of a node, and the to position must be at the end of that same node.

DecorationSet

The DecorationSet class organizes decorations efficiently for use with the editor view. It's a persistent data structure that supports efficient updates when the document changes.

Creating a DecorationSet

import { DecorationSet } from '@type-editor/decoration';

// Create from an array of decorations
const decorations = [
  Decoration.inline(5, 15, { class: 'highlight' }),
  Decoration.widget(20, () => document.createElement('button')),
  Decoration.node(30, 50, { class: 'selected' })
];

const decoSet = DecorationSet.create(doc, decorations);

Updating Decorations

When the document changes, map decorations through the change:

import { Mapping } from '@type-editor/transform';

// After a transaction
const mapping = transaction.mapping;
const newDecoSet = oldDecoSet.map(mapping, newDoc);

Finding Decorations

// Find all decorations in a range
const found = decoSet.find(10, 50);

// Find decorations at a specific position
const atPos = decoSet.find(25, 25);

// Find decorations with a specific spec property
const withKey = decoSet.find(undefined, undefined, 
  deco => deco.spec.key === 'my-widget'
);

Adding and Removing Decorations

// Add decorations
const newDecoSet = oldDecoSet.add(doc, [
  Decoration.inline(100, 110, { class: 'new-highlight' })
]);

// Remove decorations
const withoutDecoSet = oldDecoSet.remove(decorationsToRemove);

Using Decorations in a Plugin

Decorations are typically managed through a plugin's state:

import { Plugin } from '@type-editor/state';
import { Decoration, DecorationSet } from '@type-editor/decoration';

const highlightPlugin = new Plugin({
  state: {
    init(_, { doc }) {
      // Create initial decorations
      const decorations = findTextToHighlight(doc).map(({ from, to }) =>
        Decoration.inline(from, to, { class: 'highlight' })
      );
      return DecorationSet.create(doc, decorations);
    },
    apply(tr, oldSet) {
      // Map decorations through document changes
      let set = oldSet.map(tr.mapping, tr.doc);
      
      // Update decorations based on transaction metadata
      if (tr.getMeta('updateHighlights')) {
        const decorations = findTextToHighlight(tr.doc).map(({ from, to }) =>
          Decoration.inline(from, to, { class: 'highlight' })
        );
        set = DecorationSet.create(tr.doc, decorations);
      }
      
      return set;
    }
  },
  props: {
    decorations(state) {
      return this.getState(state);
    }
  }
});

Advanced Usage

Dynamic Widget Content

Widgets can access the editor view and their position:

const dynamicWidget = Decoration.widget(pos, (view, getPos) => {
  const el = document.createElement('div');
  
  // Access current position (may change as document is edited)
  const currentPos = getPos();
  
  // Access view to dispatch transactions
  el.onclick = () => {
    view.dispatch(view.state.tr.insertText('Clicked!', currentPos));
  };
  
  return el;
});

Event Handling in Widgets

Control how events are handled on widget elements:

const interactiveWidget = Decoration.widget(pos, () => {
  const el = document.createElement('input');
  return el;
}, {
  stopEvent: (event) => {
    // Return true to prevent ProseMirror from handling this event
    return event.type === 'mousedown' || event.type === 'keydown';
  }
});

Efficient Updates with Keys

Use keys to help ProseMirror identify widgets across updates:

// Without keys, widgets are recreated on every update
const withoutKey = Decoration.widget(pos, () => createComplexWidget());

// With keys, ProseMirror can reuse the same DOM node
const withKey = Decoration.widget(pos, () => createComplexWidget(), {
  key: 'user-123-cursor'
});

API Reference

Decoration

Static methods for creating decorations:

  • Decoration.widget(pos, toDOM, spec?): Create a widget decoration
  • Decoration.inline(from, to, attrs, spec?): Create an inline decoration
  • Decoration.node(from, to, attrs, spec?): Create a node decoration

Instance properties:

  • from: Start position
  • to: End position
  • type: Decoration type object
  • spec: The specification object used to create the decoration

Instance methods:

  • eq(other, offset?): Check if two decorations are equal
  • copy(from, to): Create a copy with new positions
  • map(mapping, offset, oldOffset): Map through a document change

DecorationSet

Static methods:

  • DecorationSet.create(doc, decorations): Create a decoration set from an array
  • DecorationSet.empty: Empty decoration set constant

Instance methods:

  • find(from?, to?, predicate?): Find decorations in a range
  • map(mapping, doc, options?): Map through document changes
  • add(doc, decorations): Add decorations
  • remove(decorations): Remove decorations

DecorationGroup

Helper for managing multiple decoration sources:

import { DecorationGroup } from '@type-editor/decoration';

const group = DecorationGroup.from([decoSet1, decoSet2, decoSet3]);

Performance Considerations

  1. Use keys for widgets: Assign stable keys to widget decorations to avoid unnecessary DOM recreation
  2. Minimize decoration count: Large numbers of decorations can impact performance
  3. Batch updates: Update decorations together rather than one at a time
  4. Use appropriate types: Choose the simplest decoration type for your use case
  5. Efficient mapping: The decoration set efficiently maps through changes, but creating new sets is relatively expensive

Compatibility

This module is a refactored version of ProseMirror's decoration system. While the API is nearly identical, TypeScript type definitions may differ slightly. For full ProseMirror compatibility, use the @type-editor-compat/decoration package.

Related Modules

  • @type-editor/view: The view module that renders decorations
  • @type-editor/state: State management for decoration plugins
  • @type-editor/transform: Provides the mapping system for updating decorations

License

MIT