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

prosemirror-subtree-window

v0.1.0

Published

Viewport-driven subtree content windowing for ProseMirror editors

Readme

prosemirror-subtree-window

Viewport-driven subtree-content windowing for ProseMirror.

Open the live large-document performance demo

The complete ProseMirror document always remains in EditorState. Eligible container nodes keep one height-preserving DOM placeholder while they are away from the viewport; their descendant contentDOM is materialized when they approach it. Window moves are decoration-only transactions with no steps, so they do not enter history or change the document.

This is deliberately called subtree-content windowing rather than virtualization. It leaves one placeholder per unmaterialized node, so DOM cost is O(widest sibling run), not strictly O(viewport).

Install

npm install prosemirror-subtree-window

The package is ESM-only. prosemirror-model, prosemirror-state, and prosemirror-view are peer dependencies.

Use

import { EditorState, NodeSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { createSubtreeWindowing } from "prosemirror-subtree-window";

const windowing = createSubtreeWindowing({
  scrollRoot: editorHost,
  nodeTypes: ["section"],
  minDocumentUnits: 2_000,
});

const state = EditorState.create({
  doc,
  plugins: [...plugins, windowing.plugin],
});

const view = new EditorView(editorHost, { state });

windowing.setSelection(view, NodeSelection.create(view.state.doc, position), {
  scrollIntoView: true,
  focus: true,
});

The plugin supplies its node views through plugin props and owns its IntersectionObserver lifecycle. Do not also provide a direct nodeViews entry for a windowed type: direct EditorView props take precedence over plugin props. Pass existing node views as baseNodeViews instead; the package wraps them and forwards their lifecycle hooks when their content is materialized.

Ordinary tr.setSelection(...) transactions are safe too. The plugin automatically materializes both endpoint paths of every selection, plus the selected node for a NodeSelection. The explicit API is useful when selection, scrolling, and focus should be performed together.

Configuration

scrollRoot and nodeTypes are required. The useful optional settings are:

  • baseNodeViews: existing node views to wrap and provide through the plugin.
  • subtreeUnits(node): size used for eligibility and eager subtree expansion.
  • estimateUnits(node): units used to estimate placeholder height.
  • measurementKey(node): groups nodes whose measured height per unit is shared.
  • createPlaceholder(context): custom placeholder markup.
  • isWindowable(node, pos, doc): restricts eligible container nodes.
  • alwaysRender(node, pos, doc): excludes structural roots or special nodes.
  • minDocumentUnits, minSubtreeUnits, and expandSubtreeBelow: thresholds.
  • renderMargin and keepMargin: observer margins. The wider keep margin provides hysteresis.

Defaults use non-text descendant count as the unit, group measurements by node type, materialize at 150%, and retain materialized content to 400%.

minDocumentUnits and enabled are evaluated when the plugin state is created. They do not toggle an existing editor when its document later crosses the threshold. For editors that start small and can grow large, leave minDocumentUnits at 0 (the default), or recreate the editor state when your application changes modes.

API

  • plugin: add it to the editor state.
  • setWindow(view, action): explicitly materialize or release node positions; useful for deterministic drivers and tests.
  • revealPosition(view, pos): materialize a position and its ancestor path.
  • revealRange(view, from, to): materialize both endpoint paths.
  • setSelection(view, selection, options): select with automatic reveal.
  • getState(state): return { enabled, materializedCount } diagnostics.
  • countDescendants(node): the memoized default sizing helper.

Constraints

  • Use block container nodes whose normal rendering exposes a contentDOM. Inline flow, textblocks, table-layout participants, and nested editors require application-specific evaluation.
  • Browser find, screen readers, printing, and DOM-only export cannot inspect content that is not currently materialized. Model-driven operations can scan the complete ProseMirror document and call the reveal API before navigating.
  • Placeholder accuracy depends on a size unit that correlates with rendered height. Supply estimateUnits, measurementKey, and createPlaceholder for schemas whose nodes vary substantially.
  • Without IntersectionObserver, the package fails open and renders the normal complete document.
  • Custom node views using multiType are not supported in this first version.

Performance demo

The repository-only example generates up to 10000 sections in the browser, keeps the editor interactive, and measures the windowed and complete-DOM mounts on the visitor's device.

Run it locally from a repository checkout:

cd example
npm install
npm run dev