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 🙏

© 2025 – Pkg Stats / Ryan Hefner

collaborative-textarea

v1.0.1

Published

CRDT-powered <collaborative-textarea> custom element with remote cursors, persistence hooks, and TypeScript support.

Readme

Collaborative Textarea

CRDT-powered <collaborative-textarea> custom element with remote cursor overlays, persistence hooks, and first-class TypeScript types. Drop it into any web app, wire it to your transport of choice (WebRTC data channels work great), and get resilient collaborative editing without frameworks or build tools.

Why this component

  • Uses a Text RGA CRDT (simple-crdts) so edits merge deterministically and survive offline/latency.
  • Ships as a standard custom element; no React/Vue/Svelte runtime required.
  • Remote cursor labels and colors are drawn in an overlay that matches your textarea’s typography.
  • Built-in onWritingPaused callback and toJSON() make persisting snapshots trivial.
  • Fully typed API surface, with zero dependencies beyond simple-crdts.

Installation

npm install collaborative-textarea

The package is ESM-only ("type": "module"). Importing it registers the <collaborative-textarea> tag globally and also exports the class.

import { CollaborativeTextarea } from "collaborative-textarea";

Quick start

Render the element, optionally hydrating from a saved snapshot and tagging the local user with a display name and color.

<collaborative-textarea
  id="editor"
  spellcheck="false"
  style="width: 100%; height: 320px;"
></collaborative-textarea>
<script type="module">
  import { CollaborativeTextarea } from "collaborative-textarea";

  const saved = localStorage.getItem("collab-snapshot");
  const rgaSnapshot = saved ? JSON.parse(saved) : undefined;

  const editor = new CollaborativeTextarea(
    "node-a",
    { name: "Ava", color: "#ec4899" },
    [],
    rgaSnapshot
  );

  editor.onWritingPaused = (snapshot) => {
    localStorage.setItem("collab-snapshot", JSON.stringify(snapshot));
  };

  document.getElementById("editor").replaceWith(editor);
</script>

Wiring up peers (e.g., WebRTC)

peerConnections expects objects with a send(operation) method. When using RTCDataChannel, wrap serialization yourself and call applyRemoteOp on inbound messages.

// Outbound: serialize before sending.
const channel = rtcPeerConnection.createDataChannel("collab");
const peer = {
  send: (operation) => channel.send(JSON.stringify(operation)),
};
editor.addPeer(peer);

// Inbound: parse and feed into the CRDT.
channel.onmessage = ({ data }) => {
  const operation = JSON.parse(data);
  editor.applyRemoteOp(operation);
};

You can also call insertCharAt, deleteCharAt, or applyRemoteOp directly if you bridge through another transport (WebSocket, BroadcastChannel, etc.).

API overview

Constructor

new CollaborativeTextarea(localNodeId?, userMeta?, peerConnections?, rgaSnapshot?)

  • localNodeId (string): unique id for this client. A UUID is generated if omitted.
  • userMeta ({ name?: string; color?: string; }): label and caret color shown in the overlay.
  • peerConnections (CollaborativePeer[]): objects with a send method to broadcast operations.
  • rgaSnapshot (TextRGAJSON): previously persisted CRDT state to hydrate from.

Key properties

  • spellcheck (boolean): mirrors the underlying textarea. Set via property or spellcheck attribute.
  • onWritingPaused (callback | null): invoked ~1s after edits stop with toJSON() snapshot.
  • textarea (HTMLTextAreaElement): access the underlying textarea if you need to wire extra handlers.

Methods

  • addPeer(peer) / removePeer(peer): manage outbound peer targets.
  • insertCharAt(index, char): local insert routed through the CRDT.
  • deleteCharAt(index): local delete routed through the CRDT.
  • applyRemoteOp(operation): apply an incoming insert/delete/cursor operation from a peer.
  • toJSON(): CRDT snapshot suitable for persistence or hydration.

Styling

Style the host element as you would a normal textarea wrapper; the component mirrors your font, size, alignment, and padding onto the internal textarea so the cursor overlay lines up. Example:

collaborative-textarea {
  display: block;
  width: 100%;
  height: 280px;
  border: 1px solid #d1d5db;
  border-radius: 8px;
  padding: 12px;
  font: 400 16px/1.5 "Inter", system-ui, sans-serif;
}

Persisting state

  • Listen to onWritingPaused for a debounced snapshot callback, or call toJSON() anytime.
  • Pass the stored snapshot back into the constructor to hydrate the CRDT with the correct ordering.

Notes and compatibility

  • Requires browsers with Custom Elements and ResizeObserver support (falls back to window.resize if unavailable).
  • Ships unbundled ES modules; bring your own bundler or load directly in modern browsers.
  • Dependency footprint: simple-crdts only.