132 lines
4.3 KiB
Markdown
132 lines
4.3 KiB
Markdown
# 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:
|
|
|
|
```typescript
|
|
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
|
|
```typescript
|
|
// 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
|
|
```typescript
|
|
// CORRECT - Update ref directly
|
|
const newSelection = { ...rowSelection.value }
|
|
if (newSelection[taskId]) {
|
|
delete newSelection[taskId]
|
|
} else {
|
|
newSelection[taskId] = true
|
|
}
|
|
rowSelection.value = newSelection
|
|
```
|
|
|
|
### 3. Single Click
|
|
```typescript
|
|
// CORRECT - Update ref directly
|
|
rowSelection.value = { [taskId]: true }
|
|
```
|
|
|
|
### 4. Context Menu
|
|
```typescript
|
|
// 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:
|
|
|
|
```typescript
|
|
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
|
|
|
|
## Related Requirements
|
|
|
|
- **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
|