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

@denindka/fast-ui

v0.2.2

Published

A reusable, framework-agnostic vanilla-TS UI component library — a growing home for shared UI components (dumb components: render + attach + CSS + one-call mount, over a zero-DOM core). Shared across products; built to be extended. First components: colou

Readme

@denindka/fast-ui

A reusable, framework-agnostic UI component library for our vanilla-TS stack — the F1/fastsheet shell and its fastdoc twin. It's the single home for shared UI components, designed to grow: any developer adds a new one by following the same pattern (→ AUTHORING.md). No React, no other frameworks — by design.

The components shipped so far are an advanced colour dialog, a Word/Excel colour dropdown, and a font-family menu — but the library and its pattern are the product, not those particular components; expect more (and feel free to add them). Every component is a dumb component (like a controlled React component, but vanilla): renderXxxHtml(props) + attachXxx(root, callbacks) + XXX_CSS (+ a one-call mountXxx), over a zero-DOM core.

Two kinds of consumer, one source:

  • Bundler apps (fastdoc — esbuild / Vite / webpack): import components straight from @denindka/fast-ui/ui (and /core); use the mount* helpers or render* + attach*. → see INTEGRATION.md.
  • No-bundler shells (F1 — inlines client JS as strings): import the markup builders (renderXxxHtml) at Node shell-generation time and inline the behaviour as an IIFE from @denindka/fast-ui/node (createXxxClientScript()). Markup + CSS are byte-identical to the bundler path — same single source. (This path is detailed below.)

Layers: ./core (zero-DOM logic/data/store, unit-tested) · ./ui (render + attach* + CSS + mount* — browser-safe) · ./node (Node-only client-script builders).

Start here — who reads what

| You are… | Start with | |---|---| | Using these components in an app (e.g. fastdoc) | INTEGRATION.md — install, mount* helpers (colour + font), the boundary | | Creating / maintaining a shared component (F1/fastsheet) | AUTHORING.md — the extract→verify→swap recipe — and docs/UI_COMPONENT_PLACEMENT.md for where it belongs | | Publishing a release | PUBLISHING.md |

The rest of this README is the overview + the no-bundler (F1) consumption path.

How you use it (this stack)

In your shell generator (the Node file that builds the HTML), import the builders and wire 3 spots:

import { createColorPickerClientScript } from "@denindka/fast-ui";
// in-repo (the F1 shell) → from "../../packages/fast-ui/index.js"
// every other consumer    → from "@denindka/fast-ui" (npm install)

const html = `
  ...your shell...

  <!-- 1) the dialog markup (must carry the data-grid-* attributes from the contract below) -->
  <div data-grid-format-color-dialog> ... swatches / tabs / preview / OK / Cancel ... </div>

  <script>
    ${createColorPickerClientScript()}   /* 2) the picker runtime, inlined as an IIFE */

    /* 3) wire YOUR product's callbacks: */
    __layersPicker.attachColorDialog(
      document.querySelector("[data-grid-format-color-dialog]"),
      {
        defaultValue: "#FF2600",
        onChange: (hex) => preview(hex),   // live, on every change
        onCommit: (hex) => applyFill(hex), // OK pressed
        onClose:  () => hideDialog(),
      }
    );
  </script>
`;

That's the whole integration. You write markup + one inlined runtime + your callbacks. You never write addEventListener, never touch the data-grid-* wiring, never write color math.

createColorPickerClientScript() builds that IIFE on demand with esbuild (a build-time dep) from the real ui/core modules — so there's no duplicated logic. After it runs, the page has window.__layersPicker.attachColorDialog(root, handlers).

DOM contract (what your markup must expose)

| Attribute | Element | Behavior | |-----------|---------|----------| | data-grid-format-color-dialog | dialog root | passed to attachColorDialog | | data-grid-format-color-dialog-choice="#RRGGBB" | swatch / pencil | click → set color | | data-grid-format-color-dialog-tab="<tab>" | tab button | click → switch tab | | data-grid-format-color-dialog-hex-input | text input | input → set from hex | | data-grid-format-color-dialog-spectrum | spectrum surface | pointerdown → pick by x/y | | data-grid-format-color-dialog-apply | OK button | click → commit | | data-grid-format-color-dialog-cancel | Cancel button | click → cancel | | data-grid-format-color-dialog-preview | swatch preview | patched: --format-dialog-current-color | | data-grid-format-color-dialog-name | label | patched: resolved color name | | data-grid-format-color-dialog-pane="<tab>" | tab pane | patched: hidden toggled by active tab |

<tab>wheel | sliders | palettes | image | pencils. This matches the F1 shell exactly, so its existing markup + e2e proofs keep working.

API

createColorPickerClientScript({ minify? }): string — the browser runtime as an inline-<script> IIFE. Exposes window.__layersPicker.attachColorDialog.

attachColorDialog(root, options): controller (the function exposed on the global):

options: {
  value? | defaultValue?: string;     // seed color (uncontrolled — the binder owns state)
  defaultTab?: "wheel"|"sliders"|"palettes"|"image"|"pencils";
  recents?: string[];
  translate?: (key, fallback) => string;   // for color names (i18n)
  rememberOnCommit?: boolean;               // push committed color into recents (default true)
  onChange?(hex, source); onCommit?(hex); onCancel?(); onClose?(); onTabChange?(tab); onRecentAdd?(hex);
}
controller: { setColor(hex); getColor(); getState(); subscribe(fn); destroy(); }

onChange = live (every click/drag); onCommit = confirmed (OK). Call destroy() on teardown.

./core (pure, for the markup side / tests): createColorPickerStore, hslToHex/hsvToHex/ rgbToHex/hexToRgb/normalizeHex, spectrumPointerToColor, pencilShade/renderColorPencilSvg, resolveColorName, and data (NAMED_COLORS, PENCIL_COLORS, WEB_SAFE_COLORS, PASTEL_COLORS, DEVELOPER_COLORS, PALETTE_COLLECTIONS).

Status / roadmap

  • createColorPickerClientScript() + attachColorDialog + core — smoke-tested (the smoke builds the IIFE, runs it in a mock browser, and asserts a click fires onChange).
  • renderColorDialogHtml(props) + COLOR_DIALOG_CSS — the dialog markup + CSS builders (verbatim from renderFormatColorDialog); F1 renders from them, byte-identical. Same for the palette dropdown and the font-family menu (renderFontFamilyMenuHtml / attachFontFamilyMenu / mountFontFamilyMenu) — both products render the same component.
  • mount* one-call helpers — consumption proven live in fastdoc (colour + font menus).

Other components

The font-family menu is already a full shared component — renderFontFamilyMenuHtml + attachFontFamilyMenu + FONT_FAMILY_MENU_CSS (+ mountFontFamilyMenu), the exact menu F1 renders. The Word/Excel colour dropdown is renderColorPaletteMenuHtml + attachColorPaletteMenu + COLOR_PALETTE_MENU_CSS. To add a NEW one, follow AUTHORING.md; the reusable primitive underneath is bindDelegated(root, { "click [selector]": (el) => … }) over a data-grid-* contract plus a core store.

For AI assistants

To add the color picker to a shell on this stack:

  1. Emit markup with the data-grid-format-color-dialog-* attributes (see the contract table).
  2. Inline ${createColorPickerClientScript()} inside the shell's <script> block.
  3. Call __layersPicker.attachColorDialog(root, { onCommit, onChange, ... }) with the product's callbacks.

Rules:

  • On the no-bundler F1 stack this is consumed in Node (shell generation); the browser there runs only the inlined string. Bundler apps (fastdoc) DO import @denindka/fast-ui/ui / /core directly in browser code — both paths are first-class (see INTEGRATION.md). Only @denindka/fast-ui/node (esbuild builders) must never reach browser code.
  • Consumers pass callbacks, never DOM listeners; never reimplement color math/state (that's core).
  • ui uses structural DOM interfaces (PickerElement, PickerDomEvent), never document/HTMLElement/CustomEvent.

Build / publish

See PUBLISHING.md. It's a real npm package (npm run builddist/ with .d.ts): the in-repo F1 shell consumes it by relative source import; every other consumer runs npm install @denindka/fast-ui. Test: npx tsx packages/fast-ui/smoke.ts.

Architecture

Part of the reusable-picker plan (project memory project_reusable_picker_package).