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

tabulator-awareness-module

v1.0.1

Published

Tabulator module that add collaborative awareness (realtime remote user focus state), compatible with Hocuspocus/Yjs or other custom providers.

Readme

tabulator-awareness-module

A Tabulator 6.x module that adds collaborative awareness to a table: it shows which user is focused on which cell in real time.

Written in TypeScript, published as a plain JavaScript package (ESM + CJS).

Installation

npm install tabulator-awareness-module tabulator-tables

tabulator-tables (^6.0.0) is a peer dependency, so you control which Tabulator version you use.

How it works

The module never depends on yjs, y-protocols, or @hocuspocus/provider at runtime. Instead it defines a minimal structural interface, AwarenessLike, matching the subset of the y-protocols/awareness API it actually needs (getStates, setLocalState, setLocalStateField, on, off, clientID). Any Yjs-compatible provider - Hocuspocus, y-websocket, y-webrtc, or a provider you wrote yourself - already exposes an .awareness object matching this shape.

You choose how to supply it with a small discriminated config:

type AwarenessProviderConfig =
  | { type: "hocuspocus"; provider: { awareness: AwarenessLike } }
  | { type: "custom"; awareness: AwarenessLike };

Both branches resolve to the same AwarenessLike internally; the "hocuspocus" variant is just clearer at the call site when you already hold a HocuspocusProvider instance.

Usage with Hocuspocus

import { Tabulator } from "tabulator-tables";
import { AwarenessModule } from "tabulator-awareness-module";
import { HocuspocusProvider } from "@hocuspocus/provider";

Tabulator.registerModule(AwarenessModule);

const provider = new HocuspocusProvider({
  url: "wss://your-hocuspocus-server",
  name: "my-document",
});

const table = new Tabulator("#table", {
  index: "id", // the field used as each row's stable identity
  data: myData,
  columns: myColumns,
  awareness: {
    user: {
      id: currentUser.id,
      name: currentUser.name,
      fullName: currentUser.fullName,
      color: currentUser.color,
    },
    provider: { type: "hocuspocus", provider },
  },
});

Usage with a custom provider

Any object exposing a y-protocols-compatible awareness works, for example a raw y-websocket or y-webrtc provider, or your own implementation:

import { Tabulator } from "tabulator-tables";
import { AwarenessModule } from "tabulator-awareness-module";
import { WebsocketProvider } from "y-websocket";
import * as Y from "yjs";

Tabulator.registerModule(AwarenessModule);

const doc = new Y.Doc();
const provider = new WebsocketProvider("wss://your-server", "my-document", doc);

const table = new Tabulator("#table", {
  index: "id",
  data: myData,
  columns: myColumns,
  awareness: {
    user: { id: currentUser.id, fullName: currentUser.fullName, name: currentUser.name },
    provider: { type: "custom", awareness: provider.awareness },
  },
});

Options

| Option | Type | Required | Description | |-----------------------| --- |----------|-------------------------------------------------------------------------------------------------------------------------------------------| | user.id | string | yes | Stable identifier for the local user. Used to derive a deterministic color when user.color is omitted. | | user.name | string | no | Short label rendered inside the user's badge (e.g. "JD"). | | user.fullName | string | no | Full name displayed in place of the short name when hovering the cell. | | user.color | string | no | CSS color for this user's badge and cell border. Auto-generated from user.id when omitted. | | provider | AwarenessProviderConfig | yes | Either { type: "hocuspocus", provider } or { type: "custom", awareness }. | | clearStateOnDestroy | boolean | no | Clears the local awareness state when the table is destroyed. Defaults to true. | | heartbeatIntervalMs | number | no | How often (ms) the local client republishes a heartbeat. Defaults to 20000. | | staleTimeoutMs | number | no | How long (ms) a peer's heartbeat may go without an update before its focus is hidden. Defaults to 3 * heartbeatIntervalMs. | | debug | boolean | no | Logs local focus changes, heartbeats and remote state renders to the console, prefixed with [tabulator-awareness]. Defaults to false. | | injectDefaultStyles | boolean | no | Injects the default stylesheet (AWARENESS_STYLE) into <head> on init. Set to false if you bundle your own CSS instead. Defaults to true. |

Rows must have a stable identity: the module reads it via Tabulator's own index option (defaults to "id"), through row.getIndex().

Focus lifecycle and stale peer detection

Local focus is tracked purely from the editing lifecycle: cellEditing sets it, and cellEdited or cellEditCancelled clears it. A user is only shown as "on" a cell while actively editing it.

This leaves one gap: a user who closes the tab, loses their connection, or otherwise leaves without triggering cellEdited/cellEditCancelled would keep showing as focused on that cell forever. To handle this, the module maintains a heartbeat:

  • On an interval (heartbeatIntervalMs), each client republishes a heartbeatAt timestamp on its own local awareness state.
  • On the same interval, each client re-evaluates every remote peer's heartbeatAt. Any peer whose heartbeat is older than staleTimeoutMs has its focus hidden, even if the underlying awareness entry is still technically present.

This check runs entirely on the client, for two reasons: this library only runs in the browser (it has no access to your Hocuspocus/y-websocket server process), and the "custom" provider path accepts any object satisfying AwarenessLike, which may or may not implement its own peer-timeout logic. Note that if you use a real Yjs backend (Hocuspocus, y-websocket, y-webrtc), y-protocols/awareness already prunes fully disconnected clients from getStates() on its own after about 30 seconds; this module's heartbeat is a faster, provider-agnostic safety net on top of that, and the only mechanism available at all for a "custom" provider that doesn't implement such pruning itself.

Querying a cell's state

The module exposes a table function, getCellAwareness(rowId, field), returning the list of remote users (if any) currently focused on a given cell:

const focuses = table.getCellAwareness(rowId, field);
// [{ clientId: 42, user: { id: "u2", fullName: "User Two", name: "UT" } }]

An empty array means nobody else is on that cell. This is the hook to use for blocking a concurrent edit, typically from a column's editable callback:

const table = new Tabulator("#table", {
  index: "id",
  data: myData,
  columns: [
    {
      field: "name",
      editable: (cell) => table.getCellAwareness(cell.getRow().getIndex(), cell.getField()).length === 0,
    },
  ],
  awareness: {
    user: { id: currentUser.id, fullName: currentUser.fullName, name: currentUser.name },
    provider: { type: "custom", awareness: provider.awareness },
  },
});

With this in place, Tabulator itself refuses to enter edit mode on a cell someone else is already editing, instead of letting the user start typing and only failing later.

Styling

A small default stylesheet is injected once into <head> on module initialization (cell border + stacked user badges). It's also exported as a raw string if you'd rather bundle it yourself:

import { AWARENESS_STYLE } from "tabulator-awareness-module";

Set injectDefaultStyles: false to skip the automatic injection entirely, for example if you're including AWARENESS_STYLE in your own bundled stylesheet, or shipping a fully custom look:

awareness: {
  user: { id: currentUser.id, fullName: currentUser.fullName, name: currentUser.name },
  provider: { type: "custom", awareness: provider.awareness },
  injectDefaultStyles: false,
}

The relevant class names are tabulator-awareness-cell, tabulator-awareness-badges and tabulator-awareness-badge, and the border color is driven by the --tabulator-awareness-color CSS custom property, so you can override the look with your own CSS.

Each badge shows the user's short name (tabulator-awareness-badge-name) by default. Hovering the cell swaps it for the full name (tabulator-awareness-badge-fullname) via a plain CSS rule (.tabulator-awareness-cell:hover).

Current limitations

Since focus is tied to editing, a user simply viewing or selecting a cell without editing it is not shown as focused. This is intentional: awareness reflects who is editing what, not who last clicked where.

License

MIT