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
package/dist/index.cjs
CHANGED
|
@@ -71,7 +71,7 @@ var autoBind = (self, {include, exclude} = {}) => {
|
|
|
71
71
|
return self;
|
|
72
72
|
};
|
|
73
73
|
|
|
74
|
-
var version = "7.3.
|
|
74
|
+
var version = "7.3.3";
|
|
75
75
|
|
|
76
76
|
class MigrationError extends Error {
|
|
77
77
|
constructor(message = "The global `jsPsych` variable is no longer available in jsPsych v7.") {
|
|
@@ -135,12 +135,45 @@ function deepCopy(obj) {
|
|
|
135
135
|
else {
|
|
136
136
|
return obj;
|
|
137
137
|
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Merges two objects, recursively.
|
|
141
|
+
* @param obj1 Object to merge
|
|
142
|
+
* @param obj2 Object to merge
|
|
143
|
+
*/
|
|
144
|
+
function deepMerge(obj1, obj2) {
|
|
145
|
+
let merged = {};
|
|
146
|
+
for (const key in obj1) {
|
|
147
|
+
if (obj1.hasOwnProperty(key)) {
|
|
148
|
+
if (typeof obj1[key] === "object" && obj2.hasOwnProperty(key)) {
|
|
149
|
+
merged[key] = deepMerge(obj1[key], obj2[key]);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
merged[key] = obj1[key];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
for (const key in obj2) {
|
|
157
|
+
if (obj2.hasOwnProperty(key)) {
|
|
158
|
+
if (!merged.hasOwnProperty(key)) {
|
|
159
|
+
merged[key] = obj2[key];
|
|
160
|
+
}
|
|
161
|
+
else if (typeof obj2[key] === "object") {
|
|
162
|
+
merged[key] = deepMerge(merged[key], obj2[key]);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
merged[key] = obj2[key];
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return merged;
|
|
138
170
|
}
|
|
139
171
|
|
|
140
172
|
var utils = /*#__PURE__*/Object.freeze({
|
|
141
173
|
__proto__: null,
|
|
142
174
|
unique: unique,
|
|
143
|
-
deepCopy: deepCopy
|
|
175
|
+
deepCopy: deepCopy,
|
|
176
|
+
deepMerge: deepMerge
|
|
144
177
|
});
|
|
145
178
|
|
|
146
179
|
class DataColumn {
|
|
@@ -1106,8 +1139,12 @@ class MediaAPI {
|
|
|
1106
1139
|
}
|
|
1107
1140
|
|
|
1108
1141
|
class SimulationAPI {
|
|
1142
|
+
constructor(getDisplayContainerElement, setJsPsychTimeout) {
|
|
1143
|
+
this.getDisplayContainerElement = getDisplayContainerElement;
|
|
1144
|
+
this.setJsPsychTimeout = setJsPsychTimeout;
|
|
1145
|
+
}
|
|
1109
1146
|
dispatchEvent(event) {
|
|
1110
|
-
|
|
1147
|
+
this.getDisplayContainerElement().dispatchEvent(event);
|
|
1111
1148
|
}
|
|
1112
1149
|
/**
|
|
1113
1150
|
* Dispatches a `keydown` event for the specified key
|
|
@@ -1130,7 +1167,7 @@ class SimulationAPI {
|
|
|
1130
1167
|
*/
|
|
1131
1168
|
pressKey(key, delay = 0) {
|
|
1132
1169
|
if (delay > 0) {
|
|
1133
|
-
|
|
1170
|
+
this.setJsPsychTimeout(() => {
|
|
1134
1171
|
this.keyDown(key);
|
|
1135
1172
|
this.keyUp(key);
|
|
1136
1173
|
}, delay);
|
|
@@ -1147,7 +1184,7 @@ class SimulationAPI {
|
|
|
1147
1184
|
*/
|
|
1148
1185
|
clickTarget(target, delay = 0) {
|
|
1149
1186
|
if (delay > 0) {
|
|
1150
|
-
|
|
1187
|
+
this.setJsPsychTimeout(() => {
|
|
1151
1188
|
target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
|
1152
1189
|
target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
|
|
1153
1190
|
target.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
@@ -1167,7 +1204,7 @@ class SimulationAPI {
|
|
|
1167
1204
|
*/
|
|
1168
1205
|
fillTextInput(target, text, delay = 0) {
|
|
1169
1206
|
if (delay > 0) {
|
|
1170
|
-
|
|
1207
|
+
this.setJsPsychTimeout(() => {
|
|
1171
1208
|
target.value = text;
|
|
1172
1209
|
}, delay);
|
|
1173
1210
|
}
|
|
@@ -1276,15 +1313,27 @@ class SimulationAPI {
|
|
|
1276
1313
|
}
|
|
1277
1314
|
}
|
|
1278
1315
|
|
|
1316
|
+
/**
|
|
1317
|
+
* A class that provides a wrapper around the global setTimeout and clearTimeout functions.
|
|
1318
|
+
*/
|
|
1279
1319
|
class TimeoutAPI {
|
|
1280
1320
|
constructor() {
|
|
1281
1321
|
this.timeout_handlers = [];
|
|
1282
1322
|
}
|
|
1323
|
+
/**
|
|
1324
|
+
* Calls a function after a specified delay, in milliseconds.
|
|
1325
|
+
* @param callback The function to call after the delay.
|
|
1326
|
+
* @param delay The number of milliseconds to wait before calling the function.
|
|
1327
|
+
* @returns A handle that can be used to clear the timeout with clearTimeout.
|
|
1328
|
+
*/
|
|
1283
1329
|
setTimeout(callback, delay) {
|
|
1284
1330
|
const handle = window.setTimeout(callback, delay);
|
|
1285
1331
|
this.timeout_handlers.push(handle);
|
|
1286
1332
|
return handle;
|
|
1287
1333
|
}
|
|
1334
|
+
/**
|
|
1335
|
+
* Clears all timeouts that have been created with setTimeout.
|
|
1336
|
+
*/
|
|
1288
1337
|
clearAllTimeouts() {
|
|
1289
1338
|
for (const handler of this.timeout_handlers) {
|
|
1290
1339
|
clearTimeout(handler);
|
|
@@ -1295,13 +1344,12 @@ class TimeoutAPI {
|
|
|
1295
1344
|
|
|
1296
1345
|
function createJointPluginAPIObject(jsPsych) {
|
|
1297
1346
|
const settings = jsPsych.getInitSettings();
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
].map((object) => autoBind(object)));
|
|
1347
|
+
const keyboardListenerAPI = autoBind(new KeyboardListenerAPI(jsPsych.getDisplayContainerElement, settings.case_sensitive_responses, settings.minimum_valid_rt));
|
|
1348
|
+
const timeoutAPI = autoBind(new TimeoutAPI());
|
|
1349
|
+
const mediaAPI = autoBind(new MediaAPI(settings.use_webaudio, jsPsych.webaudio_context));
|
|
1350
|
+
const hardwareAPI = autoBind(new HardwareAPI());
|
|
1351
|
+
const simulationAPI = autoBind(new SimulationAPI(jsPsych.getDisplayContainerElement, timeoutAPI.setTimeout));
|
|
1352
|
+
return Object.assign({}, ...[keyboardListenerAPI, timeoutAPI, mediaAPI, hardwareAPI, simulationAPI]);
|
|
1305
1353
|
}
|
|
1306
1354
|
|
|
1307
1355
|
var wordList = [
|
|
@@ -3056,13 +3104,14 @@ class JsPsych {
|
|
|
3056
3104
|
}
|
|
3057
3105
|
};
|
|
3058
3106
|
let trial_complete;
|
|
3107
|
+
let trial_sim_opts;
|
|
3108
|
+
let trial_sim_opts_merged;
|
|
3059
3109
|
if (!this.simulation_mode) {
|
|
3060
3110
|
trial_complete = trial.type.trial(this.DOM_target, trial, load_callback);
|
|
3061
3111
|
}
|
|
3062
3112
|
if (this.simulation_mode) {
|
|
3063
3113
|
// check if the trial supports simulation
|
|
3064
3114
|
if (trial.type.simulate) {
|
|
3065
|
-
let trial_sim_opts;
|
|
3066
3115
|
if (!trial.simulation_options) {
|
|
3067
3116
|
trial_sim_opts = this.simulation_options.default;
|
|
3068
3117
|
}
|
|
@@ -3084,13 +3133,16 @@ class JsPsych {
|
|
|
3084
3133
|
trial_sim_opts = trial.simulation_options;
|
|
3085
3134
|
}
|
|
3086
3135
|
}
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3136
|
+
// merge in default options that aren't overriden by the trial's simulation_options
|
|
3137
|
+
// including nested parameters in the simulation_options
|
|
3138
|
+
trial_sim_opts_merged = this.utils.deepMerge(this.simulation_options.default, trial_sim_opts);
|
|
3139
|
+
trial_sim_opts_merged = this.utils.deepCopy(trial_sim_opts_merged);
|
|
3140
|
+
trial_sim_opts_merged = this.replaceFunctionsWithValues(trial_sim_opts_merged, null);
|
|
3141
|
+
if ((trial_sim_opts_merged === null || trial_sim_opts_merged === void 0 ? void 0 : trial_sim_opts_merged.simulate) === false) {
|
|
3090
3142
|
trial_complete = trial.type.trial(this.DOM_target, trial, load_callback);
|
|
3091
3143
|
}
|
|
3092
3144
|
else {
|
|
3093
|
-
trial_complete = trial.type.simulate(trial, (
|
|
3145
|
+
trial_complete = trial.type.simulate(trial, (trial_sim_opts_merged === null || trial_sim_opts_merged === void 0 ? void 0 : trial_sim_opts_merged.mode) || this.simulation_mode, trial_sim_opts_merged, load_callback);
|
|
3094
3146
|
}
|
|
3095
3147
|
}
|
|
3096
3148
|
else {
|
|
@@ -3100,8 +3152,11 @@ class JsPsych {
|
|
|
3100
3152
|
}
|
|
3101
3153
|
// see if trial_complete is a Promise by looking for .then() function
|
|
3102
3154
|
const is_promise = trial_complete && typeof trial_complete.then == "function";
|
|
3103
|
-
// in simulation mode we let the simulate function call the load_callback always
|
|
3104
|
-
|
|
3155
|
+
// in simulation mode we let the simulate function call the load_callback always,
|
|
3156
|
+
// so we don't need to call it here. however, if we are in simulation mode but not simulating
|
|
3157
|
+
// this particular trial we need to call it.
|
|
3158
|
+
if (!is_promise &&
|
|
3159
|
+
(!this.simulation_mode || (this.simulation_mode && (trial_sim_opts_merged === null || trial_sim_opts_merged === void 0 ? void 0 : trial_sim_opts_merged.simulate) === false))) {
|
|
3105
3160
|
load_callback();
|
|
3106
3161
|
}
|
|
3107
3162
|
// done with callbacks
|
|
@@ -3189,20 +3244,21 @@ class JsPsych {
|
|
|
3189
3244
|
for (const param in trial.type.info.parameters) {
|
|
3190
3245
|
// check if parameter is complex with nested defaults
|
|
3191
3246
|
if (trial.type.info.parameters[param].type === exports.ParameterType.COMPLEX) {
|
|
3192
|
-
if
|
|
3247
|
+
// check if parameter is undefined and has a default value
|
|
3248
|
+
if (typeof trial[param] === "undefined" && trial.type.info.parameters[param].default) {
|
|
3249
|
+
trial[param] = trial.type.info.parameters[param].default;
|
|
3250
|
+
}
|
|
3251
|
+
// if parameter is an array, iterate over each entry after confirming that there are
|
|
3252
|
+
// entries to iterate over. this is common when some parameters in a COMPLEX type have
|
|
3253
|
+
// default values and others do not.
|
|
3254
|
+
if (trial.type.info.parameters[param].array === true && Array.isArray(trial[param])) {
|
|
3193
3255
|
// iterate over each entry in the array
|
|
3194
3256
|
trial[param].forEach(function (ip, i) {
|
|
3195
3257
|
// check each parameter in the plugin description
|
|
3196
3258
|
for (const p in trial.type.info.parameters[param].nested) {
|
|
3197
3259
|
if (typeof trial[param][i][p] === "undefined" || trial[param][i][p] === null) {
|
|
3198
3260
|
if (typeof trial.type.info.parameters[param].nested[p].default === "undefined") {
|
|
3199
|
-
console.error(
|
|
3200
|
-
p +
|
|
3201
|
-
" parameter (nested in the " +
|
|
3202
|
-
param +
|
|
3203
|
-
" parameter) in the " +
|
|
3204
|
-
trial.type +
|
|
3205
|
-
" plugin.");
|
|
3261
|
+
console.error(`You must specify a value for the ${p} parameter (nested in the ${param} parameter) in the ${trial.type.info.name} plugin.`);
|
|
3206
3262
|
}
|
|
3207
3263
|
else {
|
|
3208
3264
|
trial[param][i][p] = trial.type.info.parameters[param].nested[p].default;
|
|
@@ -3215,11 +3271,7 @@ class JsPsych {
|
|
|
3215
3271
|
// if it's not nested, checking is much easier and do that here:
|
|
3216
3272
|
else if (typeof trial[param] === "undefined" || trial[param] === null) {
|
|
3217
3273
|
if (typeof trial.type.info.parameters[param].default === "undefined") {
|
|
3218
|
-
console.error(
|
|
3219
|
-
param +
|
|
3220
|
-
" parameter in the " +
|
|
3221
|
-
trial.type.info.name +
|
|
3222
|
-
" plugin.");
|
|
3274
|
+
console.error(`You must specify a value for the ${param} parameter in the ${trial.type.info.name} plugin.`);
|
|
3223
3275
|
}
|
|
3224
3276
|
else {
|
|
3225
3277
|
trial[param] = trial.type.info.parameters[param].default;
|