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 🙏

© 2024 – Pkg Stats / Ryan Hefner

functionalland-component

v0.1.6

Published

A library to develop web component with a bit more of functional flavour.

Downloads

5

Readme

Functional Component

This is an experiemental project. The goal is to build a rendering framework leveraging the web components API. If you don't know what to do with it... it's okay.

API

factorizeComponent

Factorizes a component given a render function, a state and an arbitrary number of composable functions.

The first argument is a render function. The function is called once when the component is connected to the DOM. The render function should accept two parameters, the first one is the element that is being rendered. The second parameter is the current state that should be used to render the component.

The second argument to the factorizeComponent function is an object that will be used as the inital state.

State :: Object
R :: (DOMElement, State) -> void
F :: (((Component, R, State) -> C) -> void, ((DOMElement) -> E) -> void) -> void
factorizeComponent :: ((DOMElement, State) -> void, State, [...F]) -> Component

window.customElements.define(
  "iy-demo",
  factorizeComponent(
    (e, { title }) => {
      const h1 = window.document.createElement("h1");
      h1.textContent = title;
      e.appendChild(e);
    },
    { title: "Bluebird" },
  ),
);

Higher-order-function

The factorizeComponent also accepts an arbitrary amount of functions as arguments. Those higher-order-functions should accept 2 parameters; the first one is named factorize, the second is named construct.

Both HOFs accepts a function that will be called, respectively, at the factory phase, before the component is initialize, and, at the construction phase, when the component is being instantiated.

The factorize function has three parameters; the first parameter is a Component constructor; the second parameter is the render function which can be called to queue a render request; the third parameter is the initial state.

window.customElements.define(
  "iy-demo",
  factorizeComponent(
    render,
    state,
    (factorize, construct) => {
      factorize((Component, render) => {
        Object.defineProperty(
          Component.prototype,
          "forceRender",
          {
            configurable: false,
            enumerable: false,
            value() {
              render(this);
            },
          },
        );

        return Component;
      });
      construct((element) => {
        element.dataset.forceRender = true;
      });
    },
  ),
);

fetchTemplate

Fetches a HTML file from a server and returns the Promise of a <template>.

const element = window.document.querySelector("div");
fetchTemplate("/demo.html")()
  .then((template) => {
    element.appendChild(template.content.cloneNode(true));
  });

useAttributes

Creates a reactive lifecycle with a simple state reducer. When a user or a program sets an attribute of the component, the validation function is called which decides if the component should be rendered again.

The useAttributes function accepts a function to validate an observed attributes value and create a new state. The validation function should have three parameters. The first one is an object representing the attribute that was changed, the second is the element that is affected and the last is the current state of the element. The validation function shoudl return a state fragment or false to cancel the render.

The hook function also takes a map object for all of the attributes to observe.The value is a function to transform the value before the validation function called. If not transformation is needed, just pass the identity function. (x) => x

window.customElements.define(
  "iy-demo",
  factorizeComponent(
    (element, { value }) => {
      const span = element.querySelector("span");
      span.textContent = String(value);
    },
    { value: 0 },
    useAttributes(
      ({ oldValue, value }) =>
        (oldValue !== value && value >= 0) ? ({ value }) : false,
      {
        value: Number,
      },
    ),
    (factorize) => {
      factorize((Component) => {
        Object.defineProperty(
          Component.prototype,
          "connectedCallback",
          {
            enumerable: true,
            value() {
              console.log("hello");
              const span = window.document.createElement("span");
              const button = window.document.createElement("button");
              button.textContent = "Add";
              this.appendChild(span);
              this.appendChild(button);
              button.addEventListener("click", () => {
                const v = this.getAttribute("value");
                this.setAttribute("value", String(Number(v) + 1));
              });
            },
          },
        );
        return Component;
      });
    },
  ),
);

useCallbacks

Hooks into the component's lifecycle. Learn more about lifecycle callbacks on MDN

The function accepts as argument an object of function to hook into one of the following callback: connectedCallback, disconnectedCallback, attributeChangedCallback and adoptedCallback.

Each callback function will be called when appropriate with the element, the relevant options and a asyncRender function as arguments. The asyncRender function can be called at any moment with a "state setter" function. This returns a thunk function that may accept an argument. When the thunk function is called, the "state setter" function is called with the current element and state as argument. This function should return a state fragment or false. The state fragment is then merged with the current state and set the relevant component attributes. See useAttribute.

window.customElements.define(
  "iy-demo",
  factorizeComponent(
    (element, { value }) => {
      const span = element.querySelector("span");
      span.textContent = String(value);
    },
    { value: 0 },
    useCallbacks({
      connectedCallback: (element, render) => {
        const span = window.document.createElement("span");
        const button = window.document.createElement("button");
        button.textContent = "Add";
        element.appendChild(span);
        element.appendChild(button);
        button.addEventListener(
          "click",
          render((e, { value }) => ({ value: ++value })),
        );
      },
    }),
    (factorize) => {
      factorize((Component, render) => {
        Object.defineProperty(
          Component,
          "observedAttributes",
          {
            enumerable: true,
            value: ["value"],
          },
        );

        Object.defineProperty(
          Component.prototype,
          "attributeChangedCallback",
          {
            enumerable: true,
            value(name, oldValue, value) {
              this[StateSymbol][name] = value;
              render(this, Object.assign({}, this[StateSymbol]));
            },
          },
        );

        return Component;
      });
    },
  ),
);

useShadow

Attaches a shadow root to every instance of the Component.

window.customElements.define(
  "iy-demo",
  factorizeComponent(
    (element, { value }) => {
      const span = element.querySelector("span");
      span.textContent = String(value);
    },
    { value: 0 },
    useShadow({ mode: "open" }),
  ),
);

useTemplate

Automatically appends a clone of a template to the element or the element's shadow root.

The function accepts a function that must return a template instance or a Promise of a template instance. Optionally, the function can also be passed an object as the second argument that is used to define children that would be often queried during the render phase. The object's values must be a function that will accept the component instance as the first parameter and return a child element or node list. The elements will be accessible as the elements property of the Component instance element.

window.customElements.define(
  "iy-demo",
  factorizeComponent(
    (e, { value }) => {
      e.elements.number.textContent = String(value);
    },
    { value: 0 },
    useShadow({ mode: "open" }),
    useTemplate(
      () => {
        const t = window.document.createElement("template");
        t.innerHTML = `<span>0</span><button>Add</button>`;

        return t;
      },
      {
        number: (e) => e.shadowRoot.querySelector("span"),
        addButton: (e) => e.shadowRoot.querySelector("button"),
      },
    ),
  ),
);