Engineering Journal
Schema Editor
Schema Editor

The second kind of group: when a new container class breaks selection

2026-07-17

TLDR

Hardcoding DOM selection walks to look for a single container class (.domain-symbol) breaks when new container types are introduced. When our team added user-created selection groups (<g id="group_...">), single clicks on grouped elements selected individual child paths instead of the parent group container. Resolving this required creating a unified resolveSelectionTarget() helper that traverses ancestor trees up to the outermost valid container.
Selection Walk StrategyHandling for Palette Symbols (.domain-symbol)Handling for User Groups (<g id="group_...">)Click Result
Hardcoded Inlined SelectorSelected group containerIgnored (Selected individual child)Broken for user groups
Unified Target ResolverSelected outermost containerSelected outermost container100% Consistent Target Selection

Problem statement: the inlined selection walk defect

Our editor's palette instantiated multi-element symbols inside container groups with class .domain-symbol.

Selection logic across mouse handlers used an inlined closest() DOM walk to ensure clicking any sub-element selected the parent symbol:

// INLINED IMPLEMENTATION: Hardcoded to palette symbols
let selectionTarget = clickedElement.closest('.domain-symbol');
if (selectionTarget) clickedElement = selectionTarget;

Later, we implemented a user grouping feature (Ctrl+G) that wrapped selected canvas elements in standard SVG groups: <g id="group_1719823490">.

Because the selection walk checked strictly for .domain-symbol, clicking a user-grouped element ignored the group container and selected the individual clicked child element.


Technical failure mode: fragmented container definitions

The defect escaped initial testing because Ctrl+G worked as expected while elements remained selected.

Only after deselecting the elements and attempting to re-select the group via single click did the broken selection walk manifest:

  1. clickedElement.closest('.domain-symbol') evaluated to null.
  2. The selection engine treated the click as targeting the raw child path.
  3. The group container was bypassed completely.

The fix & architecture: unified target resolution engine

We extracted inlined closest() calls into a central resolveSelectionTarget() helper that supports all container types and walks up to the outermost group:

// REFACTORED: Unified Selection Target Resolver
export function resolveSelectionTarget(clickedElement, cameraGroup) {
  if (!clickedElement) return null;

// 1. Find nearest valid container match let container = clickedElement.closest('.domain-symbol, g[id^="group_"]'); if (!container) return clickedElement;

// 2. Walk up parent hierarchy to select outermost group wrapper let current = container; let parent = current.parentElement;

while (parent && parent !== cameraGroup && parent.tagName === 'g') { if (parent.classList.contains('domain-symbol') || parent.id?.startsWith('group_')) { current = parent; } parent = parent.parentElement; }

return current; }

Double-Click child piercing

To allow users to select individual elements inside a group (following standard Figma/Draw.io conventions), we reserved double-click for container piercing:
// Single-click selects group unit; Double-click pierces to child element
canvas.addEventListener('dblclick', (e) => {
  const rawChild = e.target;
  selectElement(rawChild, { pierceGroup: true });
});
Rule of thumb: Never inline container DOM selector strings across click handlers. Route all element selection queries through a centralized target resolver.
Read this post in the full Engineering Journal →