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

mtrl-addons

v0.6.0

Published

Additional components and utilities for the mtrl system, featuring forms, specialized elements, and extended functionality for modern applications

Readme

mtrl-addons

Extended components and utilities for the mtrl Material Design 3 component library

mtrl-addons provides high-performance, specialized components and core systems that extend mtrl's capabilities for building modern web applications. Built with the same functional composition philosophy and zero external dependencies (except mtrl as a peer dependency).

Features

  • 🚀 Virtual Scrolling - Handle millions of items with smooth 60fps scrolling
  • 📝 Form Builder - Declarative form creation with validation and state management
  • 🎨 Color Picker - Full-featured HSV picker with swatches, pipette, and variants
  • 📐 Layout System - Flexible array-based layout schemas with JSX support
  • 👆 Gesture Recognition - Touch and mouse gesture detection (tap, swipe, pinch, etc.)
  • 🎯 Viewport System - Composable virtual scrolling foundation
  • 🌳 Tree-Shaking Optimized - Import only what you need

Installation

# npm
npm install mtrl-addons mtrl

# yarn
yarn add mtrl-addons mtrl

# bun
bun add mtrl-addons mtrl

Quick Start

import { createVList, createForm, createColorPicker } from 'mtrl-addons';

Tree-Shaking Optimized Imports

mtrl-addons is optimized for tree-shaking. Constants are exported separately from component creators to minimize bundle size.

Import Patterns

| Import Type | Path | |-------------|------| | Component creators | import { createColorPicker } from 'mtrl-addons' | | ColorPicker constants | import { COLORPICKER_EVENTS } from 'mtrl-addons/components/colorpicker/constants' | | Form constants | import { FORM_EVENTS, DATA_STATE } from 'mtrl-addons/components/form/constants' | | VList constants | import { VLIST_CLASSES } from 'mtrl-addons/components/vlist/constants' | | Color utilities | import { hsvToRgb, rgbToHex } from 'mtrl-addons' | | Layout system | import { createLayout } from 'mtrl-addons/layout' | | Viewport system | import { createViewport } from 'mtrl-addons/viewport' | | Gestures | import { createGestureManager } from 'mtrl-addons/gestures' |


Components

VList (Virtual List)

High-performance virtual scrolling list for large datasets. Renders only visible items while maintaining smooth scrolling with millions of rows.

Features

  • 📊 Handles 100,000+ items efficiently
  • 🔍 Built-in search with debouncing
  • 🎛️ Filter panel integration
  • ⌨️ Keyboard navigation
  • 📍 Scroll position restoration
  • 🎯 Item selection (single/multi)
  • 📈 Stats tracking (render time, visible items)
  • ⚡ Velocity-based scroll optimization

Basic Usage

import { createVList } from 'mtrl-addons';

const vlist = createVList({
  container: '#my-list',
  template: (item) => ({
    class: 'list-item',
    children: [
      { tag: 'span', class: 'name', text: item.name },
      { tag: 'span', class: 'email', text: item.email }
    ]
  }),
  collection: {
    adapter: {
      read: async ({ page, limit }) => {
        const response = await fetch(`/api/users?page=${page}&limit=${limit}`);
        return response.json();
      }
    }
  }
});

With Layout, Search & Filters

import { createVList } from 'mtrl-addons';
import { createIconButton, createSearch, createSelect } from 'mtrl';

const vlist = createVList({
  container: '#users-list',
  class: 'users',
  
  // Layout schema - declarative UI structure
  layout: [
    ['head', { class: 'list-head' },
      [createIconButton, 'searchBtn', { icon: iconSearch, toggle: true }],
      [createIconButton, 'filterBtn', { icon: iconFilter, toggle: true }]
    ],
    ['filter-panel', { class: 'filter-panel' },
      [createSelect, 'country', { label: 'Country', options: countries }],
      [createSelect, 'role', { label: 'Role', options: roles }]
    ],
    [createSearch, 'searchBar', { placeholder: 'Search users...' }],
    ['viewport'], // Virtual scrolling area
    ['foot', { class: 'list-foot' },
      ['stats', { text: 'Loading...' }]
    ]
  ],
  
  // Search configuration
  search: {
    toggleButton: 'searchBtn',
    searchBar: 'searchBar',
    debounce: 300
  },
  
  // Filter configuration
  filter: {
    toggleButton: 'filterBtn',
    panel: 'filter-panel',
    controls: {
      country: 'country',
      role: 'role'
    }
  },
  
  // Stats display
  stats: {
    element: 'stats',
    format: ({ total, rendered }) => `${rendered} of ${total} users`
  },
  
  template: userTemplate,
  collection: {
    adapter: {
      read: async ({ page, limit, search, filters }) => {
        // search and filters are automatically populated
        return fetchUsers({ page, limit, search, ...filters });
      }
    }
  }
});

// Events
vlist.on('search:change', ({ query }) => console.log('Search:', query));
vlist.on('filter:change', ({ filters }) => console.log('Filters:', filters));
vlist.on('item:select', ({ item }) => console.log('Selected:', item));

// API
vlist.search('john');
vlist.setFilter('country', 'US');
vlist.scrollToIndex(50);
vlist.refresh();

Form

Functional form builder with declarative field configuration, validation, and state management.

Features

  • 📋 Declarative field definitions
  • ✅ Built-in validation rules
  • 🔄 Dirty/pristine state tracking
  • 🛡️ Unsaved changes protection
  • 📤 Submit handling with loading states
  • 🎯 Field-level error handling

Basic Usage

import { createForm } from 'mtrl-addons';
import { FORM_EVENTS, DATA_STATE } from 'mtrl-addons/components/form/constants';

const form = createForm({
  fields: [
    { name: 'email', type: 'email', label: 'Email', required: true },
    { name: 'password', type: 'password', label: 'Password', required: true },
    { name: 'remember', type: 'checkbox', label: 'Remember me' }
  ],
  
  // Protect unsaved changes
  protectChanges: {
    beforeUnload: true,      // Warn on page close
    onDataOverwrite: true    // Emit event when setData() called with changes
  },
  
  onSubmit: async (data) => {
    await api.login(data);
  }
});

// Mount to container
document.getElementById('login-form').appendChild(form.element);

// Events
form.on(FORM_EVENTS.CHANGE, ({ field, value }) => {
  console.log(`${field} changed to:`, value);
});

form.on(FORM_EVENTS.STATE_CHANGE, ({ state }) => {
  if (state === DATA_STATE.DIRTY) {
    console.log('Form has unsaved changes');
  }
});

form.on(FORM_EVENTS.SUBMIT, (data) => {
  console.log('Form submitted:', data);
});

form.on(FORM_EVENTS.DATA_CONFLICT, ({ currentData, newData, cancel, proceed }) => {
  if (confirm('Discard unsaved changes?')) {
    proceed();
  } else {
    cancel();
  }
});

// API
form.setData({ email: '[email protected]' });
const data = form.getData();
const isValid = form.validate();
form.reset();
form.disable();
form.enable();

ColorPicker

Full-featured color picker with HSV color area, hue slider, swatches, and multiple display variants.

Features

  • 🎨 HSV color area with saturation/brightness
  • 📊 Hue slider
  • 🔲 Opacity/alpha slider (optional)
  • 💎 Color swatches with add/remove
  • 💉 Pipette/eyedropper (native API or canvas sampling)
  • 📝 Hex input field
  • 🖼️ Multiple variants: inline, dropdown, dialog
  • 📱 Compact density mode

Basic Usage

import { createColorPicker } from 'mtrl-addons';
import { COLORPICKER_EVENTS, COLORPICKER_VARIANTS } from 'mtrl-addons/components/colorpicker/constants';

// Inline picker (always visible)
const picker = createColorPicker({
  value: '#6200ee',
  showSwatches: true,
  swatches: ['#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ff00ff', '#00ffff']
});

document.getElementById('color-picker').appendChild(picker.element);

// Events
picker.on(COLORPICKER_EVENTS.CHANGE, ({ value }) => {
  console.log('Color selected:', value);
});

picker.on(COLORPICKER_EVENTS.INPUT, ({ value }) => {
  // Live preview during drag
  document.body.style.backgroundColor = value;
});

// API
picker.setValue('#ff5722');
const hex = picker.getValue();        // '#ff5722'
const hsv = picker.getHSV();          // { h: 14, s: 86, v: 100 }
const rgb = picker.getRGB();          // { r: 255, g: 87, b: 34 }

picker.addSwatch('#9c27b0', 'Purple');
picker.setOpacity(0.5);

Dropdown Variant

const trigger = document.getElementById('color-button');

const picker = createColorPicker({
  value: '#6200ee',
  variant: COLORPICKER_VARIANTS.DROPDOWN,
  trigger: trigger,
  closeOnSelect: true
});

// Toggle programmatically
picker.open();
picker.close();
picker.toggle();

With Pipette (Color Sampling)

const picker = createColorPicker({
  value: '#ffffff',
  showPipette: true,
  // Optional: provide image for canvas-based sampling
  imageSource: document.getElementById('my-image'),
  
  onPipetteStart: () => console.log('Sampling started'),
  onPipetteEnd: (color) => console.log('Picked:', color)
});

// Trigger programmatically
const pickedColor = await picker.pickColor();

Core Systems

Layout System

Flexible array-based layout schemas for declarative UI construction. Supports mtrl components, HTML elements, and nested structures.

import { createLayout } from 'mtrl-addons/layout';
import { createButton, createTextField } from 'mtrl';

// Array-based schema
const layout = createLayout([
  'div', { class: 'form-container' },
  [
    ['header', { class: 'form-header' },
      ['h2', { text: 'Contact Form' }]
    ],
    ['main', { class: 'form-body' },
      [createTextField, 'name', { label: 'Name' }],
      [createTextField, 'email', { label: 'Email', type: 'email' }],
      [createTextField, 'message', { label: 'Message', multiline: true }]
    ],
    ['footer', { class: 'form-footer' },
      [createButton, 'submit', { text: 'Send', variant: 'filled' }],
      [createButton, 'cancel', { text: 'Cancel', variant: 'outlined' }]
    ]
  ]
]);

// Access named components
layout.name.setValue('John Doe');
layout.submit.on('click', handleSubmit);

// Append to DOM
document.body.appendChild(layout.element);

Convenience Functions

import { layout, row, stack, grid } from 'mtrl-addons/layout';

// Vertical stack
const stackLayout = stack({ gap: '1rem' });

// Horizontal row (mobile-stacks automatically)
const rowLayout = row({ gap: '1rem', mobileStack: true });

// Responsive grid
const gridLayout = grid('auto-fit', { gap: '1rem' });

Viewport System

Low-level composable virtual scrolling foundation. Used internally by VList but available for custom implementations.

import { createViewport } from 'mtrl-addons/viewport';

const viewport = createViewport({
  container: document.getElementById('scroll-container'),
  itemCount: 100000,
  estimatedItemSize: 48,
  overscan: 5, // Extra items to render outside viewport
  
  renderItem: (index) => {
    const div = document.createElement('div');
    div.textContent = `Item ${index}`;
    return div;
  },
  
  onRangeChange: ({ start, end }) => {
    console.log(`Rendering items ${start} to ${end}`);
  }
});

// API
viewport.scrollToIndex(500);
viewport.refresh();
viewport.destroy();

Feature Composition

import { 
  withBase, 
  withVirtual, 
  withScrolling, 
  withRendering 
} from 'mtrl-addons/viewport';

// Build custom viewport with specific features
const customViewport = pipe(
  withBase,
  withVirtual,
  withScrolling,
  withRendering
)(config);

Gesture System

Touch and mouse gesture recognition with support for tap, swipe, long-press, pinch, rotate, and pan.

import { createGestureManager } from 'mtrl-addons/gestures';

const gestures = createGestureManager(element, {
  // Tap detection
  onTap: ({ x, y, target }) => {
    console.log('Tapped at', x, y);
  },
  
  // Swipe detection
  onSwipe: ({ direction, velocity, distance }) => {
    console.log(`Swiped ${direction} with velocity ${velocity}`);
  },
  
  // Long press
  onLongPress: ({ x, y, duration }) => {
    console.log('Long pressed for', duration, 'ms');
  },
  
  // Pinch (zoom)
  onPinch: ({ scale, center }) => {
    element.style.transform = `scale(${scale})`;
  },
  
  // Rotation
  onRotate: ({ angle, center }) => {
    element.style.transform = `rotate(${angle}deg)`;
  },
  
  // Pan (drag)
  onPan: ({ deltaX, deltaY, state }) => {
    if (state === 'move') {
      element.style.transform = `translate(${deltaX}px, ${deltaY}px)`;
    }
  }
});

// Configuration
const gestures = createGestureManager(element, {
  tap: { maxDuration: 300 },
  swipe: { minDistance: 50, minVelocity: 0.3 },
  longPress: { duration: 500 },
  pinch: { minScale: 0.5, maxScale: 3 }
});

// Cleanup
gestures.destroy();

Individual Gesture Detectors

import { detectTap, detectSwipe, detectPinch } from 'mtrl-addons/gestures';

// Use specific detectors for lighter bundles
const tapDetector = detectTap(element, { onTap: handleTap });
const swipeDetector = detectSwipe(element, { onSwipe: handleSwipe });

Color Utilities

Pure functions for color conversion (tree-shakeable, included in main bundle).

import {
  hsvToRgb,
  rgbToHsv,
  hsvToHex,
  hexToHsv,
  rgbToHex,
  hexToRgb,
  isValidHex,
  normalizeHex,
  getContrastColor
} from 'mtrl-addons';

// HSV ↔ RGB
const rgb = hsvToRgb({ h: 200, s: 80, v: 90 }); // { r: 46, g: 184, b: 230 }
const hsv = rgbToHsv({ r: 255, g: 128, b: 0 }); // { h: 30, s: 100, v: 100 }

// Hex conversions
const hex = rgbToHex({ r: 255, g: 0, b: 128 }); // '#ff0080'
const rgb2 = hexToRgb('#ff0080'); // { r: 255, g: 0, b: 128 }

// Validation & normalization
isValidHex('#ff0080');      // true
isValidHex('ff0080');       // true
normalizeHex('f00');        // '#ff0000'
normalizeHex('#F00');       // '#ff0000'

// Contrast color (for text on colored backgrounds)
getContrastColor('#ffffff'); // '#000000'
getContrastColor('#000000'); // '#ffffff'

Development

# Install dependencies
bun install

# Build package (for distribution)
bun run build

# Build for mtrl-app integration
bun run build:app

# Run tests
bun test

# Watch mode
bun run dev

# Link mtrl for local development
bun run link:mtrl

Browser Support

mtrl-addons supports modern browsers:

  • Chrome (latest)
  • Firefox (latest)
  • Safari (latest)
  • Edge (latest)

Peer Dependencies

  • mtrl ^0.6.2 - Core Material Design 3 component library

Related Packages

  • mtrl - Core Material Design 3 component library
  • mtrl-app - Documentation and showcase application

License

MIT

Contributing

Contributions are welcome! Please follow the existing code style and include tests for new features.

# Run tests before submitting
bun test

# Build to verify no errors
bun run build