jspsych 8.2.3 → 8.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -1
- package/css/jspsych.css +20 -0
- package/dist/index.browser.js +57 -4
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.min.js +6 -6
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +56 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +19 -1
- package/dist/index.js +56 -3
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/index.scss +21 -0
- package/src/modules/plugin-api/KeyboardListenerAPI.ts +90 -1
package/README.md
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|

|
|
2
2
|
|
|
3
|
+
[](https://www.bestpractices.dev/projects/9910)
|
|
4
|
+
|
|
3
5
|
jsPsych is a JavaScript framework for creating behavioral experiments that run in a web browser.
|
|
4
6
|
|
|
5
7
|
Experiments in jsPsych are created using [plugins](https://www.jspsych.org/latest/overview/plugins).
|
|
@@ -65,4 +67,10 @@ The project is currently managed by the core team of Josh de Leeuw ([@jodeleeuw]
|
|
|
65
67
|
|
|
66
68
|
jsPsych was created by [Josh de Leeuw](https://www.vassar.edu/faculty/jdeleeuw).
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
## Thanks
|
|
71
|
+
|
|
72
|
+
We're grateful for the generous support from a [Mozilla Open Source Support award](https://www.mozilla.org/en-US/moss/), which funded development of the library from 2020-2022, and from a [National Science Foundation Grant](https://www.nsf.gov/awardsearch/show-award?AWD_ID=2346214), which funded work on the broader ecosystem from 2024-2026.
|
|
73
|
+
|
|
74
|
+
This project is tested with [BrowserStack](https://www.browserstack.com).
|
|
75
|
+
|
|
76
|
+
|
package/css/jspsych.css
CHANGED
|
@@ -408,6 +408,26 @@
|
|
|
408
408
|
cursor: not-allowed;
|
|
409
409
|
}
|
|
410
410
|
|
|
411
|
+
/* Keyboard keys, e.g., press <kbd>space</kbd> in instructions */
|
|
412
|
+
.jspsych-display-element kbd,
|
|
413
|
+
.jspsych-display-element .jspsych-key {
|
|
414
|
+
display: inline-block;
|
|
415
|
+
min-width: 1em;
|
|
416
|
+
padding: 0.15em 0.5em;
|
|
417
|
+
margin: 0 0.1em 2px;
|
|
418
|
+
font-family: "Open Sans", "Arial", sans-serif;
|
|
419
|
+
font-size: 0.8em;
|
|
420
|
+
line-height: 1.4;
|
|
421
|
+
text-align: center;
|
|
422
|
+
white-space: nowrap;
|
|
423
|
+
vertical-align: middle;
|
|
424
|
+
color: #333;
|
|
425
|
+
background-color: #f7f7f7;
|
|
426
|
+
border: 1px solid #ccc;
|
|
427
|
+
border-radius: 4px;
|
|
428
|
+
box-shadow: 0 2px 0 #ccc;
|
|
429
|
+
}
|
|
430
|
+
|
|
411
431
|
/* custom style for input[type="range] (slider) to improve alignment between positions and labels */
|
|
412
432
|
.jspsych-slider {
|
|
413
433
|
appearance: none;
|
package/dist/index.browser.js
CHANGED
|
@@ -51,7 +51,7 @@ var jsPsychModule = (function (exports) {
|
|
|
51
51
|
|
|
52
52
|
var autoBind$1 = /*@__PURE__*/getDefaultExportFromCjs(autoBind);
|
|
53
53
|
|
|
54
|
-
var version = "8.
|
|
54
|
+
var version = "8.3.0";
|
|
55
55
|
|
|
56
56
|
class ExtensionManager {
|
|
57
57
|
constructor(dependencies, extensionsConfiguration) {
|
|
@@ -604,6 +604,8 @@ var jsPsychModule = (function (exports) {
|
|
|
604
604
|
this.minimumValidRt = minimumValidRt;
|
|
605
605
|
this.listeners = /* @__PURE__ */ new Set();
|
|
606
606
|
this.heldKeys = /* @__PURE__ */ new Set();
|
|
607
|
+
this.keyDownTimestamps = /* @__PURE__ */ new Map();
|
|
608
|
+
this.pendingReleases = /* @__PURE__ */ new Map();
|
|
607
609
|
this.areRootListenersRegistered = false;
|
|
608
610
|
autoBind$1(this);
|
|
609
611
|
this.registerRootListeners();
|
|
@@ -618,11 +620,16 @@ var jsPsychModule = (function (exports) {
|
|
|
618
620
|
if (rootElement) {
|
|
619
621
|
rootElement.addEventListener("keydown", this.rootKeydownListener);
|
|
620
622
|
rootElement.addEventListener("keyup", this.rootKeyupListener);
|
|
623
|
+
window.addEventListener("blur", this.rootBlurListener);
|
|
621
624
|
this.areRootListenersRegistered = true;
|
|
622
625
|
}
|
|
623
626
|
}
|
|
624
627
|
}
|
|
625
628
|
rootKeydownListener(e) {
|
|
629
|
+
const physicalKey = this.getPhysicalKey(e);
|
|
630
|
+
if (!this.keyDownTimestamps.has(physicalKey)) {
|
|
631
|
+
this.keyDownTimestamps.set(physicalKey, performance.now());
|
|
632
|
+
}
|
|
626
633
|
for (const listener of [...this.listeners]) {
|
|
627
634
|
listener(e);
|
|
628
635
|
}
|
|
@@ -631,9 +638,43 @@ var jsPsychModule = (function (exports) {
|
|
|
631
638
|
toLowerCaseIfInsensitive(string) {
|
|
632
639
|
return this.areResponsesCaseSensitive ? string : string.toLowerCase();
|
|
633
640
|
}
|
|
641
|
+
/**
|
|
642
|
+
* identifies physical key of a keyboard event, for matching a keydown event with
|
|
643
|
+
* its corresponding keyup event, in the case of a change of shift state while
|
|
644
|
+
* the key is being held.
|
|
645
|
+
*/
|
|
646
|
+
getPhysicalKey(e) {
|
|
647
|
+
return e.code || this.toLowerCaseIfInsensitive(e.key);
|
|
648
|
+
}
|
|
634
649
|
rootKeyupListener(e) {
|
|
650
|
+
const physicalKey = this.getPhysicalKey(e);
|
|
651
|
+
for (const [listener, pending] of this.pendingReleases) {
|
|
652
|
+
if (pending.code === physicalKey) {
|
|
653
|
+
const pressTimestamp = this.keyDownTimestamps.get(physicalKey);
|
|
654
|
+
const rt_key_duration = pressTimestamp === void 0 ? null : Math.round(performance.now() - pressTimestamp);
|
|
655
|
+
this.pendingReleases.delete(listener);
|
|
656
|
+
pending.callback_function({ key: pending.key, rt: pending.rt, rt_key_duration });
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
this.keyDownTimestamps.delete(physicalKey);
|
|
635
660
|
this.heldKeys.delete(this.toLowerCaseIfInsensitive(e.key));
|
|
636
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* When the window loses focus (`blur`), the browser will not deliver the `keyup` events for keys
|
|
664
|
+
* that are currently held, which would otherwise leave stale entries in `heldKeys` and
|
|
665
|
+
* `keyDownTimestamps` (making a key look permanently held) and orphan any pending releases. Treat
|
|
666
|
+
* the blur as a release of every held key: resolve each pending release with
|
|
667
|
+
* `rt_key_duration: null`, since the true hold duration cannot be measured once the release is
|
|
668
|
+
* never observed, then clear the tracked key state.
|
|
669
|
+
*/
|
|
670
|
+
rootBlurListener() {
|
|
671
|
+
for (const [listener, pending] of this.pendingReleases) {
|
|
672
|
+
this.pendingReleases.delete(listener);
|
|
673
|
+
pending.callback_function({ key: pending.key, rt: pending.rt, rt_key_duration: null });
|
|
674
|
+
}
|
|
675
|
+
this.heldKeys.clear();
|
|
676
|
+
this.keyDownTimestamps.clear();
|
|
677
|
+
}
|
|
637
678
|
isResponseValid(validResponses, allowHeldKey, key) {
|
|
638
679
|
if (!allowHeldKey && this.heldKeys.has(key)) {
|
|
639
680
|
return false;
|
|
@@ -654,7 +695,8 @@ var jsPsychModule = (function (exports) {
|
|
|
654
695
|
audio_context,
|
|
655
696
|
audio_context_start_time,
|
|
656
697
|
allow_held_key = false,
|
|
657
|
-
minimum_valid_rt = this.minimumValidRt
|
|
698
|
+
minimum_valid_rt = this.minimumValidRt,
|
|
699
|
+
wait_for_key_release = false
|
|
658
700
|
}) {
|
|
659
701
|
if (rt_method !== "performance" && rt_method !== "audio") {
|
|
660
702
|
console.log(
|
|
@@ -681,7 +723,16 @@ var jsPsychModule = (function (exports) {
|
|
|
681
723
|
if (!persist) {
|
|
682
724
|
this.cancelKeyboardResponse(listener);
|
|
683
725
|
}
|
|
684
|
-
|
|
726
|
+
if (wait_for_key_release) {
|
|
727
|
+
this.pendingReleases.set(listener, {
|
|
728
|
+
code: this.getPhysicalKey(e),
|
|
729
|
+
key: e.key,
|
|
730
|
+
rt,
|
|
731
|
+
callback_function
|
|
732
|
+
});
|
|
733
|
+
} else {
|
|
734
|
+
callback_function({ key: e.key, rt });
|
|
735
|
+
}
|
|
685
736
|
}
|
|
686
737
|
};
|
|
687
738
|
this.listeners.add(listener);
|
|
@@ -689,9 +740,11 @@ var jsPsychModule = (function (exports) {
|
|
|
689
740
|
}
|
|
690
741
|
cancelKeyboardResponse(listener) {
|
|
691
742
|
this.listeners.delete(listener);
|
|
743
|
+
this.pendingReleases.delete(listener);
|
|
692
744
|
}
|
|
693
745
|
cancelAllKeyboardResponses() {
|
|
694
746
|
this.listeners.clear();
|
|
747
|
+
this.pendingReleases.clear();
|
|
695
748
|
}
|
|
696
749
|
compareKeys(key1, key2) {
|
|
697
750
|
if (typeof key1 !== "string" && key1 !== null || typeof key2 !== "string" && key2 !== null) {
|
|
@@ -4143,4 +4196,4 @@ var jsPsychModule = (function (exports) {
|
|
|
4143
4196
|
|
|
4144
4197
|
})({});
|
|
4145
4198
|
var initJsPsych = jsPsychModule.initJsPsych;
|
|
4146
|
-
//# sourceMappingURL=https://unpkg.com/jspsych@8.
|
|
4199
|
+
//# sourceMappingURL=https://unpkg.com/jspsych@8.3.0/dist/index.browser.js.map
|