# How DrawDB Implements Bulk Selection of Tables on the Canvas

> Learn how DrawDB implements bulk table selection using React context, drag-to-select rectangles, and Ctrl/Meta-click for efficient canvas management.

- Repository: [drawDB/drawdb](https://github.com/drawdb-io/drawdb)
- Tags: internals
- Published: 2026-08-14

---

**DrawDB uses a React context-based system with drag-to-select rectangles and Ctrl/Meta-click toggling to enable bulk selection of multiple tables, areas, and notes on the canvas.**

The **bulk selection** feature in drawdb-io/drawdb allows database designers to efficiently manipulate multiple diagram elements simultaneously. This article examines the exact implementation, from the state management in `SelectContext` to the canvas interaction logic in [`Canvas.jsx`](https://github.com/drawdb-io/drawdb/blob/main/Canvas.jsx).

## The SelectContext: Centralized Bulk Selection State

All bulk selection state lives in [`src/context/SelectContext.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/context/SelectContext.jsx). This provider exposes two critical pieces of state: the single `selectedElement` and the array `bulkSelectedElements`.

```jsx
// src/context/SelectContext.jsx
import { createContext, useState } from "react";

export const SelectContext = createContext(null);

export default function SelectContextProvider({ children }) {
  const [selectedElement, setSelectedElement] = useState({
    element: ObjectType.NONE,
    id: -1,
    openDialogue: false,
  });
  const [bulkSelectedElements, setBulkSelectedElements] = useState([]); // ⬅ bulk list

  return (
    <SelectContext.Provider
      value={{
        selectedElement,
        setSelectedElement,
        bulkSelectedElements,
        setBulkSelectedElements,
      }}
    >
      {children}
    </SelectContext.Provider>
  );
}

```

Child components access this state via the `useSelect()` hook. The [`Table.jsx`](https://github.com/drawdb-io/drawdb/blob/main/Table.jsx), [`Area.jsx`](https://github.com/drawdb-io/drawdb/blob/main/Area.jsx), and [`Note.jsx`](https://github.com/drawdb-io/drawdb/blob/main/Note.jsx) components all reference `bulkSelectedElements` to render highlighted borders when their `id` appears in the list.

## Drag-to-Select: Creating the Selection Rectangle

The `Canvas` component in [`src/components/EditorCanvas/Canvas.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/components/EditorCanvas/Canvas.jsx) implements the drag-to-select workflow through the `bulkSelectRect` state.

```jsx
// src/components/EditorCanvas/Canvas.jsx (lines 23-31)
const [bulkSelectRect, setBulkSelectRect] = useState({
  x1: 0, y1: 0, x2: 0, y2: 0,
  show: false,
  ctrlKey: false,
  metaKey: false,
});

```

When the user presses the mouse button on empty canvas space, the component initializes the rectangle with the starting coordinates and modifier key states. As the user drags, the rectangle updates in real-time to provide visual feedback.

## Collecting Elements Inside the Rectangle

On mouse-up, the `collectSelectedElements()` function (lines 40–79) determines which tables, areas, and notes intersect the drag rectangle.

```jsx
// src/components/EditorCanvas/Canvas.jsx
const collectSelectedElements = () => {
  const rect = getRectFromEndpoints(bulkSelectRect);
  const elements = [];

  // iterate tables, areas, notes …
  // add element if its bbox intersects `rect`

  if (bulkSelectRect.ctrlKey || bulkSelectRect.metaKey) {
    setBulkSelectedElements([...bulkSelectedElements, ...elements]); // additive
  } else {
    setBulkSelectedElements(elements); // replace
  }
};

```

The **hit-testing logic** uses helper functions from [`src/utils/rect.js`](https://github.com/drawdb-io/drawdb/blob/main/src/utils/rect.js) (`getRectFromEndpoints`, `isInsideRect`) to determine element intersection.

## Modifier Key Behavior: Additive vs. Replacement Selection

The implementation distinguishes between two modes based on modifier keys held during selection:

- **Ctrl or Meta held**: New elements are **added** to the existing `bulkSelectedElements` array
- **No modifier**: The new selection **replaces** the previous bulk selection

This logic appears immediately after element collection (lines 19–23 in [`Canvas.jsx`](https://github.com/drawdb-io/drawdb/blob/main/Canvas.jsx)) and ensures familiar desktop-style selection behavior.

## Ctrl/Meta-Click: Toggling Individual Elements

Users can also build bulk selections through individual clicks. The `handlePointerDownOnElement` function (lines 65–83) handles this interaction.

```jsx
// src/components/EditorCanvas/Canvas.jsx
const handlePointerDownOnElement = (e, { element, type }) => {
  const elementInBulk = {
    id: element.id,
    type,
    currentCoords: { x: element.x, y: element.y },
    initialCoords: { x: element.x, y: element.y },
  };

  const isSelected = bulkSelectedElements.some(el => isSameElement(el, elementInBulk));

  if (e.ctrlKey || e.metaKey) {
    if (isSelected) {
      setBulkSelectedElements(bulkSelectedElements.filter(el => !isSameElement(el, elementInBulk)));
    } else {
      setBulkSelectedElements([...bulkSelectedElements, elementInBulk]);
    }
    return;
  }

  // plain click → replace bulk list with single element
  setBulkSelectedElements([elementInBulk]);
};

```

Each element in the bulk list maintains both `currentCoords` and `initialCoords`—the latter enables revert-to-original functionality and proper delta calculations during moves.

## Moving Multiple Selected Elements Together

When dragging any element that belongs to `bulkSelectedElements`, the canvas applies identical deltas to all selected items simultaneously (lines 58–78).

```jsx
// src/components/EditorCanvas/Canvas.jsx
if (isDragging()) {
  const { x: mainX, y: mainY } = coordinatesAfterSnappingToGrid({
    x: pointer.spaces.diagram.x - dragging.grabOffset.x,
    y: pointer.spaces.diagram.y - dragging.grabOffset.y,
  });

  const { currentCoords } = bulkSelectedElements.find(el => isSameElement(el, dragging));
  const deltaX = mainX - currentCoords.x;
  const deltaY = mainY - currentCoords.y;

  const newBulk = bulkSelectedElements.map(el => {
    const newPos = { x: el.currentCoords.x + deltaX, y: el.currentCoords.y + deltaY };
    // persist the new position in the diagram store
    if (el.type === ObjectType.TABLE)  updateTable(el.id, newPos);
    if (el.type === ObjectType.AREA)   updateArea(el.id, newPos);
    if (el.type === ObjectType.NOTE)   updateNote(el.id, newPos);
    return { ...el, currentCoords: newPos };
  });

  setBulkSelectedElements(newBulk);
}

```

The **move operation** calculates delta from the primary dragged element, then maps that delta across all bulk-selected items. Each item type delegates to its respective store update function: `updateTable()`, `updateArea()`, or `updateNote()`.

## Key Files in the Bulk Selection System

| File | Purpose |
|------|---------|
| [`src/context/SelectContext.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/context/SelectContext.jsx) | React context providing `bulkSelectedElements` state |
| [`src/components/EditorCanvas/Canvas.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/components/EditorCanvas/Canvas.jsx) | Core canvas: rectangle selection, collection, bulk move, click handling |
| [`src/components/EditorCanvas/Table.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/components/EditorCanvas/Table.jsx) | Table component consuming bulk selection for visual feedback |
| [`src/components/EditorCanvas/Area.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/components/EditorCanvas/Area.jsx) | Area component with same bulk selection integration |
| [`src/components/EditorCanvas/Note.jsx`](https://github.com/drawdb-io/drawdb/blob/main/src/components/EditorCanvas/Note.jsx) | Note component with same bulk selection integration |
| [`src/utils/rect.js`](https://github.com/drawdb-io/drawdb/blob/main/src/utils/rect.js) | Geometry utilities for hit-testing during selection |

## Summary

- **State management**: `SelectContext` centralizes `bulkSelectedElements` array accessible throughout the component tree
- **Drag-to-select**: `bulkSelectRect` tracks selection rectangle; `collectSelectedElements()` performs intersection tests
- **Modifier behavior**: Ctrl/Meta enables additive selection; absence triggers replacement selection
- **Individual toggling**: `handlePointerDownOnElement` supports Ctrl/Meta-click to add or remove single items
- **Group movement**: Dragging any selected element calculates deltas applied uniformly to all `bulkSelectedElements`
- **Type-specific updates**: The move loop delegates to `updateTable()`, `updateArea()`, or `updateNote()` based on element type

## Frequently Asked Questions

### How does DrawDB detect which tables are inside the selection rectangle?

DrawDB uses helper functions from [`src/utils/rect.js`](https://github.com/drawdb-io/drawdb/blob/main/src/utils/rect.js)—specifically `getRectFromEndpoints` and `isInsideRect`—to test whether each table's bounding box intersects the `bulkSelectRect`. The `collectSelectedElements()` function in [`Canvas.jsx`](https://github.com/drawdb-io/drawdb/blob/main/Canvas.jsx) iterates through all tables, areas, and notes, adding those that pass the intersection test to the selection array.

### Can users add tables to an existing bulk selection without starting over?

Yes. Holding **Ctrl** (Linux/Windows) or **Meta** (macOS) during either drag-to-select or individual clicks triggers additive mode. The `bulkSelectRect.ctrlKey`/`metaKey` flags and the `e.ctrlKey`/`e.metaKey` properties in click handlers control this behavior, appending new elements rather than replacing the existing `bulkSelectedElements` array.

### What happens when dragging a table that is part of a bulk selection?

The canvas identifies the delta between the dragged element's current and new position, then applies that same delta to every item in `bulkSelectedElements`. The implementation updates `currentCoords` for each element and persists changes through type-specific store functions like `updateTable()`, keeping all selected items moving as a rigid group.