jspsych 7.3.1 → 7.3.3
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 -3
- package/dist/index.browser.js +85 -33
- package/dist/index.browser.js.map +1 -1
- package/dist/index.browser.min.js +1 -1
- package/dist/index.browser.min.js.map +1 -1
- package/dist/index.cjs +85 -33
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +85 -33
- package/dist/index.js.map +1 -1
- package/dist/modules/plugin-api/SimulationAPI.d.ts +3 -0
- package/dist/modules/plugin-api/TimeoutAPI.d.ts +13 -1
- package/dist/modules/utils.d.ts +6 -0
- package/package.json +2 -2
- package/src/JsPsych.ts +31 -21
- package/src/modules/plugin-api/SimulationAPI.ts +9 -4
- package/src/modules/plugin-api/TimeoutAPI.ts +15 -3
- package/src/modules/plugin-api/index.ts +14 -11
- package/src/modules/utils.ts +31 -0
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export declare class SimulationAPI {
|
|
2
|
+
private getDisplayContainerElement;
|
|
3
|
+
private setJsPsychTimeout;
|
|
4
|
+
constructor(getDisplayContainerElement: () => HTMLElement, setJsPsychTimeout: (callback: () => void, delay: number) => number);
|
|
2
5
|
dispatchEvent(event: Event): void;
|
|
3
6
|
/**
|
|
4
7
|
* Dispatches a `keydown` event for the specified key
|
|
@@ -1,5 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A class that provides a wrapper around the global setTimeout and clearTimeout functions.
|
|
3
|
+
*/
|
|
1
4
|
export declare class TimeoutAPI {
|
|
2
5
|
private timeout_handlers;
|
|
3
|
-
|
|
6
|
+
/**
|
|
7
|
+
* Calls a function after a specified delay, in milliseconds.
|
|
8
|
+
* @param callback The function to call after the delay.
|
|
9
|
+
* @param delay The number of milliseconds to wait before calling the function.
|
|
10
|
+
* @returns A handle that can be used to clear the timeout with clearTimeout.
|
|
11
|
+
*/
|
|
12
|
+
setTimeout(callback: () => void, delay: number): number;
|
|
13
|
+
/**
|
|
14
|
+
* Clears all timeouts that have been created with setTimeout.
|
|
15
|
+
*/
|
|
4
16
|
clearAllTimeouts(): void;
|
|
5
17
|
}
|
package/dist/modules/utils.d.ts
CHANGED
|
@@ -5,3 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export declare function unique(arr: Array<any>): any[];
|
|
7
7
|
export declare function deepCopy(obj: any): any;
|
|
8
|
+
/**
|
|
9
|
+
* Merges two objects, recursively.
|
|
10
|
+
* @param obj1 Object to merge
|
|
11
|
+
* @param obj2 Object to merge
|
|
12
|
+
*/
|
|
13
|
+
export declare function deepMerge(obj1: any, obj2: any): any;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jspsych",
|
|
3
|
-
"version": "7.3.
|
|
3
|
+
"version": "7.3.3",
|
|
4
4
|
"description": "Behavioral experiments in a browser",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.cjs",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"npm-run-all": "^4.1.5",
|
|
56
56
|
"sass": "^1.43.5",
|
|
57
57
|
"sass-loader": "^12.4.0",
|
|
58
|
-
"webpack": "^5.
|
|
58
|
+
"webpack": "^5.76.0",
|
|
59
59
|
"webpack-cli": "^4.9.2",
|
|
60
60
|
"webpack-remove-empty-scripts": "^0.7.2",
|
|
61
61
|
"replace-in-file-webpack-plugin": "^1.0.6"
|
package/src/JsPsych.ts
CHANGED
|
@@ -627,13 +627,14 @@ export class JsPsych {
|
|
|
627
627
|
};
|
|
628
628
|
|
|
629
629
|
let trial_complete;
|
|
630
|
+
let trial_sim_opts;
|
|
631
|
+
let trial_sim_opts_merged;
|
|
630
632
|
if (!this.simulation_mode) {
|
|
631
633
|
trial_complete = trial.type.trial(this.DOM_target, trial, load_callback);
|
|
632
634
|
}
|
|
633
635
|
if (this.simulation_mode) {
|
|
634
636
|
// check if the trial supports simulation
|
|
635
637
|
if (trial.type.simulate) {
|
|
636
|
-
let trial_sim_opts;
|
|
637
638
|
if (!trial.simulation_options) {
|
|
638
639
|
trial_sim_opts = this.simulation_options.default;
|
|
639
640
|
}
|
|
@@ -656,16 +657,23 @@ export class JsPsych {
|
|
|
656
657
|
trial_sim_opts = trial.simulation_options;
|
|
657
658
|
}
|
|
658
659
|
}
|
|
659
|
-
|
|
660
|
-
|
|
660
|
+
// merge in default options that aren't overriden by the trial's simulation_options
|
|
661
|
+
// including nested parameters in the simulation_options
|
|
662
|
+
trial_sim_opts_merged = this.utils.deepMerge(
|
|
663
|
+
this.simulation_options.default,
|
|
664
|
+
trial_sim_opts
|
|
665
|
+
);
|
|
661
666
|
|
|
662
|
-
|
|
667
|
+
trial_sim_opts_merged = this.utils.deepCopy(trial_sim_opts_merged);
|
|
668
|
+
trial_sim_opts_merged = this.replaceFunctionsWithValues(trial_sim_opts_merged, null);
|
|
669
|
+
|
|
670
|
+
if (trial_sim_opts_merged?.simulate === false) {
|
|
663
671
|
trial_complete = trial.type.trial(this.DOM_target, trial, load_callback);
|
|
664
672
|
} else {
|
|
665
673
|
trial_complete = trial.type.simulate(
|
|
666
674
|
trial,
|
|
667
|
-
|
|
668
|
-
|
|
675
|
+
trial_sim_opts_merged?.mode || this.simulation_mode,
|
|
676
|
+
trial_sim_opts_merged,
|
|
669
677
|
load_callback
|
|
670
678
|
);
|
|
671
679
|
}
|
|
@@ -678,8 +686,13 @@ export class JsPsych {
|
|
|
678
686
|
// see if trial_complete is a Promise by looking for .then() function
|
|
679
687
|
const is_promise = trial_complete && typeof trial_complete.then == "function";
|
|
680
688
|
|
|
681
|
-
// in simulation mode we let the simulate function call the load_callback always
|
|
682
|
-
|
|
689
|
+
// in simulation mode we let the simulate function call the load_callback always,
|
|
690
|
+
// so we don't need to call it here. however, if we are in simulation mode but not simulating
|
|
691
|
+
// this particular trial we need to call it.
|
|
692
|
+
if (
|
|
693
|
+
!is_promise &&
|
|
694
|
+
(!this.simulation_mode || (this.simulation_mode && trial_sim_opts_merged?.simulate === false))
|
|
695
|
+
) {
|
|
683
696
|
load_callback();
|
|
684
697
|
}
|
|
685
698
|
|
|
@@ -781,7 +794,14 @@ export class JsPsych {
|
|
|
781
794
|
for (const param in trial.type.info.parameters) {
|
|
782
795
|
// check if parameter is complex with nested defaults
|
|
783
796
|
if (trial.type.info.parameters[param].type === ParameterType.COMPLEX) {
|
|
784
|
-
if
|
|
797
|
+
// check if parameter is undefined and has a default value
|
|
798
|
+
if (typeof trial[param] === "undefined" && trial.type.info.parameters[param].default) {
|
|
799
|
+
trial[param] = trial.type.info.parameters[param].default;
|
|
800
|
+
}
|
|
801
|
+
// if parameter is an array, iterate over each entry after confirming that there are
|
|
802
|
+
// entries to iterate over. this is common when some parameters in a COMPLEX type have
|
|
803
|
+
// default values and others do not.
|
|
804
|
+
if (trial.type.info.parameters[param].array === true && Array.isArray(trial[param])) {
|
|
785
805
|
// iterate over each entry in the array
|
|
786
806
|
trial[param].forEach(function (ip, i) {
|
|
787
807
|
// check each parameter in the plugin description
|
|
@@ -789,13 +809,7 @@ export class JsPsych {
|
|
|
789
809
|
if (typeof trial[param][i][p] === "undefined" || trial[param][i][p] === null) {
|
|
790
810
|
if (typeof trial.type.info.parameters[param].nested[p].default === "undefined") {
|
|
791
811
|
console.error(
|
|
792
|
-
|
|
793
|
-
p +
|
|
794
|
-
" parameter (nested in the " +
|
|
795
|
-
param +
|
|
796
|
-
" parameter) in the " +
|
|
797
|
-
trial.type +
|
|
798
|
-
" plugin."
|
|
812
|
+
`You must specify a value for the ${p} parameter (nested in the ${param} parameter) in the ${trial.type.info.name} plugin.`
|
|
799
813
|
);
|
|
800
814
|
} else {
|
|
801
815
|
trial[param][i][p] = trial.type.info.parameters[param].nested[p].default;
|
|
@@ -809,11 +823,7 @@ export class JsPsych {
|
|
|
809
823
|
else if (typeof trial[param] === "undefined" || trial[param] === null) {
|
|
810
824
|
if (typeof trial.type.info.parameters[param].default === "undefined") {
|
|
811
825
|
console.error(
|
|
812
|
-
|
|
813
|
-
param +
|
|
814
|
-
" parameter in the " +
|
|
815
|
-
trial.type.info.name +
|
|
816
|
-
" plugin."
|
|
826
|
+
`You must specify a value for the ${param} parameter in the ${trial.type.info.name} plugin.`
|
|
817
827
|
);
|
|
818
828
|
} else {
|
|
819
829
|
trial[param] = trial.type.info.parameters[param].default;
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
export class SimulationAPI {
|
|
2
|
+
constructor(
|
|
3
|
+
private getDisplayContainerElement: () => HTMLElement,
|
|
4
|
+
private setJsPsychTimeout: (callback: () => void, delay: number) => number
|
|
5
|
+
) {}
|
|
6
|
+
|
|
2
7
|
dispatchEvent(event: Event) {
|
|
3
|
-
|
|
8
|
+
this.getDisplayContainerElement().dispatchEvent(event);
|
|
4
9
|
}
|
|
5
10
|
|
|
6
11
|
/**
|
|
@@ -26,7 +31,7 @@ export class SimulationAPI {
|
|
|
26
31
|
*/
|
|
27
32
|
pressKey(key: string, delay = 0) {
|
|
28
33
|
if (delay > 0) {
|
|
29
|
-
|
|
34
|
+
this.setJsPsychTimeout(() => {
|
|
30
35
|
this.keyDown(key);
|
|
31
36
|
this.keyUp(key);
|
|
32
37
|
}, delay);
|
|
@@ -43,7 +48,7 @@ export class SimulationAPI {
|
|
|
43
48
|
*/
|
|
44
49
|
clickTarget(target: Element, delay = 0) {
|
|
45
50
|
if (delay > 0) {
|
|
46
|
-
|
|
51
|
+
this.setJsPsychTimeout(() => {
|
|
47
52
|
target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
|
48
53
|
target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
|
|
49
54
|
target.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
@@ -63,7 +68,7 @@ export class SimulationAPI {
|
|
|
63
68
|
*/
|
|
64
69
|
fillTextInput(target: HTMLInputElement, text: string, delay = 0) {
|
|
65
70
|
if (delay > 0) {
|
|
66
|
-
|
|
71
|
+
this.setJsPsychTimeout(() => {
|
|
67
72
|
target.value = text;
|
|
68
73
|
}, delay);
|
|
69
74
|
} else {
|
|
@@ -1,13 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A class that provides a wrapper around the global setTimeout and clearTimeout functions.
|
|
3
|
+
*/
|
|
1
4
|
export class TimeoutAPI {
|
|
2
|
-
private timeout_handlers = [];
|
|
5
|
+
private timeout_handlers: number[] = [];
|
|
3
6
|
|
|
4
|
-
|
|
7
|
+
/**
|
|
8
|
+
* Calls a function after a specified delay, in milliseconds.
|
|
9
|
+
* @param callback The function to call after the delay.
|
|
10
|
+
* @param delay The number of milliseconds to wait before calling the function.
|
|
11
|
+
* @returns A handle that can be used to clear the timeout with clearTimeout.
|
|
12
|
+
*/
|
|
13
|
+
setTimeout(callback: () => void, delay: number): number {
|
|
5
14
|
const handle = window.setTimeout(callback, delay);
|
|
6
15
|
this.timeout_handlers.push(handle);
|
|
7
16
|
return handle;
|
|
8
17
|
}
|
|
9
18
|
|
|
10
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Clears all timeouts that have been created with setTimeout.
|
|
21
|
+
*/
|
|
22
|
+
clearAllTimeouts(): void {
|
|
11
23
|
for (const handler of this.timeout_handlers) {
|
|
12
24
|
clearTimeout(handler);
|
|
13
25
|
}
|
|
@@ -9,19 +9,22 @@ import { TimeoutAPI } from "./TimeoutAPI";
|
|
|
9
9
|
|
|
10
10
|
export function createJointPluginAPIObject(jsPsych: JsPsych) {
|
|
11
11
|
const settings = jsPsych.getInitSettings();
|
|
12
|
+
const keyboardListenerAPI = autoBind(
|
|
13
|
+
new KeyboardListenerAPI(
|
|
14
|
+
jsPsych.getDisplayContainerElement,
|
|
15
|
+
settings.case_sensitive_responses,
|
|
16
|
+
settings.minimum_valid_rt
|
|
17
|
+
)
|
|
18
|
+
);
|
|
19
|
+
const timeoutAPI = autoBind(new TimeoutAPI());
|
|
20
|
+
const mediaAPI = autoBind(new MediaAPI(settings.use_webaudio, jsPsych.webaudio_context));
|
|
21
|
+
const hardwareAPI = autoBind(new HardwareAPI());
|
|
22
|
+
const simulationAPI = autoBind(
|
|
23
|
+
new SimulationAPI(jsPsych.getDisplayContainerElement, timeoutAPI.setTimeout)
|
|
24
|
+
);
|
|
12
25
|
return Object.assign(
|
|
13
26
|
{},
|
|
14
|
-
...[
|
|
15
|
-
new KeyboardListenerAPI(
|
|
16
|
-
jsPsych.getDisplayContainerElement,
|
|
17
|
-
settings.case_sensitive_responses,
|
|
18
|
-
settings.minimum_valid_rt
|
|
19
|
-
),
|
|
20
|
-
new TimeoutAPI(),
|
|
21
|
-
new MediaAPI(settings.use_webaudio, jsPsych.webaudio_context),
|
|
22
|
-
new HardwareAPI(),
|
|
23
|
-
new SimulationAPI(),
|
|
24
|
-
].map((object) => autoBind(object))
|
|
27
|
+
...[keyboardListenerAPI, timeoutAPI, mediaAPI, hardwareAPI, simulationAPI]
|
|
25
28
|
) as KeyboardListenerAPI & TimeoutAPI & MediaAPI & HardwareAPI & SimulationAPI;
|
|
26
29
|
}
|
|
27
30
|
|
package/src/modules/utils.ts
CHANGED
|
@@ -28,3 +28,34 @@ export function deepCopy(obj) {
|
|
|
28
28
|
return obj;
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Merges two objects, recursively.
|
|
34
|
+
* @param obj1 Object to merge
|
|
35
|
+
* @param obj2 Object to merge
|
|
36
|
+
*/
|
|
37
|
+
export function deepMerge(obj1: any, obj2: any): any {
|
|
38
|
+
let merged = {};
|
|
39
|
+
for (const key in obj1) {
|
|
40
|
+
if (obj1.hasOwnProperty(key)) {
|
|
41
|
+
if (typeof obj1[key] === "object" && obj2.hasOwnProperty(key)) {
|
|
42
|
+
merged[key] = deepMerge(obj1[key], obj2[key]);
|
|
43
|
+
} else {
|
|
44
|
+
merged[key] = obj1[key];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
for (const key in obj2) {
|
|
49
|
+
if (obj2.hasOwnProperty(key)) {
|
|
50
|
+
if (!merged.hasOwnProperty(key)) {
|
|
51
|
+
merged[key] = obj2[key];
|
|
52
|
+
} else if (typeof obj2[key] === "object") {
|
|
53
|
+
merged[key] = deepMerge(merged[key], obj2[key]);
|
|
54
|
+
} else {
|
|
55
|
+
merged[key] = obj2[key];
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return merged;
|
|
61
|
+
}
|