@dev_idkwhoami/grid-system
v1.1.2
Published
A flexible grid-based layout system with drag-and-drop functionality
Maintainers
Readme
Grid System
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-systemQuick 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 inputnumber- Number input with optional min/max/steptextarea- Multi-line text inputemail- Email input with validationurl- URL input with validationdate- Date pickercheckbox- Single checkboxtoggle- Toggle switchselect- Dropdown menumultiselect- Multiple checkboxesradio- Radio button groupcolor- Color pickerrange- 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 collapsesproperties-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 buildDevelopment Mode
npm run devType Checking
npm run type-checkTesting
npm testBrowser 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.
