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

@duskmoon-dev/el-base

v1.5.5

Published

Core utilities and base classes for DuskMoon custom elements.

Readme

@duskmoon-dev/el-base

Core utilities and base classes for DuskMoon custom elements.

Installation

bun add @duskmoon-dev/el-base

Usage

BaseElement

The BaseElement class provides a foundation for creating custom elements with:

  • Shadow DOM setup with adoptedStyleSheets
  • Reactive properties with attribute reflection
  • Batched updates using microtask queue
  • Style injection utilities
  • Event emission helpers
import { BaseElement, css } from '@duskmoon-dev/el-base';

const styles = css`
  :host {
    display: block;
  }
  .greeting {
    color: var(--dm-primary);
  }
`;

class MyGreeting extends BaseElement {
  static properties = {
    name: { type: String, reflect: true, default: 'World' },
  };

  declare name: string;

  constructor() {
    super();
    this.attachStyles(styles);
  }

  render() {
    return `<div class="greeting">Hello, ${this.name}!</div>`;
  }
}

customElements.define('my-greeting', MyGreeting);

CSS Utilities

css Template Tag

Creates a CSSStyleSheet from a template literal:

import { css } from '@duskmoon-dev/el-base';

const styles = css`
  :host {
    display: inline-flex;
  }
  button {
    padding: 0.5rem 1rem;
  }
`;

combineStyles

Combines multiple stylesheets:

import { combineStyles } from '@duskmoon-dev/el-base';

const combinedStyles = combineStyles(baseStyles, themeStyles, componentStyles);

cssVars

Creates CSS custom property declarations:

import { cssVars } from '@duskmoon-dev/el-base';

const vars = cssVars({
  'dm-primary': '#3b82f6',
  'dm-spacing': '1rem',
});
// Returns: '--dm-primary: #3b82f6; --dm-spacing: 1rem;'

Default Theme

The package includes default CSS custom properties for theming. Import the theme stylesheet:

import { defaultTheme, resetStyles } from '@duskmoon-dev/el-base';

// Apply to shadow root
this.shadowRoot.adoptedStyleSheets = [resetStyles, defaultTheme, componentStyles];

Color Tokens

| Variable | Description | | ---------------- | ---------------------- | | --dm-primary | Primary brand color | | --dm-secondary | Secondary color | | --dm-success | Success/positive color | | --dm-warning | Warning color | | --dm-error | Error/danger color | | --dm-info | Information color |

Gray Scale

| Variable | Description | | --------------------------------------- | ------------- | | --dm-gray-50 | Lightest gray | | --dm-gray-100 through --dm-gray-800 | Gray scale | | --dm-gray-900 | Darkest gray |

Typography

| Variable | Description | | ---------------------------------------------------------------------------------------------------------- | ---------------- | | --dm-font-family | Base font family | | --dm-font-size-xs through --dm-font-size-2xl | Font sizes | | --dm-font-weight-normal, --dm-font-weight-medium, --dm-font-weight-semibold, --dm-font-weight-bold | Font weights | | --dm-line-height-tight, --dm-line-height-normal, --dm-line-height-relaxed | Line heights |

Spacing

| Variable | Description | | ----------------- | --------------------- | | --dm-spacing-xs | Extra small (0.25rem) | | --dm-spacing-sm | Small (0.5rem) | | --dm-spacing-md | Medium (1rem) | | --dm-spacing-lg | Large (1.5rem) | | --dm-spacing-xl | Extra large (2rem) |

Border Radius

| Variable | Description | | ------------------ | ---------------- | | --dm-radius-sm | Small radius | | --dm-radius-md | Medium radius | | --dm-radius-lg | Large radius | | --dm-radius-full | Full/pill radius |

Shadows

| Variable | Description | | ---------------- | ------------- | | --dm-shadow-sm | Small shadow | | --dm-shadow-md | Medium shadow | | --dm-shadow-lg | Large shadow |

Transitions

| Variable | Description | | ------------------------ | ------------------------- | | --dm-transition-fast | Fast transition (150ms) | | --dm-transition-normal | Normal transition (200ms) | | --dm-transition-slow | Slow transition (300ms) |

API

BaseElement

| Method | Description | | ---------------------- | ------------------------------------------------ | | attachStyles(styles) | Attach one or more stylesheets to Shadow DOM | | render() | Override to return HTML content string | | update() | Called when reactive properties change (batched) | | emit(name, detail?) | Emit a CustomEvent from the element | | query(selector) | Query single element in Shadow DOM | | queryAll(selector) | Query all matching elements in Shadow DOM |

Property Definitions

Define reactive properties with automatic attribute reflection:

static properties = {
  // Simple string property
  label: { type: String },

  // Boolean with attribute reflection
  disabled: { type: Boolean, reflect: true },

  // Number with default value
  count: { type: Number, default: 0 },

  // Custom attribute name (kebab-case)
  maxItems: { type: Number, attribute: 'max-items' },

  // Object/Array (not reflected to attributes)
  data: { type: Object },
  items: { type: Array, default: [] },
};

Property definition options:

| Option | Type | Description | | ----------- | ---------- | ------------------------------------------------------------------ | | type | Function | Type constructor: String, Number, Boolean, Object, Array | | reflect | boolean | Whether to reflect property to attribute | | attribute | string | Custom attribute name (defaults to lowercase property name) | | default | any | Default value for the property |

Lifecycle

class MyElement extends BaseElement {
  // Called when element is added to DOM
  connectedCallback() {
    super.connectedCallback();
    // Setup code
  }

  // Called when element is removed from DOM
  disconnectedCallback() {
    super.disconnectedCallback();
    // Cleanup code
  }

  // Called when properties change (batched)
  update() {
    // Re-render or update DOM
    this.shadowRoot.innerHTML = this.render();
  }

  // Return HTML string for Shadow DOM content
  render() {
    return `<div>Content</div>`;
  }
}

TypeScript Types

import type {
  PropertyDefinition,
  PropertyDefinitions,
  Size,
  Variant,
  ValidationState,
  BaseElementProps,
  SizableProps,
  VariantProps,
  FormElementProps,
  ValidatableProps,
  ValueChangeEventDetail,
} from '@duskmoon-dev/el-base';

Creating Custom Elements

Full example of a custom element:

import { BaseElement, css, defaultTheme, resetStyles } from '@duskmoon-dev/el-base';

const styles = css`
  :host {
    display: inline-block;
  }

  .counter {
    display: flex;
    align-items: center;
    gap: var(--dm-spacing-sm);
  }

  button {
    padding: var(--dm-spacing-xs) var(--dm-spacing-sm);
    border-radius: var(--dm-radius-sm);
    background: var(--dm-primary);
    color: white;
    border: none;
    cursor: pointer;
    transition: opacity var(--dm-transition-fast);
  }

  button:hover {
    opacity: 0.9;
  }

  .value {
    min-width: 2rem;
    text-align: center;
    font-weight: var(--dm-font-weight-medium);
  }
`;

export class MyCounter extends BaseElement {
  static properties = {
    value: { type: Number, reflect: true, default: 0 },
    min: { type: Number, default: 0 },
    max: { type: Number, default: 100 },
  };

  declare value: number;
  declare min: number;
  declare max: number;

  constructor() {
    super();
    this.attachStyles(resetStyles, defaultTheme, styles);
  }

  connectedCallback() {
    super.connectedCallback();
    this.shadowRoot?.addEventListener('click', this.handleClick.bind(this));
  }

  private handleClick(e: Event) {
    const target = e.target as HTMLElement;
    if (target.matches('[data-action="decrement"]')) {
      this.decrement();
    } else if (target.matches('[data-action="increment"]')) {
      this.increment();
    }
  }

  private decrement() {
    if (this.value > this.min) {
      this.value--;
      this.emit('change', { value: this.value });
    }
  }

  private increment() {
    if (this.value < this.max) {
      this.value++;
      this.emit('change', { value: this.value });
    }
  }

  render() {
    return `
      <div class="counter">
        <button data-action="decrement" ${this.value <= this.min ? 'disabled' : ''}>-</button>
        <span class="value">${this.value}</span>
        <button data-action="increment" ${this.value >= this.max ? 'disabled' : ''}>+</button>
      </div>
    `;
  }
}

export function register() {
  if (!customElements.get('my-counter')) {
    customElements.define('my-counter', MyCounter);
  }
}

License

MIT