Selection
This document describes the selection behavior for pointer interactions and keyboard modifiers.
Global Rules
- Selection is normalized: parent and child nodes cannot be selected simultaneously.
Overlay Concept
The selection overlay is the event receiver for selection interactions.
Definition:
- Overlay is a UX box rendered on the surface, not direct canvas content
- Overlay acts as an event target for pointer interactions
- Overlay is present even when the selected item is culled (invisible)
Behavior:
- Dragging within the selected overlay region preserves selection
- Clicking on nodes within the overlay allows selection changes (deferred to
click) - Clicking empty space within the overlay clears selection (deferred to
click) - This ensures consistent behavior regardless of item visibility
Event Model
Note on terminology: The term click in this document refers to a virtual concept: pointerdown + pointerup without drag (no cancellation intent). This is distinct from the DOM/JavaScript click event, which may fire even after drags if the down/up targets match. Our system uses a virtual click that only occurs when no drag has occurred.
Primary events:
pointerdown- Some selection changes occur immediately at this event (for new/empty targets not under current selection)pointerup- Used to distinguish click from dragclick- Completion of pointerdown + pointerup without drag (virtual, not DOM click event). Deferred selection changes are applied at this event.dragstart- Indicates user intent to drag (cancels deferred selection changes)drag- Continuous drag gesturedragend- Completion of drag gesture
Modifiers:
Shiftkey - Modifies selection behavior (additive/toggle mode)
Selection Timing
Selection changes occur at different times based on whether the target is within the current selection:
Rule of thumb:
- Select immediately (on
pointerdown): if target is new or empty, and when not under current selection - Select on
click(onpointerupwithout drag): if target is within current selection
Rationale: When the target is within the current selection, we don't yet know if the user's intent is to toggle/change selection or to drag. If we handle selection changes (especially Shift+toggle) immediately on pointerdown, users will never be able to drag the current selection with Shift key pressed (for axis lock), forcing them to first drag, then press Shift key (annoying UX). By deferring selection changes until click, we can distinguish between drag intent and selection change intent.
Immediate (on pointerdown)
Selection changes immediately when:
- Target is an unselected node (without Shift) → selects that node
- Target is an unselected node (with Shift) → adds to selection
- Target is empty space outside selection overlay (without Shift) → clears selection
Rationale: User intent is unambiguous - they want to select, add, or clear. Target is not within current selection.
Deferred (on click, cancelled on dragstart)
Selection changes are deferred when the target is within the current selection (applies to both single and multi-selection):
- Target is a selected node (without Shift) → defer reset to just that node
- Target is a selected node (with Shift) → defer toggle (deselect)
- Target is a child of selected node(s) → defer reset to that child
- Target is empty space within selection overlay (without Shift) → defer clear selection
Rationale: User intent is ambiguous - they may want to drag the selection or change selection. Deferring allows distinguishing between drag and click. This applies equally to single and multi-selection - if we toggle immediately on pointerdown when Shift is pressed on a selected node, users cannot start a drag with Shift already pressed for axis lock.
Behavior:
- If
dragstartoccurs → deferred operation is cancelled, selection preserved - If
clickoccurs (no drag) → deferred operation is applied, selection changes
Test Cases
Empty Space Click
Scenario: User clicks on empty space (no target node) without Shift key.
Expected behavior:
- Selection is cleared immediately on
pointerdown - Does not wait for
pointeruporclick
Rationale: User intent is clear - they want to deselect everything.
Single Selection - Child Hover During Drag
Scenario:
- One container node is selected (contains a rectangle child, total 2 nodes)
- User hovers over the inner rectangle
- User drags
Expected behavior:
- Selection does not change during drag
- Container remains selected
Rationale: User's intent is to drag the selected container, not change selection.
Single Selection - Child Click
Scenario:
- One container node is selected (contains a rectangle child)
- User clicks (no drag) on the inner rectangle
Expected behavior:
- Selection changes to the inner rectangle on
click(pointerup without drag) - Does not change on
pointerdown
Rationale: To select inner content, selection occurs on click, not pointerdown, to avoid confusing drag intent.
Multi-Selection - Child Hover During Drag
Scenario:
- Two root containers are selected (each contains a rectangle, total 4 nodes)
- User hovers over inner rectangle of one container
- User drags
Expected behavior:
- Selection does not change during drag
- Both containers remain selected
Rationale: User's intent is to drag the selection group, not change selection.
Multi-Selection - Node Click Within Selection
Scenario:
- Two nodes (A and B) are selected
- User clicks (no drag) on node A
Expected behavior:
- Selection changes to just node A on
click(pointerup without drag) - Does not change on
pointerdown
Rationale: User can change selection by clicking on a node within the selection group. Selection change is deferred to click to distinguish from drag intent.
Single Selection - Node Toggle (Shift+Click)
Scenario:
- One node (A) is selected
- User presses Shift and clicks (no drag) on node A
Expected behavior:
- Node A is toggled out of selection (deselected) on
click(pointerup without drag) - Selection becomes empty
- Does not change on
pointerdown
Rationale: If toggle happened immediately on pointerdown, users could not start a drag with Shift already pressed for axis lock. Deferring to click allows distinguishing between drag intent and toggle intent.
Multi-Selection - Node Toggle Within Selection
Scenario:
- Two nodes (A and B) are selected
- User presses Shift and clicks (no drag) on node A
Expected behavior:
- Node A is toggled out of selection on
click(pointerup without drag) - Selection becomes just node B
- Does not change on
pointerdown
Rationale: If toggle happened immediately on pointerdown, users could not start a drag with Shift already pressed for axis lock. Deferring to click allows distinguishing between drag intent and toggle intent. This applies equally to single and multi-selection.
Multi-Selection - Empty Space Drag Within Overlay
Scenario:
- Two nodes (A and B) are selected, forming a selection group overlay
- User presses pointer down on empty space within the selection overlay bounds (no node behind the pointer)
- User drags (with or without Shift key)
Expected behavior:
- Selection does not change during drag
- Both nodes remain selected
- Selection group is dragged as a unit
- Applies whether Shift key is pressed or not
Rationale: User's intent is to drag the selection group, not clear selection or start marquee. Empty space within the overlay bounds is part of the selection region. Shift key should enable axis lock for dragging, not start marquee selection.
Multi-Selection - Empty Space Click Within Overlay
Scenario:
- Two nodes (A and B) are selected, forming a selection group overlay
- User clicks (no drag) on empty space within the selection overlay bounds (no node behind the pointer)
Expected behavior:
- Selection is cleared on
click(pointerup without drag) - Both nodes are deselected
Rationale: A complete click (pointerdown + pointerup without drag) on empty space is a clear intent to clear selection, even within the overlay bounds.
Selection Change Rules
On pointerdown
Immediate selection changes:
- Unselected node (no Shift) → select that node
- Unselected node (with Shift) → add to selection
- Empty space outside selection overlay (no Shift) → clear selection
Deferred selection changes:
- Selected node (no Shift) → defer reset to that node
- Selected node (with Shift) → defer toggle (deselect)
- Child of selected node(s) → defer reset to that child
- Empty space within selection overlay (no Shift) → defer clear selection
On click (pointerup without drag)
Applied operations:
- All deferred selection operations are applied
On dragstart
Cancelled operations:
- All deferred selection operations are cancelled
- Selection remains unchanged