LinkDesk/frontend/docs/shot-delete-event-bubbling-...

125 lines
4.0 KiB
Markdown

# Shot Delete Event Bubbling Fix
## Issue Description
When clicking the "Delete Shot" button in the ShotsDataTable, the shot detail panel was being triggered along with the delete action. This was happening because the delete button click event was bubbling up to the table row click handler, which opens the shot detail panel.
## Root Cause
The issue was caused by event bubbling in the table row structure:
1. The table row has a `@click="handleRowClick"` event handler that opens the shot detail panel
2. The delete button (inside a dropdown menu) is within the table row
3. Even though the dropdown menu items had `e.stopPropagation()`, the event was still bubbling up to the row click handler
## Solution
### 1. Enhanced Event Handling in ShotsDataTable
Modified the `handleRowClick` function in `ShotsDataTable.vue` to check if the click target is within an interactive element:
```typescript
const handleRowClick = (shot: Shot, event: MouseEvent) => {
// Check if the click target is within a dropdown menu, button, or other interactive element
const target = event.target as HTMLElement
if (target) {
// Check if the click is on a button, dropdown, or any interactive element
const isInteractiveElement = target.closest('button, [role="menuitem"], [data-radix-collection-item]')
if (isInteractiveElement) {
// Don't emit row-click if clicking on interactive elements
return
}
}
emit('row-click', shot, event)
}
```
This approach:
- Checks if the click target is within a button, dropdown menu item, or other interactive element
- Uses `closest()` to traverse up the DOM tree to find interactive elements
- Prevents the row-click event from being emitted when clicking on interactive elements
### 2. Improved Event Prevention in Columns
Enhanced the event handling in `columns.ts` for all dropdown menu items:
```typescript
// Before
onClick: (e: Event) => { e.stopPropagation(); meta.onDelete(shot) }
// After
onClick: (e: Event) => {
e.stopPropagation()
e.preventDefault()
meta.onDelete(shot)
}
```
Added `e.preventDefault()` in addition to `e.stopPropagation()` for more robust event handling.
### 3. Enhanced Dropdown Trigger
Improved the dropdown trigger button event handling:
```typescript
h(
DropdownMenuTrigger,
{
asChild: true,
onClick: (e: Event) => {
e.stopPropagation()
e.preventDefault()
}
},
{
default: () =>
h(
Button,
{
variant: 'ghost',
size: 'sm',
class: 'h-8 w-8 p-0',
onClick: (e: Event) => {
e.stopPropagation()
e.preventDefault()
}
},
// ...
),
}
)
```
## Testing
Created `test-shot-delete-event-fix.html` to verify the fix works correctly:
1. Clicking the "Delete Shot" button should NOT trigger the row click event
2. Clicking anywhere else on the row SHOULD trigger the row click event
3. The event log shows which events are triggered and which are prevented
## Files Modified
1. `frontend/src/components/shot/ShotsDataTable.vue`
- Enhanced `handleRowClick` function to detect interactive elements
2. `frontend/src/components/shot/columns.ts`
- Added `e.preventDefault()` to all dropdown menu item click handlers
- Enhanced dropdown trigger event handling
## Benefits
- Prevents unwanted shot detail panel opening when deleting shots
- Maintains proper row click functionality for non-interactive areas
- Provides a robust solution that works with various UI components
- Uses DOM traversal to detect interactive elements, making it future-proof
## Prevention
To prevent similar issues in the future:
1. Always use both `e.stopPropagation()` and `e.preventDefault()` for interactive elements within clickable containers
2. Consider using DOM traversal (`closest()`) to detect interactive elements in container click handlers
3. Test interactive elements within clickable containers to ensure proper event handling
4. Use specific selectors for interactive elements (buttons, menu items, etc.)