Skip to content

fix: clear text selection on card drag start (#515)#557

Merged
Chris0Jeky merged 4 commits intomainfrom
fix/515-drag-text-selection
Mar 29, 2026
Merged

fix: clear text selection on card drag start (#515)#557
Chris0Jeky merged 4 commits intomainfrom
fix/515-drag-text-selection

Conversation

@Chris0Jeky
Copy link
Copy Markdown
Owner

Summary

Root cause

When a user selects text inside a card, the browser prioritises a text-drag over the card's HTML drag. Because the drag handle never cleared the selection, the card became effectively non-draggable until the user manually deselected the text.

Fix

  1. @mousedown on the drag handle calls window.getSelection()?.removeAllRanges() immediately, before the browser decides whether to start a text-drag or an element-drag.
  2. CSS user-select: none on the handle element prevents the handle icon itself from being selected.

Test plan

  • Select text on a card, then grab the drag handle — card moves correctly
  • Text can still be selected normally inside card title/description when not dragging
  • npm run typecheck passes (verified: clean exit)

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses issue #515 by clearing active text selections when the card drag handle is clicked, ensuring the browser initiates a card drag instead of a text-drag. It introduces a handleDragHandleMouseDown function and applies user-select: none to the drag handle. The review feedback suggests adding an automated test to verify the selection clearing behavior and recommends including additional vendor prefixes for the user-select CSS property to improve cross-browser compatibility.

Comment on lines +44 to +50
function handleDragHandleMouseDown() {
// Clear any active text selection so the browser initiates a card drag
// instead of treating the interaction as a text-drag. Without this, a prior
// text selection inside the card prevents the drag handle from working until
// the selection is cleared by clicking elsewhere. Fixes #515.
window.getSelection()?.removeAllRanges()
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To prevent future regressions, it would be beneficial to add an automated test case that covers this scenario. You can simulate text selection within the test and then verify that the selection is cleared when mousedown is triggered on the drag handle.

Here's an example of how you could write such a test using vitest and @vue/test-utils:

it('clears text selection on drag handle mousedown', async () => {
  const wrapper = mount(CardItem, {
    props: { card: createCard() },
  });

  const selection = window.getSelection();
  if (!selection) {
    // JSDOM supports getSelection, but good practice to check.
    return;
  }

  // 1. Simulate text selection
  const title = wrapper.get('.td-board-card__title');
  const range = document.createRange();
  range.selectNodeContents(title.element);
  selection.removeAllRanges();
  selection.addRange(range);
  expect(selection.toString()).toBe(wrapper.props().card.title);

  // 2. Trigger mousedown on the drag handle
  await wrapper.get('[data-action=\"drag-card-handle\"]').trigger('mousedown');

  // 3. Assert that the selection was cleared
  expect(selection.toString()).toBe('');
});

This would ensure the fix remains effective as the codebase evolves.

Comment on lines +210 to +211
user-select: none;
-webkit-user-select: none;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For improved cross-browser compatibility, it's best practice to include other vendor prefixes for user-select and list the standard property last. This ensures that browsers use the standard implementation if available, falling back to the prefixed version otherwise.

  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;

#515)

Covers handleDragHandleMouseDown — verifies removeAllRanges is called on
mousedown and that null getSelection does not throw.
@Chris0Jeky
Copy link
Copy Markdown
Owner Author

Adversarial review findings

Reviewed against the full checklist. Six items are clean; one genuine gap was found and fixed.

Items that pass

  1. Touch/mobile — The mousedown handler is desktop-only (HTML5 drag API does not fire on touch). However, the underlying drag mechanism (draggable="true" / @dragstart) has never worked on touch, so this is a pre-existing limitation, not a regression introduced by this PR.

  2. Drag library conflicts — No SortableJS, vue-draggable, or other drag library is present in package.json. Plain HTML5 drag API only. No conflict possible.

  3. user-select: none scope — CSS is scoped (<style scoped>) and the rule is on .td-card-drag-handle only. The card title and description are siblings of the handle inside .td-board-card, not descendants of the handle, so they are unaffected. Users can still select text in title/description normally.

  4. mousedown vs dragstart placementmousedown is the correct hook. It fires before the browser decides whether to start a text-drag or an element-drag. Adding the removeAllRanges call to dragstart instead would be too late.

  5. Browser compatibilityuser-select (unprefixed) covers all modern browsers. -webkit-user-select is retained for Safari/older Chrome. ms-user-select would only be needed for IE11, which is not a target here.

  6. CSS class name — Template applies td-card-drag-handle (line 86); CSS targets .td-card-drag-handle (line 209). They match exactly.

Genuine issue found and fixed

  1. Missing test coveragehandleDragHandleMouseDown shipped with no unit test. The existing CardItem.spec.ts had tests for drag guardrails but none covering the new mousedownremoveAllRanges behavior.

Fix applied in commit dad99cc0: added two vitest cases to CardItem.spec.ts in a new CardItem drag handle — text selection clearing describe block:

  • verifies removeAllRanges is called once when the drag handle receives mousedown
  • verifies no throw when getSelection() returns null (the optional-chain path)

All 5 tests (3 pre-existing + 2 new) pass.

- Add -moz-user-select and -ms-user-select vendor prefixes to drag handle CSS, standard property listed last
- Replace mocked removeAllRanges test with real DOM createRange/selection test per Gemini suggestion
@Chris0Jeky Chris0Jeky merged commit 47e198c into main Mar 29, 2026
18 checks passed
@Chris0Jeky Chris0Jeky deleted the fix/515-drag-text-selection branch March 29, 2026 18:18
@github-project-automation github-project-automation bot moved this from Pending to Done in Taskdeck Execution Mar 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

BUG: Text-selected cards become non-draggable until selection is cleared

1 participant