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

@connectedxm/entity-editor

v0.0.15

Published

A **zero-dependency**, lightweight React text editor with intelligent entity recognition and real-time formatting using an innovative bitmap-based architecture.

Readme

Entity Editor

A zero-dependency, lightweight React text editor with intelligent entity recognition and real-time formatting using an innovative bitmap-based architecture.

✨ Features

  • 🚀 Zero Dependencies: Pure React implementation with no external libraries
  • 🎯 Smart Entity Recognition: Automatically detects and styles:
    • @mentions (@username)
    • #interests (#hashtag)
    • 🔗 Links (https://example.com)
  • 📝 Rich Text Formatting: Bold, italic, underline, strikethrough with keyboard shortcuts
  • ⚡ Real-time Processing: Instant styling as you type with optimized performance
  • 🗜️ Bitmap Architecture: Ultra-efficient character-level state tracking using bit operations
  • 🎨 Customizable Styling: Configurable colors and styles for all entity types
  • 🔍 Search & Autocomplete: Built-in search functionality for mentions and interests
  • ⌨️ Keyboard Shortcuts: Cmd/Ctrl+B/I/U/Shift+S for formatting
  • 📱 Accessible: Built on standard contentEditable with proper cursor management

🏗️ Bitmap Architecture

The editor's core innovation is its bitmap-based character tracking system. Instead of storing complex objects for each formatting state, every character position in the text is represented by a single number where each bit represents a different property:

// Each character's state encoded in 8 bits (1 byte)
const BOLD_MASK = 0b00000010; // Bit 1: Bold formatting
const ITALIC_MASK = 0b00000100; // Bit 2: Italic formatting
const UNDERLINE_MASK = 0b00001000; // Bit 3: Underline formatting
const STRIKE_MASK = 0b00010000; // Bit 4: Strikethrough formatting
const MENTION_MASK = 0b00100000; // Bit 5: Part of @mention
const INTEREST_MASK = 0b01000000; // Bit 6: Part of #hashtag
const LINK_MASK = 0b10000000; // Bit 7: Part of URL link

How It Works

  1. Character Mapping: Each character position has a corresponding number in the bitmap array
  2. Bit Operations: Properties are set/unset using bitwise OR (|) and AND (&) operations
  3. Entity Building: Consecutive characters with the same bitmap value are grouped into entities
  4. Efficient Updates: Only modified bitmap sections trigger re-rendering

Example: The text "Hello @john" might have a bitmap like:

Text:   H  e  l  l  o     @  j  o  h  n
Bitmap: 0  0  0  0  0  0  32 32 32 32 32

Where 32 is 0b00100000 (MENTION_MASK), indicating those characters are part of a mention.

🚀 Quick Start

Installation

npm install @connectedxm/entity-editor

Design philosophy

The editor owns its own state during a message. Parent components do not mirror plain text, entities, or marks into their own React state. There are only two crossings between the editor and its parent:

  1. Mention search — the editor calls onMentionSearch when the user types @foo; the parent renders a picker and calls editorRef.selectEntity(...) to inject the chosen result.
  2. Submit time read — the parent calls editorRef.getState() once, right before submitting, to get the final { plainText, entities, markState }.

Auto-detected links (https://example.com) and hashtags (#bike) are tagged and styled inside the editor automatically. They surface in getState().entities at submit time — there is no per-event callback for these.

Basic Usage

import React, { useRef, useState } from "react";
import {
  Editor,
  EditorRef,
  MarkState,
  MentionSearch,
} from "@connectedxm/entity-editor";

function App() {
  const editorRef = useRef<EditorRef>(null);
  const [mentionSearch, setMentionSearch] = useState<MentionSearch | null>(null);
  const [markState, setMarkState] = useState<MarkState>({
    bold: false,
    italic: false,
    underline: false,
    strike: false,
  });

  const handleSubmit = () => {
    const state = editorRef.current?.getState();
    if (!state) return;
    // state.plainText  -> "Hello @rob check #bike https://example.com"
    // state.entities   -> [{ type: "mention", ... }, { type: "interest", ... }, { type: "link", ... }]
    // state.markState  -> { bold: false, italic: false, underline: false, strike: false }
    submitToBackend(state);
  };

  return (
    <>
      <Editor
        ref={editorRef}
        onMentionSearch={setMentionSearch}
        onMarkStateChange={setMarkState}
        options={{ mentions: true, interests: true, links: true }}
        placeholder="Type something..."
        style={{ border: "1px solid #ccc", minHeight: "100px", padding: "10px" }}
      />

      {/* render your mention picker using `mentionSearch` */}
      {/* render your toolbar buttons using `markState` */}

      <button onClick={handleSubmit}>Send</button>
    </>
  );
}

Mention picker round-trip

{mentionSearch && (
  <ul>
    {searchAccounts(mentionSearch.search).map((account) => (
      <li
        key={account.id}
        onClick={() =>
          editorRef.current?.selectEntity(
            "mention",
            mentionSearch.startIndex,
            mentionSearch.endIndex,
            account.username
          )
        }
      >
        {account.name} (@{account.username})
      </li>
    ))}
  </ul>
)}

selectEntity replaces the in-progress @foo with the picked username, tags the bitmap as a mention, and clears the search (the next onMentionSearch invocation fires with null).

Toolbar

<button onClick={() => editorRef.current?.toggleMark("bold")}>
  {markState.bold ? "✓ Bold" : "Bold"}
</button>

The toolbar drives marks via toggleMark. The editor reflects the current cursor's mark state through onMarkStateChange so toolbar buttons can show their active state.

Initial content

<Editor
  ref={editorRef}
  initialPlainText="Welcome back @rob"
  initialEntities={[
    { type: "mention", startIndex: 12, endIndex: 16, marks: [], username: "rob" },
  ]}
/>

initialPlainText and initialEntities are read once on mount. To restore content imperatively later, call editorRef.current?.reset().

Styling Entities with CSS

Add CSS to style the recognized entities:

.activity-mention {
  color: #1da1f2;
  background-color: rgba(29, 161, 242, 0.1);
  border-radius: 3px;
  padding: 0 2px;
}

.activity-interest {
  color: #ff6b35;
  font-weight: 500;
}

.activity-link {
  color: #9c27b0;
  text-decoration: underline;
}

.activity-bold {
  font-weight: bold;
}

.activity-italic {
  font-style: italic;
}

.activity-underline {
  text-decoration: underline;
}

.activity-strike {
  text-decoration: line-through;
}

📊 Entity Structure

The editor converts bitmap data into structured entities for easy consumption:

interface Entity {
  type: "mention" | "interest" | "link" | "segment";
  startIndex: number;
  endIndex: number;
  marks: ("bold" | "italic" | "underline" | "strike")[];
  href?: string; // For links
  username?: string; // For mentions
  interest?: string; // For interests
}

interface SearchEntity {
  type: "mention" | "interest" | "link";
  search: string;
  startIndex: number;
  endIndex: number;
}

Bitmap to Entity Conversion

  1. Bitmap Scanning: The system scans through the bitmap array
  2. Grouping: Consecutive characters with identical bit values are grouped together
  3. Entity Creation: Each group becomes an entity with its type determined by the bitmap value
  4. Mark Extraction: Formatting bits are converted to a marks array

🎯 Recognition Patterns

  • Mentions: @username (alphanumeric, underscores, hyphens, apostrophes)
  • Interests: #hashtag (alphanumeric, underscores, hyphens)
  • Links: https:// or http:// URLs
  • Formatting: Applied via keyboard shortcuts or toolbar

⚡ Performance Benefits

Why Bitmap Architecture?

The bitmap approach provides significant performance advantages:

Memory Efficiency:

  • Each character: 1 number (4-8 bytes) vs complex objects (50+ bytes)
  • 10,000 characters: ~40KB vs ~500KB+ in traditional approaches
  • 90%+ memory reduction for large documents

Processing Speed:

  • Bit operations are CPU-native and extremely fast
  • O(1) property checks vs O(n) object property lookups
  • 5-10x faster entity processing

Update Performance:

  • Only modified bitmap regions trigger re-rendering
  • Optimized React re-renders through efficient state management

📝 API Reference

Props

| Prop | Type | Required | Description | | ------------------- | ------------------------------------------------- | -------- | ----------------------------------------------------------------------------- | | ref | React.RefObject<EditorRef \| null> | Yes | Ref to access editor methods | | initialPlainText | string | No | Initial text content (read once on mount) | | initialEntities | Entity[] | No | Initial entities to restore (read once on mount) | | onMentionSearch | (search: MentionSearch \| null) => void | No | Fires (debounced 300ms) when the user types/clears @foo | | onMarkStateChange | (state: MarkState) => void | No | Fires when the cursor's bold/italic/underline/strike state changes | | options | EntityOptions | No | Toggle mentions / interests / links detection | | entityStyles | StyleOptions | No | Custom colors for entity types | | placeholder | string | No | Placeholder text shown when empty and unfocused | | style | React.CSSProperties | No | Inline styles for the editor | | className | string | No | CSS class name for the editor | | debug | boolean | No | Render the raw bitmap below the editor (default: false) |

Ref methods (EditorRef)

| Method | Signature | Purpose | | -------------- | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------ | | getState | () => { plainText, entities, markState } | Submit-time read. Runs a final commit sweep then returns state. | | selectEntity | (type: EntityType, startIndex: number, endIndex: number, newText: string) => void | Inject a picked mention/interest/link in place of the search word. | | toggleMark | (mark: MarkType) => void | Apply / remove a mark on the current selection. | | clear | () => void | Wipe content to empty. | | reset | () => void | Restore the editor to initialPlainText / initialEntities. | | focus | () => void | Move keyboard focus to the editor. |

Interfaces

interface EntityOptions {
  mentions?: boolean; // Enable @mention detection
  interests?: boolean; // Enable #hashtag detection
  links?: boolean; // Enable URL detection
}

interface StyleOptions {
  mentionColor?: string; // Custom color for mentions
  interestColor?: string; // Custom color for interests
  linkColor?: string; // Custom color for links
}

interface MarkState {
  bold: boolean;
  italic: boolean;
  underline: boolean;
  strike: boolean;
}

interface MentionSearch {
  type: "mention";
  search: string;     // the text after @ (without the @)
  startIndex: number; // position of the @ in plainText
  endIndex: number;   // position immediately after the search word
}

interface EditorState {
  plainText: string;
  entities: Entity[];
  markState: MarkState;
}

Keyboard Shortcuts

  • Ctrl/Cmd + B: Toggle bold
  • Ctrl/Cmd + I: Toggle italic
  • Ctrl/Cmd + U: Toggle underline
  • Ctrl/Cmd + Shift + S: Toggle strikethrough

🧪 Testing

Run the test suite:

npm test      # Run tests in watch mode
npm run test:run  # Run tests once

🏗️ Development

# Install dependencies
npm install

# Start development server
npm run dev

# Build for production
npm run build

# Create local package
npm run local

🏆 Why Zero Dependencies?

This editor proves that powerful text editing doesn't require heavy libraries:

  • Performance: No bundle bloat, faster load times
  • Security: No third-party vulnerabilities
  • Control: Full understanding and control over every feature
  • Maintenance: Easier updates and customization
  • Reliability: No breaking changes from external dependencies

🤝 Contributing

This project maintains its zero-dependency philosophy and bitmap-based architecture. When contributing:

  1. No External Dependencies: Keep the runtime dependency-free
  2. Bitmap First: All new features should leverage the bitmap system
  3. Performance Focused: Optimize for memory usage and processing speed
  4. TypeScript: Maintain strict typing throughout
  5. Test Coverage: Add comprehensive tests for new features
  6. API Stability: Keep the component interface simple and focused

Understanding the Codebase

  • src/Editor.tsx - Main editor component
  • src/interfaces.ts - TypeScript interfaces and types
  • src/helpers/bitmap.ts - Core bitmap manipulation functions
  • src/helpers/entities.ts - Bitmap-to-entity conversion logic
  • src/helpers/marks/ - Individual formatting bit operations
  • src/helpers/entities/ - Entity type detection and processing
  • src/helpers/dom.ts - DOM manipulation and cursor management
  • src/helpers/keyboard.ts - Keyboard shortcut handling
  • src/hooks/useDebounce.ts - Debounce hook for performance

📄 License

MIT License - feel free to use in your projects!


Built with ❤️ and zero dependencies by ConnectedXM