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

mumulib

v1.0.16

Published

mumulib is a simple typescript state management, html templating, and form processing library. It contains three modules: state, patslot, and dialog.

Downloads

37

Readme

mumulib

mumulib is a simple typescript state management, html templating, and form processing library. It contains three modules: state, patslot, and dialog.

state provides a simple method for managing state and a way to register onstate callbacks to react to state changes.

patslot provides a simple html templating api, with html templates that can be filled with sample data and all logic being performed in normal TypeScript or JavaScript code.

dialog provides functionality to show html dialog elements populated with state from the state module, and automatically update the state when form inputs in the dialog change.

Building

Running make build converts the typescript files into JavaScript files ready for the browser in the dist directory.

Examples

Running make also runs build and starts a server on port 8000 which can be used to view the examples link below.

state

The state module provides simple state management with a toplevel javascript object and a function set_state which takes a new object and updates the state by merging all toplevel keys with the old state. The onstate function registers a callback which is called when the state has changed.

import { state } from "mumulib";

state.onstate(new_state => {
  const node = document.createElement("div");
  node.textContent = "Got state " + JSON.stringify(new_state);
  document.appendChild(node);
});

state.set_state({hello: "world"});

state.set_path("hello", "everybody");

http://127.0.0.1:8000/examples/use_state/

<input> elements whose name starts with "this." will automatically update the state when those inputs change. Validation can be performed by using JavaScript setter functions in your state tree or by using the built in html input types such as color, email, month, number, range, tel, time, url, etc.

<div>
  <input name="this.name" placeholder="Name" />
  <input name="this.age" placeholder="Age" type="number" />
  <div>Favorite color <input name="this.color" type="color" /></div>
</div>
import { state } from "mumulib";

state.onstate(new_state => {
  const node = document.createElement("div");
  node.textContent = "Got state " + JSON.stringify(new_state);
  document.appendChild(node);
});

http://127.0.0.1:8000/examples/use_state_input/

There is a special toplevel state key "selected" which is the path to the currently selected state object. Inputs whose name start with "selected." will use the object at the path specified by the toplevel "selected" key as the root when traversing the path and setting the state.

<input type="radio" name="selected" value="person1">
<input type="radio" name="selected" value="person2">

<div>
  <input name="selected.name" placeholder="Name" />
  <input name="selected.age" placeholder="Age" type="number" />
  <div>Favorite color <input name="selected.color" type="color" /></div>
</div>
import { state } from "mumulib";

state.onstate(async new_state => {
  const node = document.createElement("div");
  node.textContent = "Got state " + JSON.stringify(new_state);
  document.body.appendChild(node);
});

http://127.0.0.1:8000/examples/use_state_selected/

state api

type State = { [key: string]: any }; type OnStateChange = (state: State) => Promise<void>;

onstate(callback: OnStateChange): Registers callback to be called when the state has changed.

set_state(new_state: State): Applies all toplevel keys in new_state to the old state object, and calls all the onstate handlers if the state has changed. onstate handlers are free to call set_state again and onstate handlers will be called again on the next animation frame.

set_path(path: string, new_substate: any): Traverses the given path and sets the substate to new_substate. If the state has changed, calls all the onstate handlers.

patslot

Patterns and Slots provide a very simple html templating mechanism with templates that can be edited with sample data in them in a graphical html editor. There are only three tag attributes: data-pat, data-slot, and data-attr. All logic is delegated to normal TypeScript or JavaScript code.

<dl data-pat="person" data-attr="style=color">
  <dt>Name</dt>
  <dd data-slot="name">
    John Smith
  </dd>
  <dt>Age</dt>
  <dd data-slot="age">
    42
  </dd>
</dl>
import { patslot } from "mumulib";

window.onload = async () => {
  // Returns an HTMLElement with the slots filled
  let node = await patslot.clone_pat("person", {
    name: "Jane Smith",
    age: 12,
    color: "color: blue"
  });

  document.body.appendChild(node);
}

http://127.0.0.1:8000/examples/use_patslot/

There is a convenience function fill_body you can use to fill the top level slots in your page. There is also the function fill_slots if you have an HTML element you wish to fill.

<dl data-attr="style=color">
  <dt>Name</dt>
  <dd data-slot="name">
    John Smith
  </dd>
  <dt>Age</dt>
  <dd data-slot="age">
    42
  </dd>
</dl>

<div id="fill-element">
  This element has <span data-slot="fill_me"> not been filled yet.</span>
</div>
import { patslot } from "mumulib";

window.onload = async () => {
  await patslot.fill_body({
    name: "Jane Smith",
    age: 12,
    color: "color: blue"
  });
  setTimeout(() => {
    patslot.fill_slots(
      document.getElementById("fill-element"),
      { fill_me: "now been filled." }
    );
  });
}

http://127.0.0.1:8000/examples/use_patslot_fill/

You can use JavaScript generators to make rendering nested hierarchies easy.

<main>
    <ol data-slot="towns">
        <li data-pat="town">
            <h1>Town:</h1>
            <div data-slot="town_name"></div>
            <h2>People:</h2>
            <div data-slot="people">
                <dl data-pat="person">
                    <dt>Name</dt>
                    <dd data-slot="name"></dd>
                    <dt>Age</dt>
                    <dd data-slot="age"></dd>
                </dl>    
            </div>
        </li>
    </ol>
</main>

<footer data-slot="footer">

</footer>


import { patslot } from 'mumulib';


const dataset = {
  towns: [
    {
      name: "Los Angeles",
      people: [
        {"name": "Joe Smith", age: 67},
        {"name": "Example Person", age: 2}
      ]
    },
    {
      name: "London",
      people: [
        {"name": "Jane Smith", age: 23},
        {"name": "John Doe", age: 34}
      ]
    }
  ]
};


function render_people(people) {
  return people.map((person) => patslot.clone_pat("person", person));
}


function* render_towns(towns) {
  for (const town of towns) {
    yield patslot.clone_pat("town", {
      town_name: town.name,
      people: render_people(town.people)
    });
  }
}


window.onload = async () => {
  patslot.fill_body({
    towns: await render_towns(dataset.towns),
    footer: "This is the footer."
  });
}

http://127.0.0.1:8000/examples/use_patslot_nested/

patslot api

type SyncPattern = HTMLElement | (HTMLElement | Generator<Pattern> | string)[] | Generator<Pattern> | string | number; type Pattern = Promise<SyncPattern> | SyncPattern;

clone_pat(pattern_name: string, slot_values: { [key: string]: Pattern}) => HTMLElement: Clone a pattern in the current html page and fill any slots with the given values. Return the filled HTMLElement.

fill_slots(element: HTMLElement, slot_name: string, slot_value: Pattern): Given an HTMLElement, fill any slots with the given values.

append_to_slots(element: HTMLElement, slot_name: string, slot_value: Pattern): Given an HTMLElement, append the given values to the named slots.

fill_body(slot_values: { [key: string]: Pattern }): Fill slots in the current html page body with the given slot_values.

dialog

The dialog module provides a simple function for showing a <dialog> element with forms in it and automatically calling a method of your choice when a form is submitted.

If your dialog contains a <form> element, you can use a hidden input with the name "path" and one with the name "method" to specify a "path" into the state tree to an object whose "method" attribute will be called with a list of all of the <form> <input> values when a form is submitted.

<dialog id="my_dialog">
    <form>
        <input type="hidden" name="path" value="this.my_object" />
        <input type="hidden" name="method" value="my_method" />
        <input name="name" />
        <input type="number" name="age" />
        <button>Save</button>
    </form>
</dialog>

import { state, dialog } from 'mumulib';

class MyObject {
    my_method(args) {
        const node = document.createElement("div");
        node.textContent = "my_method was called " + args.name + " " + args.age;
        document.body.appendChild(node);
    }
}

state.onstate(async (new_state) => {
    if (!new_state.my_object) {
        state.set_state({my_object: new MyObject()});
    } else {
        dialog.do_dialog("my_dialog", "this.my_object", (el, _state) => {
            return el;
        })
    }
});

http://127.0.0.1:8000/examples/use_dialog/

dialog api

type RenderFunc = (el: HTMLElement, state: object) => HTMLElement;

do_dialog(dialog_id: string, path: string, render: RenderFunc) => HTMLElement: Fetch the state at path, call the render function, set the contents of the <dialog> element with the id dialog_id to the result of the render function, and display the dialog.