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

@dev_idkwhoami/grid-system

v1.1.2

Published

A flexible grid-based layout system with drag-and-drop functionality

Readme

Grid System

npm version npm downloads license

A flexible, dependency-free grid-based layout system with drag-and-drop functionality, built with TypeScript and Web Components.

Features

  • Zero Dependencies - Built entirely from scratch
  • TypeScript Support - Full type definitions included
  • Web Components - Auto-registering custom elements
  • Drag & Drop - Custom implementation with smooth interactions
  • Resizable Elements - Interactive resize with constraint enforcement
  • Meta-Properties System - Define custom properties for elements with rich form controls
  • Properties Drawer - Optional collapsible panel for editing element properties
  • State Management - Built-in undo system and persistence
  • Responsive - Container-aware design
  • Mobile Compatible - Supports both mouse and touch events
  • Dark/Light Mode - Full theme support with system preference detection
  • Customizable - CSS variables for easy theming

Installation

npm install @dev_idkwhoami/grid-system

Quick Start

1. Import the package

import {GridSystem} from '@dev_idkwhoami/grid-system';
import '@dev_idkwhoami/grid-system/styles';

2. Define your elements

const elements = [
  {
    id: 'widget',
    name: 'Widget',
    description: 'A simple widget',
    minSize: { width: 2, height: 2 },
    maxSize: { width: 6, height: 6 },
    defaultSize: { width: 3, height: 3 },
    aspectRatioLocked: false,
    reusable: true,
    group: 'Components',
    metaProperties: [
      {
        key: 'title',
        label: 'Title',
        type: 'string',
        defaultValue: 'My Widget'
      },
      {
        key: 'showBorder',
        label: 'Show Border',
        type: 'checkbox',
        defaultValue: true
      }
    ]
  }
];

3. Create a grid instance

const grid = GridSystem.create({
  width: 12,
  height: 8,
  elements: elements,
  callbacks: {
    onElementAdded: (element) => console.log('Added:', element),
    onElementMoved: (element, oldPos, newPos) => console.log('Moved:', element),
    onElementResized: (element, oldSize, newSize) => console.log('Resized:', element),
    onElementRemoved: (id, element) => console.log('Removed:', element),
    onElementSelected: (element, viaDrag) => console.log('Selected:', element),
    onPropertyChanged: (element, key, oldValue, newValue) => console.log('Property changed:', key),
    onPropertiesSaved: (element) => console.log('Properties saved:', element.metaProperties)
  }
});

4. Add web components to your HTML

<div style="display: grid; grid-template-columns: 300px 1fr 320px; gap: 20px; height: 600px;">
  <!-- Element Library -->
  <element-library instance-id="my-grid"></element-library>

  <!-- Grid Canvas -->
  <grid-system instance-id="my-grid"></grid-system>

  <!-- Properties Drawer (optional) -->
  <properties-drawer instance-id="my-grid"></properties-drawer>
</div>

API Reference

GridSystem.create(config)

Creates a new grid instance.

Parameters:

  • config: GridConfig - Configuration object

Returns: GridSystemInstance

GridConfig

interface GridConfig {
  width: number;                          // Grid width in slots
  height: number;                         // Grid height in slots
  elements: ElementDefinition[];          // Available elements
  callbacks?: EventCallbacks;             // Optional event handlers
  maxUndoStates?: number;                 // Max undo history (default: 20)
  localStoragePrefix?: string;            // localStorage key prefix (default: 'grid-system')
  autoSaveToLocalStorage?: boolean;       // Enable auto-save (default: false)
    autoLoadFromLocalStorage?: boolean;     // Auto-load on init (default: false)
  instanceName?: string;                  // Custom instance name
    theme?: 'system' | 'light' | 'dark';    // Color theme (default: 'system')
}

ElementDefinition

interface ElementDefinition {
  id: string;                      // Unique identifier
  name: string;                    // Display name
  description: string;             // Description
  minSize: Size;                   // Minimum size in slots
  maxSize: Size;                   // Maximum size in slots
  defaultSize: Size;               // Default size when placed
  aspectRatioLocked: boolean;      // Lock aspect ratio during resize
  reusable: boolean;               // Can be placed multiple times
  group: string;                   // Category for library grouping
  metaProperties?: PropertyDefinition[]; // Optional custom properties
}

GridSystemInstance Methods

getState(): GridState

Returns the current grid state (deep copy).

const state = grid.getState();
console.log(state.elements);

loadState(state: GridState): void

Loads a state into the grid.

grid.loadState({
  gridWidth: 12,
  gridHeight: 8,
  elements: [...]
});

undo(): boolean

Undoes the last action. Returns true if successful.

const undone = grid.undo();

getElement(id: string): ElementInstance | null

Retrieves an element by its unique ID.

const element = grid.getElement('widget-123');

removeElement(id: string): boolean

Removes an element from the grid. Returns true if successful.

grid.removeElement('widget-123');

clearLocalStorage(): void

Clears saved state from localStorage.

grid.clearLocalStorage();

destroy(): void

Cleans up and destroys the grid instance.

grid.destroy();

Meta-Properties System

The meta-properties system allows you to define custom properties for your elements with rich form controls.

Property Types

The system supports 13 different property types:

  • string - Text input
  • number - Number input with optional min/max/step
  • textarea - Multi-line text input
  • email - Email input with validation
  • url - URL input with validation
  • date - Date picker
  • checkbox - Single checkbox
  • toggle - Toggle switch
  • select - Dropdown menu
  • multiselect - Multiple checkboxes
  • radio - Radio button group
  • color - Color picker
  • range - Slider with min/max

Property Definition

interface PropertyDefinition {
  key: string;              // Unique property key
  label: string;            // Display label
  type: PropertyType;       // Property type (see above)
  defaultValue: any;        // Default value
  options?: PropertyOption[]; // For select/multiselect/radio
  min?: number;             // For number/range
  max?: number;             // For number/range
  step?: number;            // For number/range
  placeholder?: string;     // For text inputs
  rows?: number;            // For textarea
}

Example: Widget with Properties

{
  id: 'widget',
  name: 'Dashboard Widget',
  description: 'Configurable widget',
  // ... size config
  metaProperties: [
    {
      key: 'title',
      label: 'Widget Title',
      type: 'string',
      defaultValue: 'My Widget',
      placeholder: 'Enter title'
    },
    {
      key: 'refreshRate',
      label: 'Refresh Rate (seconds)',
      type: 'range',
      defaultValue: 60,
      min: 10,
      max: 300,
      step: 10
    },
    {
      key: 'chartType',
      label: 'Chart Type',
      type: 'radio',
      defaultValue: 'line',
      options: [
        { label: 'Line Chart', value: 'line' },
        { label: 'Bar Chart', value: 'bar' },
        { label: 'Pie Chart', value: 'pie' }
      ]
    },
    {
      key: 'features',
      label: 'Features',
      type: 'multiselect',
      defaultValue: ['legend', 'tooltip'],
      options: [
        { label: 'Show Legend', value: 'legend' },
        { label: 'Show Tooltip', value: 'tooltip' },
        { label: 'Enable Zoom', value: 'zoom' }
      ]
    }
  ]
}

Properties Drawer Component

The optional <properties-drawer> component provides a collapsible UI for editing element properties.

Attributes

  • instance-id - Grid instance ID (required)
  • initially-collapsed - Start in collapsed state (optional)

Custom Collapse Icon

You can customize the collapse icon using a named slot:

<properties-drawer instance-id="my-grid">
  <svg slot="collapse-icon" viewBox="0 0 24 24">
    <!-- Your custom icon -->
  </svg>
</properties-drawer>

Events

  • properties-drawer-collapsed - Fired when drawer collapses
  • properties-drawer-expanded - Fired when drawer expands

Dynamic Property Schemas

You can define property schemas dynamically based on the element instance:

const grid = GridSystem.create({
  // ...
  propertySchemaProvider: (element) => {
    // Return different properties based on element type
    if (element.definitionId === 'chart') {
      return [
        { key: 'dataSource', label: 'Data Source', type: 'url', defaultValue: '' },
        // ... more properties
      ];
    }
    return [];
  }
});

Event Callbacks

onElementAdded

Triggered when an element is successfully placed on the grid.

onElementAdded: (element: ElementInstance) => {
  console.log('Element added:', element);
}

onElementMoved

Triggered when an element's position changes.

onElementMoved: (element: ElementInstance, oldPosition: Position, newPosition: Position) => {
  console.log(`Moved from (${oldPosition.x},${oldPosition.y}) to (${newPosition.x},${newPosition.y})`);
}

onElementResized

Triggered when an element is resized.

onElementResized: (element: ElementInstance, oldSize: Size, newSize: Size) => {
  console.log(`Resized from ${oldSize.width}×${oldSize.height} to ${newSize.width}×${newSize.height}`);
}

onElementRemoved

Triggered when an element is removed from the grid.

onElementRemoved: (elementId: string, element: ElementInstance) => {
  console.log('Element removed:', elementId);
}

onSave

Triggered when the state should be saved (after every change).

onSave: (state: GridState) => {
  // Save to backend, localStorage, etc.
  fetch('/api/save', {
    method: 'POST',
    body: JSON.stringify(state)
  });
}

onLoad

Called during initialization to load saved state.

onLoad: () => {
  const saved = localStorage.getItem('my-grid-state');
  return saved ? JSON.parse(saved) : null;
}

onElementSelected

Triggered when an element is selected or deselected. Includes whether selection was via drag.

onElementSelected: (element: ElementInstance | null, viaDrag: boolean) => {
  if (element) {
    console.log(`Selected ${element.definitionId} via ${viaDrag ? 'drag' : 'click'}`);
  } else {
    console.log('Element deselected');
  }
}

onPropertyChanged

Triggered when a property value changes (before save).

onPropertyChanged: (element: ElementInstance, key: string, oldValue: any, newValue: any) => {
  console.log(`Property "${key}" changed from`, oldValue, 'to', newValue);
}

onPropertiesSaved

Triggered when property changes are saved.

onPropertiesSaved: (element: ElementInstance) => {
  console.log('Properties saved:', element.metaProperties);
  // Sync to backend
  fetch(`/api/elements/${element.uniqueId}`, {
    method: 'PATCH',
    body: JSON.stringify(element.metaProperties)
  });
}

Styling & Customization

The grid system uses CSS variables for easy customization. Override these in your own CSS:

:root {
  /* Colors */
  --grid-border-color: #333;
  --grid-slot-line-color: #ddd;
  --element-border-color: #999;
  --element-bg-color: #fff;
  --element-selected-border-color: #0066ff;
  --element-invalid-tint: rgba(255, 0, 0, 0.3);
  --delete-button-bg: #ff0000;
  --resize-handle-color: #0066ff;
  --library-bg: #f5f5f5;
  --library-item-hover-bg: #e0e0e0;

  /* Dimensions */
  --resize-handle-size: 10px;
  --border-width: 1px;
  --selected-border-width: 2px;

  /* Effects */
  --transition-speed: 200ms;
  --empty-state-opacity: 0.3;
}

Examples

Basic Example

See examples/basic/index.html for a complete working example.

With localStorage Persistence

const grid = GridSystem.create({
  width: 16,
  height: 9,
  elements: elements,
  autoSaveToLocalStorage: true,
  instanceName: 'my-dashboard'
});

Multiple Grids on Same Page

const grid1 = GridSystem.create({
  width: 12,
  height: 8,
  elements: elements1,
  instanceName: 'grid-1'
});

const grid2 = GridSystem.create({
  width: 16,
  height: 9,
  elements: elements2,
  instanceName: 'grid-2'
});
<grid-system instance-id="grid-1"></grid-system>
<grid-system instance-id="grid-2"></grid-system>

Development

Build

npm run build

Development Mode

npm run dev

Type Checking

npm run type-check

Testing

npm test

Browser Support

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

License

MIT

Contributing

Contributions are welcome! Please open an issue or submit a pull request.

Acknowledgments

Built with ❤️ using TypeScript and Web Components.