Conversation
Hover over the column edge and drag to resize column - add composable to do size calculations - use request animation frame to deduplicate drag events and avoid direct repaints ( performance ) - pass the container to allow calculating drag indicator line for table with dynamic content and horizontal scroll - move column sort down to label to avoid confusion with drag
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR adds draggable column resizing to the EMX2 table component, allowing users to drag column borders to adjust widths. It introduces a new useColumnResize composable that manages resize state and a visual guide line, and integrates it into the table component.
Changes:
- New
useColumnResizecomposable that handles mouse-based column resizing with requestAnimationFrame throttling - Updated
TableEMX2.vueto use pixel-based column widths with drag handles and a visual resize guide - Moved sort click handler in
TableHeaderAction.vueto avoid conflict with the new resize handle
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
apps/tailwind-components/app/composables/useColumnResize.ts |
New composable managing column resize state, mouse events, and guide position |
apps/tailwind-components/app/components/table/TableEMX2.vue |
Integrates column resize: adds drag handles, guide overlay, and pixel-based widths |
apps/tailwind-components/app/components/table/TableHeaderAction.vue |
Moves sort click from outer div to inner span to avoid interfering with resize |
apps/tailwind-components/tests/vitest/composables/useColumnResize.spec.ts |
Unit tests for the new composable |
Comments suppressed due to low confidence (1)
apps/tailwind-components/app/components/table/TableEMX2.vue:101
- The inline
:stylewidth now conflicts with the CSS classesw-60andw-60 lg:w-fullstill applied via:class. Sincetable-fixedis used and column widths are now managed bycolumnWidths, these Tailwind width classes should be removed to avoid the style conflict (inline styles and CSS classes both setting width).
:style="{ width: columnWidths[column.id] + 'px' }"
class="text-table-row group-hover:bg-hover"
:class="{
'w-60 lg:w-full': columns.length <= 5,
'w-60': columns.length > 5,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| function startResize(event: MouseEvent, columnId: string) { | ||
| isResizing.value = true; | ||
| resizingColumn.value = columnId; | ||
|
|
||
| startX.value = event.clientX; | ||
| startWidth.value = columnWidths.value[columnId] ?? 240; | ||
|
|
||
| guideX.value = getRelativeX(event.clientX); | ||
|
|
||
| document.body.style.cursor = "col-resize"; | ||
|
|
||
| window.addEventListener("mousemove", handleMouseMove); | ||
| window.addEventListener("mouseup", stopResize); | ||
| } |
There was a problem hiding this comment.
pendingX is initialized to 0 and is only updated on mousemove. If the user does a mousedown then mouseup without moving the mouse, pendingX is 0, causing diff = 0 - startX which produces a large negative value. While the MIN_WIDTH guard prevents the width from going negative, the column width silently stays unchanged with no feedback, and pendingX retains stale state across resize operations. pendingX should be initialized to event.clientX in startResize to ensure correct behavior when no mousemove occurs.
| if (newWidth > MIN_WIDTH) { | ||
| columnWidths.value[resizingColumn.value] = newWidth; | ||
| } |
There was a problem hiding this comment.
When newWidth <= MIN_WIDTH, the column width is left unchanged at its previous value rather than being clamped to MIN_WIDTH. This means dragging far to the left has no effect instead of shrinking the column to its minimum. Consider clamping: columnWidths.value[resizingColumn.value] = Math.max(newWidth, MIN_WIDTH).
| if (newWidth > MIN_WIDTH) { | |
| columnWidths.value[resizingColumn.value] = newWidth; | |
| } | |
| columnWidths.value[resizingColumn.value] = Math.max(newWidth, MIN_WIDTH); |
| <span | ||
| @click="emit('sort-requested', column.id)" | ||
| class="hover:cursor-pointer" | ||
| >{{ column.label }}</span | ||
| > |
There was a problem hiding this comment.
The sort click handler was moved from the wrapping div to a span inside the button, but the button itself still has @click.prevent which only prevents default — it doesn't trigger sorting. This means clicking the button area outside the span text (e.g., padding) no longer triggers sorting, reducing the clickable area. The @click for sort should be on the button element instead, replacing the @click.prevent.
|
|
||
| function getRelativeX(clientX: number) { | ||
| const rect = container.value?.getBoundingClientRect(); | ||
| return rect ? clientX - rect.left : clientX; |
There was a problem hiding this comment.
The guideX position is calculated relative to the container's left edge but doesn't account for scrollLeft. If the table is scrolled horizontally, the guide line will appear at the wrong position. getRelativeX should add container.value.scrollLeft to the result.
| return rect ? clientX - rect.left : clientX; | |
| const scrollLeft = container.value?.scrollLeft ?? 0; | |
| return rect ? clientX - rect.left + scrollLeft : clientX; |
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
What are the main changes you did
How to test
Checklist