JSFiddle - React, Tailwind, and code Playground

TypeScript box selector. Select a group of boxes

by Glen Cheney

HTML

<ul id="boxes">
  <li class="box"></li>
  <li class="box"></li>
  <li class="box"></li>
  <li class="box"></li>
  <li class="box"></li>
  <li class="box"></li>
  <li class="box"></li>
  <li class="box"></li>
  <li class="box"></li>
  <li class="box"></li>
  <li class="box"></li>
  <li class="box"></li>
  <li class="box"></li>
  <li class="box"></li>
</ul>
<div class="selection"></div>

CSS

body {
  font-family: sans-serif;
  box-sizing: border-box;
  padding-bottom: 400px;
}

*,
*::before,
*::after {
  box-sizing: inherit;
}

#boxes {
  display: grid;
  grid-gap: 6px;
  padding: 0;
  margin: 0;
  list-style: none;
  grid-template-columns: repeat(auto-fill, 100px);
}

.box {
  width: 100px;
  height: 100px;
  background-color: white;
  border: 3px solid navy;
}

.box--selected {
  background-color: skyblue;
}

.selection {
  position: absolute;
  top: 0;
  left: 0;
  display: none;
  width: 0;
  height: 0;
  background-color: rgba(0, 0, 0, 0.5);
  border: 2px solid navy;
}

TypeScript

// https://imgur.com/a/eLeU2
// Save positions of all boxes. Update on resize.
// Listen for mousedown on body. Save starting position.
// On mouse move, test every box to see if it overlaps with the selection.
// On mouse up, hide the selection.

class Rect {
	constructor(x: number, y: number, w: number, h: number) {
  	this.left = x;
    this.top = y;
    this.width = w;
    this.height = h;
  }
  
  get right(): number {
  	return this.left + this.width;
  }
  
  get bottom(): number {
  	return this.top + this.height;
  }
  
  static intersects(a: Rect, b: Rect): boolean {
  	return (
    	a.left <= b.right &&
      b.left <= a.right &&
      a.top <= b.bottom &&
      b.top <= a.bottom
    );
  }
}

class Box {
	constructor(public element: HTMLElement, public rect: Rect) {}
}

class Point {
	constructor(public x: number, public y: number) {}
}

class Selector {
	private selection: Rect;
  private start: Point;

	constructor() {
  	this.boxes = this.getBoxes();
    this._selectionElement = document.querySelector('.selection');
    this._handleResize = this._handleResize.bind(this);
    this._handleMouseDown = this._handleMouseDown.bind(this);
    this._handleMouseMove = this._handleMouseMove.bind(this);
    this._handleMouseUp = this._handleMouseUp.bind(this);
    window.addEventListener('resize', this._handleResize);
    document.body.addEventListener('mousedown', this._handleMouseDown);
  }
  
  private getBoxes(): Box[] {
		return Array.from(
    	document.querySelectorAll('.box'),
			this.getSingleBox,
    );
  }
  
  private getSingleBox(element: HTMLElement): Box {
  	const { top, left, width, height } = element.getBoundingClientRect();
    const rect = new Rect(left, top, width, height);
    return new Box(element, rect);
  }
  
  private deselectAll(): void {
  	this.boxes.forEach((box: Box) => {
    	box.element.classList.remove('box--selected');
    });
  }
  
  private drawSelection(): void {
  	// Set the size of the overlay.
   ...