How the Selection System for Diagram Elements Works in DrawDB
DrawDB implements a centralized selection system for diagram elements using a React context provider that maintains single-element focus and multi-selection arrays, exposing them through a lightweight useSelect hook consumed across canvas, sidebar, and toolbar components.
The selection system for diagram elements in drawdb-io/drawdb eliminates prop-drilling by storing all selection state in a top-level React context wrapped around the entire editor in src/pages/Editor.jsx. By maintaining both individual element focus and bulk selection arrays in a single source of truth, the application ensures that canvas components, sidebar panels, and toolbar controls synchronize automatically when users interact with tables, relationships, notes, or areas.
Core Architecture: SelectContext and State Management
The architecture centers on SelectContext, defined in src/context/SelectContext.jsx, which exports the SelectContextProvider component. This provider wraps the application and maintains two distinct pieces of state that drive the entire UI.
Single Element Selection State
The selectedElement object tracks the currently active element using enums defined in src/data/constants.js. When populated, it contains:
{
element: ObjectType.TABLE, // type from constants.js (TABLE, AREA, NOTE, etc.)
id: 42, // unique identifier from the data model
open: true, // controls pop-over/sidesheet visibility
openFromToolbar: false, // flag for toolbar-triggered selections
currentTab: Tab.TABLES, // active sidebar tab enumeration
// additional UI-specific flags
}
Multi-Selection State
For bulk operations, the provider maintains bulkSelectedElements, an array of element IDs. This array powers features like multi-delete or batch moves when users hold Shift or Ctrl while clicking canvas elements. Operations iterate over this array before the component clears it via setBulkSelectedElements([]).
The useSelect Hook
Located in src/hooks/useSelect.js, this hook is a minimal wrapper around useContext(SelectContext). Any component needing selection access calls:
const { selectedElement, setSelectedElement, bulkSelectedElements, setBulkSelectedElements } = useSelect();
This pattern ensures that Table.jsx, ControlPanel.jsx, and TablesTab.jsx all read from the same state without intermediate prop passing, regardless of their depth in the component tree.
How Components Consume Selection State
Canvas Components
SVG-based canvas elements in src/components/EditorCanvas/Table.jsx, Relationship.jsx, Note.jsx, and Area.jsx check selectedElement to render selection outlines and handle click events. When a user clicks a table, the component calls setSelectedElement() with the table's ID and type, triggering a synchronized re-render across all consumers.
Sidebar Panels
Components like src/components/EditorSidePanel/TablesTab.jsx, RelationshipsTab.jsx, and AreasTab.jsx use the selection state to highlight the currently chosen item in lists and to drive property editing interfaces. The sidebar reacts to currentTab values in the selection object to display the appropriate editing interface for the active element type.
Toolbar Controls
src/components/EditorHeader/ControlPanel.jsx and related header components enable or disable action buttons based on selection validity. For example, "Delete Table" buttons check whether selectedElement contains a valid table ID and whether bulkSelectedElements contains items before allowing execution.
Selection Interaction Flow
The system follows a unidirectional data flow:
-
User Interaction: A click handler in
Table.jsxor similar callssetSelectedElement()with an object containingelement,id, and UI flags likeopenandcurrentTab. -
Context Update: The
SelectContextProviderupdates its internal state, causing React to re-render all subscribing components. -
UI Response: Canvas components display selection borders, side panels load element-specific data, and toolbars enable context-specific actions based on the new state.
-
Bulk Operations: When users multi-select, individual click handlers add or remove IDs from
bulkSelectedElements. Operations like move or delete iterate over this array before clearing it.
Practical Implementation Examples
Selecting a Single Table
Inside src/components/EditorCanvas/Table.jsx, selection updates work as follows:
import useSelect from "../../hooks/useSelect";
import { ObjectType, Tab } from "../../data/constants";
function Table({ tableData }) {
const { selectedElement, setSelectedElement } = useSelect();
const onClick = () => {
setSelectedElement({
element: ObjectType.TABLE,
id: tableData.id,
open: true,
currentTab: Tab.TABLES,
openFromToolbar: false,
});
};
return (
<g onClick={onClick} /* …other SVG props… */>
{/* rendering logic */}
</g>
);
}
Performing Bulk Deletes
Toolbar components in header files handle multi-selection operations using the bulk array:
import useSelect from "../../hooks/useSelect";
import { Action } from "../../data/constants";
function DeleteSelectedButton() {
const { bulkSelectedElements, setBulkSelectedElements } = useSelect();
const handleDelete = () => {
// dispatch a delete action for each id in bulkSelectedElements
bulkSelectedElements.forEach(id => deleteElementById(id));
setBulkSelectedElements([]);
};
return (
<button disabled={bulkSelectedElements.length === 0} onClick={handleDelete}>
Delete ({bulkSelectedElements.length})
</button>
);
}
Programmatic Selection from Toolbar
Components can trigger selection to open side panels programmatically:
import useSelect from "../../hooks/useSelect";
import { ObjectType, Tab } from "../../data/constants";
function OpenNoteFromToolbar(noteId) {
const { setSelectedElement } = useSelect();
const open = () => {
setSelectedElement({
element: ObjectType.NOTE,
id: noteId,
open: true,
currentTab: Tab.NOTES,
openFromToolbar: true,
});
};
return <button onClick={open}>Edit Note</button>;
}
Summary
SelectContextinsrc/context/SelectContext.jsxprovides the single source of truth for diagram element selection state in DrawDB.- State splits into
selectedElement(single focus) andbulkSelectedElements(multi-select array) to support both individual editing and batch operations. - The
useSelecthook insrc/hooks/useSelect.jseliminates prop-drilling by exposing selection functions to any component in the tree. - Canvas components like
Table.jsxupdate selection on user clicks, while sidebar panels and toolbar controls react to state changes to highlight items and enable actions. - The system relies on
ObjectTypeandTabenums fromsrc/data/constants.jsto maintain type safety across the selection payload.
Frequently Asked Questions
How does DrawDB distinguish between single and multiple element selection?
DrawDB maintains two separate state variables in SelectContext: selectedElement for the individually focused item that drives side panels, and bulkSelectedElements for the array of IDs used in batch operations. Single clicks update the object, while Shift or Ctrl clicks modify the array.
What determines the shape of the selection state object?
The selection payload structure follows the ObjectType and Tab enums defined in src/data/constants.js. Every selection object must include an element type, unique id, and UI flags like open and currentTab to coordinate focus between the canvas and sidebar components.
Why does the selection system use a React context instead of local state?
By wrapping the editor in SelectContextProvider at src/pages/Editor.jsx, DrawDB avoids prop-drilling through SVG canvas hierarchies and sidebar trees. This ensures deeply nested Table.jsx components and distant toolbar buttons access identical selection data without intermediate props.
How do canvas components visualize the currently selected element?
Components like Table.jsx import useSelect and compare their own ID against selectedElement.id. When matched, they render selection outlines, while the open flag controls pop-over visibility for inline editing directly on the diagram canvas.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →