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

@qnc/node_list_manager

v1.1.1

Published

Efficiently keeps a DOM node's children in sync with a data array

Readme

NodeListManager

A TypeScript utility for efficiently managing a list of DOM child nodes driven by a data array. Handles insertion, removal, reordering, and updates automatically — while preserving focus state.

Motivation

When making small reusable custom elements (eg. @qnc/dynamic_select) I'm reluctant to add an entire vdom library as a dependency. I hope (but haven't verified) that this approach is much lighter than a vdom library.

Project Status

VERY IMMATURE.

While this helps in certain situations that aren't complex enough to need a vdom library, I think most of those situations would still be well served by a minimalistic vdom library (which handles a little more than this does, and provides a nice declarative API).

Many situations start out simple enough for something like this to work well, but then the complexity increases just a bit, and you find yourself wishing you had a declarative vdom library type of approach.

TODO / Warning

I'm pretty sure this does NOT work if get_node() returns a DocumentFragment. We need to verify, then document the result and provide better run time error detection/warning.

Overview

NodeListManager is an abstract class that maps an array of data items to a set of DOM nodes inside a container. Call sync_to whenever your data changes, and it will reconcile the DOM to match.

abstract class NodeListManager<DataType, ComponentType, KeyType>

| Type Parameter | Description | |-----------------|-------------| | DataType | Your data structure (usually a plain object) | | ComponentType | Your component wrapper (can be Node for simple cases). Should hold references to whatever elements need to be updated by the update_component method | | KeyType | A stable, unique key — prefer string or number |

Abstract Methods

Subclasses must implement these four methods:

// Return a unique, stable key for a data item
abstract get_key(data: DataType): KeyType;

// Create a new component for the given key
abstract create_component(key: KeyType): ComponentType;

// Update a component's DOM to reflect current data
abstract update_component(component: ComponentType, data: DataType): void;

// Extract the root Node from a component
abstract get_node(component: ComponentType): Node;

Concrete method: sync_to

sync_to(
    container: Element,
    items: DataType[],
    options: {
        start_anchor?: Node;
        end_anchor?: Node;
    }={}
): void

Reconciles container's children to match items. Only children between start_anchor and end_anchor are managed — nodes outside that range are left untouched. Omit start_anchor/end_anchor to use all children toward the beginning/end of container.

What it does on each call:

  • Creates components for new keys
  • Updates all existing components
  • Removes components whose keys are no longer present
  • Reorders nodes to match the order of items

Focus Preservation

If a managed node contains document.activeElement at the time of a sync_to call, that node will never be temporarily removed from the DOM — even during reordering. Sibling nodes are moved around it instead, so focus and input state are preserved.

Example

import { NodeListManager } from './src/node_list_manager';

interface Item {
    id: number;
    label: string;
}

class ItemListManager extends NodeListManager<Item, HTMLLIElement, number> {
    get_key(data: Item) { return data.id; }
    create_component(_key: number) { return document.createElement('li'); }
    update_component(node: HTMLLIElement, data: Item) { node.textContent = data.label; }
    get_node(node: HTMLLIElement) { return node; }
}

const manager = new ItemListManager();
const ul = document.querySelector('ul')!;

// Initial render
manager.sync_to(ul, [{ id: 1, label: 'One' }, { id: 2, label: 'Two' }], {
    start_anchor: null,
    end_anchor: null,
});

// Update — only changed nodes are touched
manager.sync_to(ul, [{ id: 2, label: 'Two' }, { id: 3, label: 'Three' }], {
    start_anchor: null,
    end_anchor: null,
});

Shadow DOM Support

Focus detection is Shadow DOM-aware. When container is inside a ShadowRoot, activeElement is resolved against that shadow root rather than the top-level document.