Skip to content

fix(useDoubleClick): handle keyboard and programmatic clicks - #579

Merged
mnxmnz merged 5 commits into
toss:mainfrom
KooYS:fix/use-double-click-keyboard
Oct 28, 2026
Merged

mnxmnz merged 5 commits into
toss:mainfrom
KooYS:fix/use-double-click-keyboard

Conversation

@KooYS

@KooYS KooYS commented Oct 27, 2026 •

Copy link
Copy Markdown
Contributor

Overview

When the handler returned by useDoubleClick is attached to a native button, activating it with Enter or Space does not invoke the provided click callback. The browser dispatches a click event, but the hook ignores it because its click count is zero. This PR preserves that single-click activation; it does not map keyboard input to doubleClick.

Reproduction:

import { useDoubleClick } from 'react-simplikit';

function Repro() {
  const handleEvent = useDoubleClick({
    click: () => console.log('click'),
    doubleClick: () => console.log('doubleClick'),
  });

  return <button type="button" onClick={handleEvent}>Test button</button>;
}
  1. Render <Repro /> and press Tab to focus the button.
  2. Press Enter (or Space).
  3. Expected: click is logged once, like a plain <button onClick>. Actual: nothing is logged.

Cause: keyboard activation (and element.click()) dispatches a click with event.detail === 0, while the hook only handles detail 1 and 2. Such a click ran neither callback, and because clearClickTimeout() ran first it also cancelled a pending single click from the mouse.

Fix: detail === 0 now runs click immediately and returns. These activations do not participate in the pointer click-count sequence, so this implementation invokes click without waiting for a second pointer click. The return sits before clearClickTimeout() so the click stays independent of the mouse: a pending mouse click still fires after its delay (when the two overlap, the callbacks can run in reverse order). doubleClick is still only called for detail === 2, and the mouse path is unchanged.

Docs: the JSDoc description and the delay param now say delay applies to pointer single clicks and that clicks without a click count run click immediately; the generated and localized docs and the plugin skill reference follow. Patch changeset included.

Verification

Real Chrome 156 (headless, Playwright mouse/keyboard input), React 19 production build, delay 250ms, 350ms wait; main and this branch measured in the same session. Calls of click / doubleClick:

Input main this PR
Mouse single click 1 / 0 1 / 0
Mouse double click 0 / 1 0 / 1
Tab, then Enter 0 / 0 1 / 0
Tab, then Space 0 / 0 1 / 0
button.click() 0 / 0 1 / 0
Tab, then Enter three times 0 / 0 3 / 0
Mouse click, then Enter inside the delay 0 / 0 2 / 0

Browser verification covers Chrome only; Safari, Firefox, and assistive technologies were not tested.

Tests: 8 new detail === 0 tests (7 fail on main) and an SSR test; coverage stays at 100%.

Self-review: vi.restoreAllMocks() is added to the shared afterEach in useDoubleClick.spec.tsx because the existing unmount test's window.clearTimeout spy was never restored and leaked into the appended tests (window.clearTimeout is not a function).

Checklist

  • Did you write the test code?
  • Have you run yarn run fix to format and lint the code and docs? (ran the check-only yarn test:lint / yarn test:format, both clean)
  • Have you run yarn run test:coverage to make sure there is no uncovered line?
  • Did you write the JSDoc?

@changeset-bot

changeset-bot Bot commented Oct 27, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 6839941

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
react-simplikit Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@KooYS
KooYS marked this pull request as ready for review October 27, 2026 07:34

@mnxmnz mnxmnz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the fix and the clear before/after table! 👍

I checked it in Chromium too, and the new branch only catches keyboard and programmatic clicks, as intended 🙌

@codecov-commenter

codecov-commenter commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (4b9a4aa) to head (6839941).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff            @@
##              main      #579   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           70        70           
  Lines         2307      2311    +4     
  Branches       747       751    +4     
=========================================
+ Hits          2307      2311    +4     
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@mnxmnz
mnxmnz merged commit 8828710 into toss:main Oct 28, 2026
31 of 32 checks passed
@github-actions github-actions Bot mentioned this pull request Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants