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/dist/index.d.ts
CHANGED
|
@@ -588,6 +588,7 @@ interface GetKeyboardResponseOptions {
|
|
|
588
588
|
audio_context_start_time?: number;
|
|
589
589
|
allow_held_key?: boolean;
|
|
590
590
|
minimum_valid_rt?: number;
|
|
591
|
+
wait_for_key_release?: boolean;
|
|
591
592
|
}
|
|
592
593
|
declare class KeyboardListenerAPI {
|
|
593
594
|
private getRootElement;
|
|
@@ -596,6 +597,8 @@ declare class KeyboardListenerAPI {
|
|
|
596
597
|
constructor(getRootElement: () => Element | undefined, areResponsesCaseSensitive?: boolean, minimumValidRt?: number);
|
|
597
598
|
private listeners;
|
|
598
599
|
private heldKeys;
|
|
600
|
+
private keyDownTimestamps;
|
|
601
|
+
private pendingReleases;
|
|
599
602
|
private areRootListenersRegistered;
|
|
600
603
|
/**
|
|
601
604
|
* If not previously done and `this.getRootElement()` returns an element, adds the root key
|
|
@@ -604,9 +607,24 @@ declare class KeyboardListenerAPI {
|
|
|
604
607
|
private registerRootListeners;
|
|
605
608
|
private rootKeydownListener;
|
|
606
609
|
private toLowerCaseIfInsensitive;
|
|
610
|
+
/**
|
|
611
|
+
* identifies physical key of a keyboard event, for matching a keydown event with
|
|
612
|
+
* its corresponding keyup event, in the case of a change of shift state while
|
|
613
|
+
* the key is being held.
|
|
614
|
+
*/
|
|
615
|
+
private getPhysicalKey;
|
|
607
616
|
private rootKeyupListener;
|
|
617
|
+
/**
|
|
618
|
+
* When the window loses focus (`blur`), the browser will not deliver the `keyup` events for keys
|
|
619
|
+
* that are currently held, which would otherwise leave stale entries in `heldKeys` and
|
|
620
|
+
* `keyDownTimestamps` (making a key look permanently held) and orphan any pending releases. Treat
|
|
621
|
+
* the blur as a release of every held key: resolve each pending release with
|
|
622
|
+
* `rt_key_duration: null`, since the true hold duration cannot be measured once the release is
|
|
623
|
+
* never observed, then clear the tracked key state.
|
|
624
|
+
*/
|
|
625
|
+
private rootBlurListener;
|
|
608
626
|
private isResponseValid;
|
|
609
|
-
getKeyboardResponse({ callback_function, valid_responses, rt_method, persist, audio_context, audio_context_start_time, allow_held_key, minimum_valid_rt, }: GetKeyboardResponseOptions): KeyboardListener;
|
|
627
|
+
getKeyboardResponse({ callback_function, valid_responses, rt_method, persist, audio_context, audio_context_start_time, allow_held_key, minimum_valid_rt, wait_for_key_release, }: GetKeyboardResponseOptions): KeyboardListener;
|
|
610
628
|
cancelKeyboardResponse(listener: KeyboardListener): void;
|
|
611
629
|
cancelAllKeyboardResponses(): void;
|
|
612
630
|
compareKeys(key1: string | null, key2: string | null): boolean;
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import autoBind from 'auto-bind';
|
|
|
2
2
|
import rw from 'random-words';
|
|
3
3
|
import seedrandom from 'seedrandom/lib/alea.js';
|
|
4
4
|
|
|
5
|
-
var version = "8.
|
|
5
|
+
var version = "8.3.0";
|
|
6
6
|
|
|
7
7
|
class ExtensionManager {
|
|
8
8
|
constructor(dependencies, extensionsConfiguration) {
|
|
@@ -555,6 +555,8 @@ class KeyboardListenerAPI {
|
|
|
555
555
|
this.minimumValidRt = minimumValidRt;
|
|
556
556
|
this.listeners = /* @__PURE__ */ new Set();
|
|
557
557
|
this.heldKeys = /* @__PURE__ */ new Set();
|
|
558
|
+
this.keyDownTimestamps = /* @__PURE__ */ new Map();
|
|
559
|
+
this.pendingReleases = /* @__PURE__ */ new Map();
|
|
558
560
|
this.areRootListenersRegistered = false;
|
|
559
561
|
autoBind(this);
|
|
560
562
|
this.registerRootListeners();
|
|
@@ -569,11 +571,16 @@ class KeyboardListenerAPI {
|
|
|
569
571
|
if (rootElement) {
|
|
570
572
|
rootElement.addEventListener("keydown", this.rootKeydownListener);
|
|
571
573
|
rootElement.addEventListener("keyup", this.rootKeyupListener);
|
|
574
|
+
window.addEventListener("blur", this.rootBlurListener);
|
|
572
575
|
this.areRootListenersRegistered = true;
|
|
573
576
|
}
|
|
574
577
|
}
|
|
575
578
|
}
|
|
576
579
|
rootKeydownListener(e) {
|
|
580
|
+
const physicalKey = this.getPhysicalKey(e);
|
|
581
|
+
if (!this.keyDownTimestamps.has(physicalKey)) {
|
|
582
|
+
this.keyDownTimestamps.set(physicalKey, performance.now());
|
|
583
|
+
}
|
|
577
584
|
for (const listener of [...this.listeners]) {
|
|
578
585
|
listener(e);
|
|
579
586
|
}
|
|
@@ -582,9 +589,43 @@ class KeyboardListenerAPI {
|
|
|
582
589
|
toLowerCaseIfInsensitive(string) {
|
|
583
590
|
return this.areResponsesCaseSensitive ? string : string.toLowerCase();
|
|
584
591
|
}
|
|
592
|
+
/**
|
|
593
|
+
* identifies physical key of a keyboard event, for matching a keydown event with
|
|
594
|
+
* its corresponding keyup event, in the case of a change of shift state while
|
|
595
|
+
* the key is being held.
|
|
596
|
+
*/
|
|
597
|
+
getPhysicalKey(e) {
|
|
598
|
+
return e.code || this.toLowerCaseIfInsensitive(e.key);
|
|
599
|
+
}
|
|
585
600
|
rootKeyupListener(e) {
|
|
601
|
+
const physicalKey = this.getPhysicalKey(e);
|
|
602
|
+
for (const [listener, pending] of this.pendingReleases) {
|
|
603
|
+
if (pending.code === physicalKey) {
|
|
604
|
+
const pressTimestamp = this.keyDownTimestamps.get(physicalKey);
|
|
605
|
+
const rt_key_duration = pressTimestamp === void 0 ? null : Math.round(performance.now() - pressTimestamp);
|
|
606
|
+
this.pendingReleases.delete(listener);
|
|
607
|
+
pending.callback_function({ key: pending.key, rt: pending.rt, rt_key_duration });
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
this.keyDownTimestamps.delete(physicalKey);
|
|
586
611
|
this.heldKeys.delete(this.toLowerCaseIfInsensitive(e.key));
|
|
587
612
|
}
|
|
613
|
+
/**
|
|
614
|
+
* When the window loses focus (`blur`), the browser will not deliver the `keyup` events for keys
|
|
615
|
+
* that are currently held, which would otherwise leave stale entries in `heldKeys` and
|
|
616
|
+
* `keyDownTimestamps` (making a key look permanently held) and orphan any pending releases. Treat
|
|
617
|
+
* the blur as a release of every held key: resolve each pending release with
|
|
618
|
+
* `rt_key_duration: null`, since the true hold duration cannot be measured once the release is
|
|
619
|
+
* never observed, then clear the tracked key state.
|
|
620
|
+
*/
|
|
621
|
+
rootBlurListener() {
|
|
622
|
+
for (const [listener, pending] of this.pendingReleases) {
|
|
623
|
+
this.pendingReleases.delete(listener);
|
|
624
|
+
pending.callback_function({ key: pending.key, rt: pending.rt, rt_key_duration: null });
|
|
625
|
+
}
|
|
626
|
+
this.heldKeys.clear();
|
|
627
|
+
this.keyDownTimestamps.clear();
|
|
628
|
+
}
|
|
588
629
|
isResponseValid(validResponses, allowHeldKey, key) {
|
|
589
630
|
if (!allowHeldKey && this.heldKeys.has(key)) {
|
|
590
631
|
return false;
|
|
@@ -605,7 +646,8 @@ class KeyboardListenerAPI {
|
|
|
605
646
|
audio_context,
|
|
606
647
|
audio_context_start_time,
|
|
607
648
|
allow_held_key = false,
|
|
608
|
-
minimum_valid_rt = this.minimumValidRt
|
|
649
|
+
minimum_valid_rt = this.minimumValidRt,
|
|
650
|
+
wait_for_key_release = false
|
|
609
651
|
}) {
|
|
610
652
|
if (rt_method !== "performance" && rt_method !== "audio") {
|
|
611
653
|
console.log(
|
|
@@ -632,7 +674,16 @@ class KeyboardListenerAPI {
|
|
|
632
674
|
if (!persist) {
|
|
633
675
|
this.cancelKeyboardResponse(listener);
|
|
634
676
|
}
|
|
635
|
-
|
|
677
|
+
if (wait_for_key_release) {
|
|
678
|
+
this.pendingReleases.set(listener, {
|
|
679
|
+
code: this.getPhysicalKey(e),
|
|
680
|
+
key: e.key,
|
|
681
|
+
rt,
|
|
682
|
+
callback_function
|
|
683
|
+
});
|
|
684
|
+
} else {
|
|
685
|
+
callback_function({ key: e.key, rt });
|
|
686
|
+
}
|
|
636
687
|
}
|
|
637
688
|
};
|
|
638
689
|
this.listeners.add(listener);
|
|
@@ -640,9 +691,11 @@ class KeyboardListenerAPI {
|
|
|
640
691
|
}
|
|
641
692
|
cancelKeyboardResponse(listener) {
|
|
642
693
|
this.listeners.delete(listener);
|
|
694
|
+
this.pendingReleases.delete(listener);
|
|
643
695
|
}
|
|
644
696
|
cancelAllKeyboardResponses() {
|
|
645
697
|
this.listeners.clear();
|
|
698
|
+
this.pendingReleases.clear();
|
|
646
699
|
}
|
|
647
700
|
compareKeys(key1, key2) {
|
|
648
701
|
if (typeof key1 !== "string" && key1 !== null || typeof key2 !== "string" && key2 !== null) {
|