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

box-me

v0.0.5

Published

[![npm version](https://badge.fury.io/js/box-me.svg)](https://badge.fury.io/js/box-me) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) [![Angular](https://img.shields.io/badge/Angular-17+-red.svg

Readme

Box-Me Angular Component Library

npm version License: MIT Angular

A powerful, flexible, and highly customizable Angular library for building complex, interactive, and nested grid-based layouts. Box-Me enables you to create sophisticated grid systems with unlimited nesting capabilities, cross-grid interactions, and dynamic styling.

🚀 Key Features

  • 🔄 Unlimited Nesting: Create grids within grids to any depth with full interaction support
  • 🎯 Cross-Grid Highlighting: Perform interactions that span across different nested grids seamlessly
  • ⚙️ Advanced Configuration: Fine-tune behavior with detailed BoxConfig objects
  • 🎨 Custom Templates: Use your own ng-template for complete control over item rendering
  • 🎭 Dynamic Styling: Leverage CSS custom properties and inline styles for easy theming
  • 🏗️ Service-Oriented Architecture: Clean, maintainable codebase with clear separation of concerns
  • 📦 Standalone Components: Built with modern Angular standalone components for easy integration
  • 📊 Smart Spot Calculation: Automatic calculation of spots and totalSpots for complex nested structures
  • 🎪 Demo Presets: Ready-to-use demo boxes for quick prototyping and learning

📦 Installation

npm install box-me

🧩 Library Architecture

Box-Me is built with a modular, service-oriented architecture that provides maximum flexibility and maintainability.

Core Components

  • BoxComponent: The main component for rendering interactive grid layouts
  • BoxFactoryService: Factory service for creating Box instances with various configurations
  • DemoBoxesService: Preset service providing ready-to-use demo layouts

Core Services

  • GridAnalysisService: Performs complex layout calculations and position analysis
  • HighlightingService: Manages cross-grid highlighting and selection states
  • ItemManipulationService: Handles item placement, removal, and grid modifications
  • BoxEventHandlerService: Orchestrates user interactions and event handling
  • BoxStyleService: Manages dynamic styling and CSS class application
  • BoxTemplateService: Handles template rendering and context management
  • VisualPositionService: Calculates and manages nested position paths
  • ContextBuilderService: Builds template contexts for custom item rendering

🛠️ Quick Start

Basic Usage

import { Component } from '@angular/core';
import { BoxComponent } from 'box-me';
import { Box, Item, BoxFactoryService } from 'box-me';

@Component({
  selector: 'app-example',
  standalone: true,
  imports: [BoxComponent],
  template: `
    <lib-box 
      [box]="myBox" 
      [selectedItem]="selectedItem" 
      (itemsChange)="onItemsChange($event)"
      (itemClick)="onItemClick($event)">
      <ng-template #itemTemplate let-item let-rowIndex="rowIndex" let-colIndex="colIndex">
        <div class="custom-item" [class.selected]="item.isVisible">
          <img [src]="item.imageUrl" *ngIf="item.isVisible" [alt]="item.id">
          <div class="placeholder" *ngIf="!item.isVisible">
            {{ rowIndex }},{{ colIndex }}
          </div>
        </div>
      </ng-template>
    </lib-box>
  `,
  styles: [`
    .custom-item {
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      border: 1px solid #ccc;
      border-radius: 4px;
    }
    .custom-item.selected {
      border-color: #007bff;
      background-color: #f8f9fa;
    }
    .placeholder {
      color: #6c757d;
      font-size: 12px;
    }
  `]
})
export class ExampleComponent {
  myBox: Box;
  selectedItem: Item | null = { 
    id: 'item-1', 
    imageUrl: 'https://via.placeholder.com/50', 
    isVisible: true 
  };

  constructor(private boxFactory: BoxFactoryService) {
    this.myBox = this.boxFactory.createBox(
      'My First Box', 
      2, 
      2, 
      'A simple 2x2 grid box'
    );
  }

  onItemsChange(box: Box) {
    console.log('Box updated:', box);
    console.log('Total spots:', box.totalSpots);
    console.log('Current spots:', box.spots);
  }

  onItemClick(event: any) {
    console.log('Item clicked:', event);
  }
}

Using Demo Presets

import { Component } from '@angular/core';
import { BoxComponent } from 'box-me';
import { Box, DemoBoxesService } from 'box-me';

@Component({
  selector: 'app-demo',
  standalone: true,
  imports: [BoxComponent],
  template: `
    <div class="demo-container">
      <h2>Demo Layouts</h2>
      
      <div class="demo-section">
        <h3>Complex Layout with Styling</h3>
        <lib-box [box]="complexBox" (itemsChange)="onBoxChange($event)">
          <ng-template #itemTemplate let-item>
            <div class="demo-item">{{ item.name || 'Empty' }}</div>
          </ng-template>
        </lib-box>
      </div>

      <div class="demo-section">
        <h3>Clean Layout</h3>
        <lib-box [box]="cleanBox" (itemsChange)="onBoxChange($event)">
          <ng-template #itemTemplate let-item>
            <div class="demo-item clean">{{ item.name || 'Empty' }}</div>
          </ng-template>
        </lib-box>
      </div>

      <div class="demo-section">
        <h3>Simple 5x5 Grid</h3>
        <lib-box [box]="simpleBox" (itemsChange)="onBoxChange($event)">
          <ng-template #itemTemplate let-item>
            <div class="demo-item simple">{{ item.name || 'Empty' }}</div>
          </ng-template>
        </lib-box>
      </div>
    </div>
  `,
  styles: [`
    .demo-container {
      padding: 20px;
      max-width: 1200px;
      margin: 0 auto;
    }
    .demo-section {
      margin-bottom: 40px;
    }
    .demo-item {
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      border: 1px solid #ddd;
      border-radius: 4px;
      font-size: 12px;
      background: #f8f9fa;
    }
    .demo-item.clean {
      background: #e9ecef;
      border-color: #adb5bd;
    }
    .demo-item.simple {
      background: #fff;
      border-color: #007bff;
    }
  `]
})
export class DemoComponent {
  complexBox: Box;
  cleanBox: Box;
  simpleBox: Box;

  constructor(private demoBoxes: DemoBoxesService) {
    this.complexBox = this.demoBoxes.getDemoBigMiddleSpotBox();
    this.cleanBox = this.demoBoxes.getBigMiddleSpotBox();
    this.simpleBox = this.demoBoxes.get5x5Box();
  }

  onBoxChange(box: Box) {
    console.log('Box changed:', box.name, 'Total spots:', box.totalSpots);
  }
}

📋 Data Models

Box Interface

interface Box {
  name: string;                    // Display name for the box
  description?: string;            // Optional description
  rows: number;                    // Number of rows in the grid
  cols: number;                    // Number of columns in the grid
  spots: number;                   // Current box capacity (rows * cols)
  totalSpots: number;              // Total capacity including nested boxes
  items: BoxItem[][];              // 2D array of items (Item or nested Box)
  config?: BoxConfig;              // Optional configuration
  rowStyles?: { [rowIndex: number]: RowColumnStyle };
  colStyles?: { [rowIndex: number]: { [colIndex: number]: RowColumnStyle } };
  itemStyles?: { [rowIndex: number]: { [colIndex: number]: RowColumnStyle } };
  placeholderStyles?: { [rowIndex: number]: { [colIndex: number]: RowColumnStyle } };
}

Item Interface

interface Item {
  id: string;                      // Unique identifier
  imageUrl: string;                // URL for item image
  isVisible: boolean;              // Visibility state
  data?: any;                      // Custom data
}

BoxConfig Interface

interface BoxConfig {
  general?: GeneralConfig;
  single?: SingleSelectionConfig;
  multiple?: MultipleSelectionConfig;
  debug?: boolean;
}

interface GeneralConfig {
  selectionMode?: 'single' | 'multiple';
  maxItemSize?: string;
  boxBorderRadius?: string;
  boxPadding?: string;
  rowsGap?: string;
  colsGap?: string;
  rowMinHeight?: string;
}

interface SingleSelectionConfig {
  clearSpotOnClickWhenSelected?: boolean;
}

interface MultipleSelectionConfig {
  enableCrossBoxHighlighting?: boolean;
  continueHighlightingAfterCompletingTheSelection?: boolean;
}

🎨 Advanced Configuration Examples

Single Selection Mode

const singleSelectionConfig: BoxConfig = {
  general: {
    selectionMode: 'single',
    maxItemSize: '80px',
    boxBorderRadius: '8px',
    boxPadding: '12px',
    rowsGap: '8px',
    colsGap: '8px',
    rowMinHeight: '60px'
  },
  single: {
    clearSpotOnClickWhenSelected: true
  }
};

const singleSelectionBox = this.boxFactory.createBox(
  'Single Selection Box',
  3,
  3,
  'Box with single selection mode',
  singleSelectionConfig
);

Multiple Selection with Cross-Grid Highlighting

const multipleSelectionConfig: BoxConfig = {
  general: {
    selectionMode: 'multiple',
    maxItemSize: '60px',
    boxBorderRadius: '4px',
    boxPadding: '8px',
    rowsGap: '4px',
    colsGap: '4px',
    rowMinHeight: '50px'
  },
  multiple: {
    enableCrossBoxHighlighting: true,
    continueHighlightingAfterCompletingTheSelection: false
  }
};

const multipleSelectionBox = this.boxFactory.createBox(
  'Multiple Selection Box',
  4,
  4,
  'Box with cross-grid highlighting',
  multipleSelectionConfig
);

🔄 Nested Grids

Creating Nested Structures

// Create child boxes
const childBox1 = this.boxFactory.createBox(
  'Child Box 1',
  2,
  2,
  'A nested 2x2 grid',
  { general: { selectionMode: 'multiple' } }
);

const childBox2 = this.boxFactory.createBox(
  'Child Box 2',
  1,
  3,
  'A nested 1x3 grid',
  { general: { selectionMode: 'single' } }
);

// Create parent box with nested children
const parentBox = this.boxFactory.createBox(
  'Parent Box',
  2,
  3,
  'Parent container with nested boxes',
  { general: { selectionMode: 'multiple' } },
  [
    [0, 0, childBox1],  // Place childBox1 at row 0, column 0
    [1, 1, childBox2]   // Place childBox2 at row 1, column 1
  ]
);

Complex Nested Layout

// Create a complex dashboard layout
const headerBox = this.boxFactory.createBox(
  'Header',
  1,
  4,
  'Dashboard header',
  { general: { selectionMode: 'single' } }
);

const sidebarBox = this.boxFactory.createBox(
  'Sidebar',
  3,
  1,
  'Navigation sidebar',
  { general: { selectionMode: 'single' } }
);

const contentBox = this.boxFactory.createBox(
  'Content',
  2,
  2,
  'Main content area',
  { general: { selectionMode: 'multiple' } }
);

const footerBox = this.boxFactory.createBox(
  'Footer',
  1,
  4,
  'Dashboard footer',
  { general: { selectionMode: 'single' } }
);

const dashboardBox = this.boxFactory.createBox(
  'Dashboard',
  3,
  3,
  'Complete dashboard layout',
  { general: { selectionMode: 'multiple' } },
  [
    [0, 0, headerBox],    // Header spans top row
    [1, 0, sidebarBox],   // Sidebar on left
    [1, 1, contentBox],   // Content in center
    [2, 0, footerBox]     // Footer spans bottom row
  ]
);

🎨 Advanced Styling

Row and Column Styling

const styledBox = this.boxFactory.createBox(
  'Styled Box',
  3,
  3,
  'Box with custom row and column styling',
  this.defaultConfig,
  [],
  // Row styles
  {
    0: {
      classes: ['header-row'],
      styles: { 'background-color': '#f8f9fa', 'font-weight': 'bold' }
    },
    2: {
      classes: ['footer-row'],
      styles: { 'background-color': '#e9ecef', 'border-top': '2px solid #dee2e6' }
    }
  },
  // Column styles
  {
    0: {
      0: { classes: ['first-col'], styles: { 'border-left': '3px solid #007bff' } },
      1: { classes: ['first-col'], styles: { 'border-left': '3px solid #007bff' } },
      2: { classes: ['first-col'], styles: { 'border-left': '3px solid #007bff' } }
    }
  }
);

Item and Placeholder Styling

const itemStyledBox = this.boxFactory.createBox(
  'Item Styled Box',
  2,
  2,
  'Box with custom item and placeholder styling',
  this.defaultConfig,
  [],
  {}, // Row styles
  {}, // Column styles
  // Item styles
  {
    0: {
      0: { 
        classes: ['featured-item'],
        styles: { 
          'background': 'linear-gradient(45deg, #ff6b6b, #feca57)',
          'color': 'white',
          'font-weight': 'bold'
        }
      }
    }
  },
  // Placeholder styles
  {
    1: {
      1: {
        classes: ['special-placeholder'],
        styles: {
          'background-color': '#e3f2fd',
          'border': '2px dashed #2196f3'
        }
      }
    }
  }
);

🎪 Demo Boxes Service

The DemoBoxesService provides ready-to-use preset layouts for quick prototyping:

Available Demo Methods

// Complex layouts
getDemoBigMiddleSpotBox()           // Complex layout with colorful styling
getBigMiddleSpotBox()               // Clean layout with minimal styling
getBigMiddleSpotBoxNoBorders()      // Borderless layout

// Simple grids
get5x5Box()                         // Simple 5x5 grid
getZBox()                           // 5x5 grid with Z-pattern styling
get2x6Box()                         // 2x6 grid for medium content
get4x12Box()                        // 4x12 grid for large content areas

// Specialized layouts
getGifBox()                         // Gift box layout with nested boxes

Using Demo Boxes

import { DemoBoxesService } from 'box-me';

@Component({
  // ... component configuration
})
export class DemoComponent {
  constructor(private demoBoxes: DemoBoxesService) {}

  // Get different demo layouts
  getComplexLayout() {
    return this.demoBoxes.getDemoBigMiddleSpotBox();
  }

  getCleanLayout() {
    return this.demoBoxes.getBigMiddleSpotBox();
  }

  getSimpleGrid() {
    return this.demoBoxes.get5x5Box();
  }

  getGiftBox() {
    return this.demoBoxes.getGifBox();
  }
}

🎨 Custom Styling

CSS Custom Properties

Box-Me uses CSS custom properties for easy theming. Override these in your global styles:

:root {
  /* Box container */
  --max-item-size: 120px;
  --box-border-radius: 16px;
  --box-padding: 8px;
  
  /* Grid spacing */
  --row-gap: 8px;
  --col-gap: 8px;
  --row-min-height: 80px;
  
  /* Colors */
  --box-background: #ffffff;
  --box-border-color: #dee2e6;
  --item-background: #f8f9fa;
  --item-border-color: #adb5bd;
  --selected-item-background: #e3f2fd;
  --selected-item-border-color: #2196f3;
  
  /* Transitions */
  --transition-duration: 0.2s;
  --transition-timing: ease-in-out;
}

Custom Component Styling

@Component({
  selector: 'app-custom-box',
  template: `
    <lib-box [box]="myBox" class="custom-box-theme">
      <ng-template #itemTemplate let-item let-rowIndex="rowIndex" let-colIndex="colIndex">
        <div class="custom-item" [class.selected]="item.isVisible">
          <div class="item-content">
            <img [src]="item.imageUrl" *ngIf="item.isVisible" [alt]="item.id">
            <div class="placeholder" *ngIf="!item.isVisible">
              <span class="coordinates">{{ rowIndex }},{{ colIndex }}</span>
            </div>
          </div>
        </div>
      </ng-template>
    </lib-box>
  `,
  styles: [`
    .custom-box-theme {
      --max-item-size: 100px;
      --box-border-radius: 12px;
      --box-padding: 12px;
      --row-gap: 6px;
      --col-gap: 6px;
    }

    .custom-item {
      width: 100%;
      height: 100%;
      border-radius: 8px;
      overflow: hidden;
      transition: all 0.3s ease;
      cursor: pointer;
    }

    .custom-item:hover {
      transform: scale(1.05);
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    }

    .custom-item.selected {
      border: 2px solid #007bff;
      box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25);
    }

    .item-content {
      width: 100%;
      height: 100%;
      display: flex;
      align-items: center;
      justify-content: center;
      background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
    }

    .placeholder {
      color: #6c757d;
      font-size: 14px;
      font-weight: 500;
    }

    .coordinates {
      background: rgba(0, 0, 0, 0.1);
      padding: 4px 8px;
      border-radius: 4px;
    }
  `]
})
export class CustomBoxComponent {
  // ... component logic
}

🔧 Advanced Usage Patterns

Dynamic Box Creation

@Component({
  selector: 'app-dynamic-box',
  template: `
    <div class="controls">
      <button (click)="addRow()">Add Row</button>
      <button (click)="addColumn()">Add Column</button>
      <button (click)="removeRow()">Remove Row</button>
      <button (click)="removeColumn()">Remove Column</button>
    </div>
    
    <lib-box [box]="dynamicBox" (itemsChange)="onBoxChange($event)">
      <ng-template #itemTemplate let-item let-rowIndex="rowIndex" let-colIndex="colIndex">
        <div class="dynamic-item">
          {{ rowIndex }},{{ colIndex }}
        </div>
      </ng-template>
    </lib-box>
  `
})
export class DynamicBoxComponent {
  dynamicBox: Box;
  currentRows = 3;
  currentCols = 3;

  constructor(private boxFactory: BoxFactoryService) {
    this.updateBox();
  }

  updateBox() {
    this.dynamicBox = this.boxFactory.createBox(
      'Dynamic Box',
      this.currentRows,
      this.currentCols,
      `Dynamic ${this.currentRows}x${this.currentCols} grid`
    );
  }

  addRow() {
    this.currentRows++;
    this.updateBox();
  }

  addColumn() {
    this.currentCols++;
    this.updateBox();
  }

  removeRow() {
    if (this.currentRows > 1) {
      this.currentRows--;
      this.updateBox();
    }
  }

  removeColumn() {
    if (this.currentCols > 1) {
      this.currentCols--;
      this.updateBox();
    }
  }

  onBoxChange(box: Box) {
    console.log('Dynamic box updated:', box);
  }
}

Event Handling

@Component({
  selector: 'app-event-handling',
  template: `
    <lib-box 
      [box]="myBox" 
      [selectedItem]="selectedItem"
      (itemsChange)="onItemsChange($event)"
      (itemClick)="onItemClick($event)"
      (itemHover)="onItemHover($event)"
      (itemDragStart)="onItemDragStart($event)"
      (itemDragEnd)="onItemDragEnd($event)">
      <ng-template #itemTemplate let-item let-rowIndex="rowIndex" let-colIndex="colIndex">
        <div class="interactive-item" 
             [class.selected]="item.isVisible"
             [class.hovered]="hoveredItem === item.id">
          <img [src]="item.imageUrl" *ngIf="item.isVisible" [alt]="item.id">
          <div class="placeholder" *ngIf="!item.isVisible">
            {{ rowIndex }},{{ colIndex }}
          </div>
        </div>
      </ng-template>
    </lib-box>
  `
})
export class EventHandlingComponent {
  myBox: Box;
  selectedItem: Item | null = null;
  hoveredItem: string | null = null;

  constructor(private boxFactory: BoxFactoryService) {
    this.myBox = this.boxFactory.createBox('Event Box', 3, 3, 'Box with event handling');
  }

  onItemsChange(box: Box) {
    console.log('Box items changed:', box);
    // Handle item changes, save to backend, etc.
  }

  onItemClick(event: any) {
    console.log('Item clicked:', event);
    this.selectedItem = event.item;
  }

  onItemHover(event: any) {
    console.log('Item hovered:', event);
    this.hoveredItem = event.item?.id || null;
  }

  onItemDragStart(event: any) {
    console.log('Item drag started:', event);
    // Handle drag start
  }

  onItemDragEnd(event: any) {
    console.log('Item drag ended:', event);
    // Handle drag end
  }
}

🧪 Testing

Unit Testing

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { BoxComponent } from 'box-me';
import { BoxFactoryService } from 'box-me';

describe('BoxComponent', () => {
  let component: BoxComponent;
  let fixture: ComponentFixture<BoxComponent>;
  let boxFactory: BoxFactoryService;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      imports: [BoxComponent],
      providers: [BoxFactoryService]
    }).compileComponents();

    fixture = TestBed.createComponent(BoxComponent);
    component = fixture.componentInstance;
    boxFactory = TestBed.inject(BoxFactoryService);
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  it('should render correct number of cells', () => {
    const testBox = boxFactory.createBox('Test Box', 2, 3, 'Test grid');
    component.box = testBox;
    fixture.detectChanges();

    const cells = fixture.nativeElement.querySelectorAll('.box-cell');
    expect(cells.length).toBe(6); // 2 rows * 3 columns
  });

  it('should emit itemsChange event when items are modified', () => {
    const testBox = boxFactory.createBox('Test Box', 2, 2, 'Test grid');
    component.box = testBox;
    
    spyOn(component.itemsChange, 'emit');
    
    // Simulate item change
    component.onItemClick({ rowIndex: 0, colIndex: 0 });
    
    expect(component.itemsChange.emit).toHaveBeenCalled();
  });
});

🚀 Performance Optimization

Lazy Loading

@Component({
  selector: 'app-lazy-box',
  template: `
    <div *ngIf="isLoaded; else loading">
      <lib-box [box]="lazyBox" (itemsChange)="onBoxChange($event)">
        <ng-template #itemTemplate let-item>
          <div class="lazy-item">{{ item.name || 'Empty' }}</div>
        </ng-template>
      </lib-box>
    </div>
    
    <ng-template #loading>
      <div class="loading">Loading box layout...</div>
    </ng-template>
  `
})
export class LazyBoxComponent implements OnInit {
  lazyBox: Box | null = null;
  isLoaded = false;

  constructor(private boxFactory: BoxFactoryService) {}

  ngOnInit() {
    // Simulate async loading
    setTimeout(() => {
      this.lazyBox = this.boxFactory.createBox(
        'Lazy Loaded Box',
        4,
        4,
        'Box loaded asynchronously'
      );
      this.isLoaded = true;
    }, 1000);
  }

  onBoxChange(box: Box) {
    console.log('Lazy box changed:', box);
  }
}

OnPush Change Detection

@Component({
  selector: 'app-optimized-box',
  template: `
    <lib-box [box]="optimizedBox" (itemsChange)="onBoxChange($event)">
      <ng-template #itemTemplate let-item>
        <div class="optimized-item">{{ item.name || 'Empty' }}</div>
      </ng-template>
    </lib-box>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class OptimizedBoxComponent {
  optimizedBox: Box;

  constructor(private boxFactory: BoxFactoryService) {
    this.optimizedBox = this.boxFactory.createBox(
      'Optimized Box',
      3,
      3,
      'Box with OnPush change detection'
    );
  }

  onBoxChange(box: Box) {
    // Handle changes efficiently
    console.log('Optimized box changed:', box);
  }
}

🔧 Troubleshooting

Common Issues

  1. Box not rendering: Ensure you've imported BoxComponent in your module/standalone component
  2. Styling not applied: Check that CSS custom properties are defined in your global styles
  3. Events not firing: Verify that event handlers are properly bound in the template
  4. Nested boxes not working: Ensure child boxes are properly placed in the nested array

Debug Mode

Enable debug mode to see positioning information:

const debugConfig: BoxConfig = {
  debug: true,
  general: {
    selectionMode: 'multiple'
  }
};

const debugBox = this.boxFactory.createBox(
  'Debug Box',
  2,
  2,
  'Box with debug information',
  debugConfig
);

🤝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

# Clone the repository
git clone https://github.com/your-org/box-me.git
cd box-me

# Install dependencies
npm install

# Build the library
ng build box-me

# Run the demo app
ng serve

# Run tests
ng test box-me

# Run linting
ng lint box-me

Code Style

  • Follow Angular style guide
  • Use TypeScript strict mode
  • Write comprehensive tests
  • Add JSDoc comments for public APIs
  • Keep components and services focused and single-purpose

📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

🙏 Acknowledgments

  • Built with Angular
  • Inspired by modern grid layout systems
  • Community feedback and contributions

📞 Support


Made with ❤️ by the Box-Me Team