LinkDesk/frontend/docs/shift-click-selection-fix.md

4.3 KiB

Shift+Click Selection Visual Feedback Fix

Date: 2025-11-26
Issue: When using Shift+click for range selection and select-all checkbox, selected rows were not showing the visual highlight (background color change)

Problem

The TasksDataTable component's selection state was not properly synchronized with TanStack Table's internal state, causing row.getIsSelected() to return incorrect values and preventing the visual feedback (bg-muted/50 class) from being applied.

Root Cause

The issue was with how TanStack Table's state management works. The table needs to read selection state from a reactive source. The configuration uses a getter:

state: {
  get rowSelection() {
    return rowSelection.value
  }
}

This getter ensures that whenever rowSelection.value changes, the table's internal state updates and row.getIsSelected() returns the correct value. The key is that we must update rowSelection.value directly, not try to use non-existent table methods.

Solution

The correct approach is to update rowSelection.value directly. The table configuration's state.rowSelection getter ensures the table reads from our reactive ref:

1. Shift+Click Range Selection

// CORRECT - Update ref directly
const newSelection: Record<string, boolean> = {}
for (let i = start; i <= end; i++) {
  const id = String(props.tasks[i].id)
  newSelection[id] = true
}
rowSelection.value = newSelection

2. Ctrl+Click Toggle

// CORRECT - Update ref directly
const newSelection = { ...rowSelection.value }
if (newSelection[taskId]) {
  delete newSelection[taskId]
} else {
  newSelection[taskId] = true
}
rowSelection.value = newSelection

3. Single Click

// CORRECT - Update ref directly
rowSelection.value = { [taskId]: true }

4. Context Menu

// CORRECT - Update ref directly
if (!rowSelection.value[taskId]) {
  const newSelection = { ...rowSelection.value }
  newSelection[taskId] = true
  rowSelection.value = newSelection
}

Changes Made

File: frontend/src/components/task/TasksDataTable.vue

  1. handleRowClick function:

    • All selection updates now directly modify rowSelection.value
    • Shift+click: Creates new selection object and assigns to rowSelection.value
    • Ctrl+click: Creates new selection object with toggle logic and assigns to rowSelection.value
    • Single click: Assigns new object with single selection to rowSelection.value
  2. handleContextMenu function:

    • Checks rowSelection.value[taskId] directly instead of using table methods
    • Updates rowSelection.value directly when adding unselected task

How It Works

The table configuration includes a reactive getter:

state: {
  get rowSelection() {
    return rowSelection.value
  }
}

This means:

  1. When we update rowSelection.value, Vue's reactivity triggers
  2. The table's state getter reads the new value
  3. The table's internal state updates
  4. row.getIsSelected() returns the correct value
  5. The template re-renders with correct CSS classes

Benefits

  1. Proper State Synchronization: Table state automatically syncs with our ref through the getter
  2. Visual Feedback Works: The row.getIsSelected() method correctly reflects selection state
  3. Simple and Direct: No need for complex table methods, just update the ref
  4. Vue Reactivity: Leverages Vue's reactivity system properly

Testing

To verify the fix:

  1. Navigate to a project's Tasks view
  2. Click on Task 1
  3. Hold Shift and click on Task 5
  4. Expected: All tasks from 1-5 should show the selected background color (bg-muted/50)
  5. Expected: Selection count should show "5 tasks selected"

Additional Test Cases

  • Backward range: Click Task 5, Shift+click Task 1 → Tasks 1-5 selected with visual feedback
  • Ctrl+click after range: Select range, then Ctrl+click to toggle individual tasks → Visual feedback updates correctly
  • Context menu: Right-click unselected task → Task gets added to selection with visual feedback
  • Requirement 3.3: Shift+click range selection
  • Requirement 6.1: Selected rows have distinct background color
  • Requirement 6.2: Hover state is distinct from selection

Status

Fixed - Shift+click range selection now properly updates visual feedback