feat: measure key hold duration in getKeyboardResponse and record rt_key_duration in keyboard response plugins - #3707
Conversation
…_key_duration in keyboard response plugins Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
🦋 Changeset detectedLatest commit: f14e978 The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 packages
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 |
📦 Preview build readyBuilt from PR head Changed packages: Quick-start HTML: <script src="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@ebc99650b9337d231d3fdbd5eb6c58aa99c054e9/packages/jspsych/dist/index.browser.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@ebc99650b9337d231d3fdbd5eb6c58aa99c054e9/packages/jspsych/css/jspsych.css">
<script src="https://cdn.jsdelivr.net/gh/jspsych/jsPsych@ebc99650b9337d231d3fdbd5eb6c58aa99c054e9/packages/plugin-audio-keyboard-response/dist/index.browser.min.js"></script>All package URLs
Last updated 2026-07-25 15:31 UTC for PR head |
…hat defers the response to keyup Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…next trial Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With persist: true, a listener holds one pending release at a time; a later valid press supersedes an unreleased earlier one. Document this intended behavior in the API reference and a code comment, and add a test that locks it in. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix: handle case-sensitive key duration review when shift state changes before key is released
jadeddelta
left a comment
There was a problem hiding this comment.
it looks great, there's just one thing to address of debatable urgency: if one actuates a keydown, alt/cmd+tabs or has an OS notification that steals attention away from the browser, releases the key after focus is lost, and then comes back to the experiment, tapping the key will measure the duration from the key press before the loss of focus, rather than the most recent one. deferring this to you because it looks like changes to handle require managing blur events or other mechanisms if you find them (and because it might not be that important- if it isn't, re-request the review and i'll approve it)
(here's an html that describes this, put this in your /examples folder to demonstrate after running npm run build: https://gist.github.com/jadeddelta/2906d6e99c4e4894463d2935693a66ff)
…ending release When the window loses focus, the browser stops delivering keyup, leaving a held key looking permanently pressed and orphaning any wait_for_key_release callback (later inflating rt_key_duration by the blur gap). Treat blur as releasing all keys: resolve pending releases with rt_key_duration: null and clear held-key state so it cannot leak into the next trial. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
good catch! I decided to have the release fire on blur, but pass |
jadeddelta
left a comment
There was a problem hiding this comment.
just the changeset and we should be good!
| } | ||
|
|
||
| export async function windowBlur() { | ||
| window.dispatchEvent(new Event("blur")); |
There was a problem hiding this comment.
don't forget to add a changeset for this now
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Summary
Adds the ability to measure how long a response key is held down, recorded as a new
rt_key_durationdata field in the five*-keyboard-responseplugins (html, image, audio, video, canvas).Core API (
jspsych)jsPsych.pluginAPI.getKeyboardResponse()gains an optional boolean optionwait_for_key_release(defaultfalse). When enabled,callback_functiondoes not fire at keydown — it fires when the response key is released, with an extended payload:rtis still measured at the keydown (same clock rules as before, includingrt_method: "audio"); only the firing of the callback moves to keyup.rt_key_duration = keyup − initial keydown, always measured on theperformance.now()clock. Key-repeat events do not reset the press timestamp.cancelKeyboardResponse/cancelAllKeyboardResponses, so a deferred callback can never fire after a trial has ended.wait_for_key_release: false(the default) behavior and the{ key, rt }payload are completely unchanged.Plugins
Each of the five keyboard-response plugins gains a
wait_for_key_releasetrial parameter (defaultfalse) and art_key_durationdata field:response_ends_trial: truethe trial ends at the release, andrt_key_durationis part of the trial data atfinishTrial(soon_finishalways sees the final value — no post-hoc data updates anywhere in this design).trial_duration) while the key is still held, no response is recorded. This is documented in the parameter description.rt_key_durationisnull.wait_for_key_releaseis enabled (null otherwise); visual simulation works naturally since simulated key presses release immediately.Docs & release
docs/reference/jspsych-pluginAPI.md: documentswait_for_key_releaseand the extended payload.docs/plugins/*.md: parameter row andrt_key_durationdata row for each of the five plugins.jspsychand the five plugins.Test plan
Tests were written first (TDD) and confirmed failing before implementation.
pluginapi.test.ts): callback does not fire at keydown and fires at keyup with{ key, rt, rt_key_duration };rtreflects the keydown time; default payload has nort_key_durationproperty; cancellation between keydown and keyup suppresses the callback (bothcancelKeyboardResponseandcancelAllKeyboardResponses); invalid keys fire nothing; key-repeat does not reset the press timestamp;persist: truefires once per press-release cycle.rtandrt_key_duration; press+release during a fixed-duration trial; timeout while the key is still held records a null response; a key held pasttrial_durationdoes not contaminate the following trial (no response from the leftover keyup or from key-repeat events, and a fresh press-release cycle in the next trial records correct values); default trials unchanged.turbo run buildpasses for all six packages.🤖 Generated with Claude Code