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.js
CHANGED
|
@@ -67,7 +67,7 @@ var autoBind = (self, {include, exclude} = {}) => {
|
|
|
67
67
|
return self;
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
var version = "7.3.
|
|
70
|
+
var version = "7.3.3";
|
|
71
71
|
|
|
72
72
|
class MigrationError extends Error {
|
|
73
73
|
constructor(message = "The global `jsPsych` variable is no longer available in jsPsych v7.") {
|
|
@@ -131,12 +131,45 @@ function deepCopy(obj) {
|
|
|
131
131
|
else {
|
|
132
132
|
return obj;
|
|
133
133
|
}
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Merges two objects, recursively.
|
|
137
|
+
* @param obj1 Object to merge
|
|
138
|
+
* @param obj2 Object to merge
|
|
139
|
+
*/
|
|
140
|
+
function deepMerge(obj1, obj2) {
|
|
141
|
+
let merged = {};
|
|
142
|
+
for (const key in obj1) {
|
|
143
|
+
if (obj1.hasOwnProperty(key)) {
|
|
144
|
+
if (typeof obj1[key] === "object" && obj2.hasOwnProperty(key)) {
|
|
145
|
+
merged[key] = deepMerge(obj1[key], obj2[key]);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
merged[key] = obj1[key];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
for (const key in obj2) {
|
|
153
|
+
if (obj2.hasOwnProperty(key)) {
|
|
154
|
+
if (!merged.hasOwnProperty(key)) {
|
|
155
|
+
merged[key] = obj2[key];
|
|
156
|
+
}
|
|
157
|
+
else if (typeof obj2[key] === "object") {
|
|
158
|
+
merged[key] = deepMerge(merged[key], obj2[key]);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
merged[key] = obj2[key];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return merged;
|
|
134
166
|
}
|
|
135
167
|
|
|
136
168
|
var utils = /*#__PURE__*/Object.freeze({
|
|
137
169
|
__proto__: null,
|
|
138
170
|
unique: unique,
|
|
139
|
-
deepCopy: deepCopy
|
|
171
|
+
deepCopy: deepCopy,
|
|
172
|
+
deepMerge: deepMerge
|
|
140
173
|
});
|
|
141
174
|
|
|
142
175
|
class DataColumn {
|
|
@@ -1102,8 +1135,12 @@ class MediaAPI {
|
|
|
1102
1135
|
}
|
|
1103
1136
|
|
|
1104
1137
|
class SimulationAPI {
|
|
1138
|
+
constructor(getDisplayContainerElement, setJsPsychTimeout) {
|
|
1139
|
+
this.getDisplayContainerElement = getDisplayContainerElement;
|
|
1140
|
+
this.setJsPsychTimeout = setJsPsychTimeout;
|
|
1141
|
+
}
|
|
1105
1142
|
dispatchEvent(event) {
|
|
1106
|
-
|
|
1143
|
+
this.getDisplayContainerElement().dispatchEvent(event);
|
|
1107
1144
|
}
|
|
1108
1145
|
/**
|
|
1109
1146
|
* Dispatches a `keydown` event for the specified key
|
|
@@ -1126,7 +1163,7 @@ class SimulationAPI {
|
|
|
1126
1163
|
*/
|
|
1127
1164
|
pressKey(key, delay = 0) {
|
|
1128
1165
|
if (delay > 0) {
|
|
1129
|
-
|
|
1166
|
+
this.setJsPsychTimeout(() => {
|
|
1130
1167
|
this.keyDown(key);
|
|
1131
1168
|
this.keyUp(key);
|
|
1132
1169
|
}, delay);
|
|
@@ -1143,7 +1180,7 @@ class SimulationAPI {
|
|
|
1143
1180
|
*/
|
|
1144
1181
|
clickTarget(target, delay = 0) {
|
|
1145
1182
|
if (delay > 0) {
|
|
1146
|
-
|
|
1183
|
+
this.setJsPsychTimeout(() => {
|
|
1147
1184
|
target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
|
|
1148
1185
|
target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
|
|
1149
1186
|
target.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
|
@@ -1163,7 +1200,7 @@ class SimulationAPI {
|
|
|
1163
1200
|
*/
|
|
1164
1201
|
fillTextInput(target, text, delay = 0) {
|
|
1165
1202
|
if (delay > 0) {
|
|
1166
|
-
|
|
1203
|
+
this.setJsPsychTimeout(() => {
|
|
1167
1204
|
target.value = text;
|
|
1168
1205
|
}, delay);
|
|
1169
1206
|
}
|
|
@@ -1272,15 +1309,27 @@ class SimulationAPI {
|
|
|
1272
1309
|
}
|
|
1273
1310
|
}
|
|
1274
1311
|
|
|
1312
|
+
/**
|
|
1313
|
+
* A class that provides a wrapper around the global setTimeout and clearTimeout functions.
|
|
1314
|
+
*/
|
|
1275
1315
|
class TimeoutAPI {
|
|
1276
1316
|
constructor() {
|
|
1277
1317
|
this.timeout_handlers = [];
|
|
1278
1318
|
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Calls a function after a specified delay, in milliseconds.
|
|
1321
|
+
* @param callback The function to call after the delay.
|
|
1322
|
+
* @param delay The number of milliseconds to wait before calling the function.
|
|
1323
|
+
* @returns A handle that can be used to clear the timeout with clearTimeout.
|
|
1324
|
+
*/
|
|
1279
1325
|
setTimeout(callback, delay) {
|
|
1280
1326
|
const handle = window.setTimeout(callback, delay);
|
|
1281
1327
|
this.timeout_handlers.push(handle);
|
|
1282
1328
|
return handle;
|
|
1283
1329
|
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Clears all timeouts that have been created with setTimeout.
|
|
1332
|
+
*/
|
|
1284
1333
|
clearAllTimeouts() {
|
|
1285
1334
|
for (const handler of this.timeout_handlers) {
|
|
1286
1335
|
clearTimeout(handler);
|
|
@@ -1291,13 +1340,12 @@ class TimeoutAPI {
|
|
|
1291
1340
|
|
|
1292
1341
|
function createJointPluginAPIObject(jsPsych) {
|
|
1293
1342
|
const settings = jsPsych.getInitSettings();
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
].map((object) => autoBind(object)));
|
|
1343
|
+
const keyboardListenerAPI = autoBind(new KeyboardListenerAPI(jsPsych.getDisplayContainerElement, settings.case_sensitive_responses, settings.minimum_valid_rt));
|
|
1344
|
+
const timeoutAPI = autoBind(new TimeoutAPI());
|
|
1345
|
+
const mediaAPI = autoBind(new MediaAPI(settings.use_webaudio, jsPsych.webaudio_context));
|
|
1346
|
+
const hardwareAPI = autoBind(new HardwareAPI());
|
|
1347
|
+
const simulationAPI = autoBind(new SimulationAPI(jsPsych.getDisplayContainerElement, timeoutAPI.setTimeout));
|
|
1348
|
+
return Object.assign({}, ...[keyboardListenerAPI, timeoutAPI, mediaAPI, hardwareAPI, simulationAPI]);
|
|
1301
1349
|
}
|
|
1302
1350
|
|
|
1303
1351
|
var wordList = [
|
|
@@ -3052,13 +3100,14 @@ class JsPsych {
|
|
|
3052
3100
|
}
|
|
3053
3101
|
};
|
|
3054
3102
|
let trial_complete;
|
|
3103
|
+
let trial_sim_opts;
|
|
3104
|
+
let trial_sim_opts_merged;
|
|
3055
3105
|
if (!this.simulation_mode) {
|
|
3056
3106
|
trial_complete = trial.type.trial(this.DOM_target, trial, load_callback);
|
|
3057
3107
|
}
|
|
3058
3108
|
if (this.simulation_mode) {
|
|
3059
3109
|
// check if the trial supports simulation
|
|
3060
3110
|
if (trial.type.simulate) {
|
|
3061
|
-
let trial_sim_opts;
|
|
3062
3111
|
if (!trial.simulation_options) {
|
|
3063
3112
|
trial_sim_opts = this.simulation_options.default;
|
|
3064
3113
|
}
|
|
@@ -3080,13 +3129,16 @@ class JsPsych {
|
|
|
3080
3129
|
trial_sim_opts = trial.simulation_options;
|
|
3081
3130
|
}
|
|
3082
3131
|
}
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3132
|
+
// merge in default options that aren't overriden by the trial's simulation_options
|
|
3133
|
+
// including nested parameters in the simulation_options
|
|
3134
|
+
trial_sim_opts_merged = this.utils.deepMerge(this.simulation_options.default, trial_sim_opts);
|
|
3135
|
+
trial_sim_opts_merged = this.utils.deepCopy(trial_sim_opts_merged);
|
|
3136
|
+
trial_sim_opts_merged = this.replaceFunctionsWithValues(trial_sim_opts_merged, null);
|
|
3137
|
+
if ((trial_sim_opts_merged === null || trial_sim_opts_merged === void 0 ? void 0 : trial_sim_opts_merged.simulate) === false) {
|
|
3086
3138
|
trial_complete = trial.type.trial(this.DOM_target, trial, load_callback);
|
|
3087
3139
|
}
|
|
3088
3140
|
else {
|
|
3089
|
-
trial_complete = trial.type.simulate(trial, (
|
|
3141
|
+
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);
|
|
3090
3142
|
}
|
|
3091
3143
|
}
|
|
3092
3144
|
else {
|
|
@@ -3096,8 +3148,11 @@ class JsPsych {
|
|
|
3096
3148
|
}
|
|
3097
3149
|
// see if trial_complete is a Promise by looking for .then() function
|
|
3098
3150
|
const is_promise = trial_complete && typeof trial_complete.then == "function";
|
|
3099
|
-
// in simulation mode we let the simulate function call the load_callback always
|
|
3100
|
-
|
|
3151
|
+
// in simulation mode we let the simulate function call the load_callback always,
|
|
3152
|
+
// so we don't need to call it here. however, if we are in simulation mode but not simulating
|
|
3153
|
+
// this particular trial we need to call it.
|
|
3154
|
+
if (!is_promise &&
|
|
3155
|
+
(!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))) {
|
|
3101
3156
|
load_callback();
|
|
3102
3157
|
}
|
|
3103
3158
|
// done with callbacks
|
|
@@ -3185,20 +3240,21 @@ class JsPsych {
|
|
|
3185
3240
|
for (const param in trial.type.info.parameters) {
|
|
3186
3241
|
// check if parameter is complex with nested defaults
|
|
3187
3242
|
if (trial.type.info.parameters[param].type === ParameterType.COMPLEX) {
|
|
3188
|
-
if
|
|
3243
|
+
// check if parameter is undefined and has a default value
|
|
3244
|
+
if (typeof trial[param] === "undefined" && trial.type.info.parameters[param].default) {
|
|
3245
|
+
trial[param] = trial.type.info.parameters[param].default;
|
|
3246
|
+
}
|
|
3247
|
+
// if parameter is an array, iterate over each entry after confirming that there are
|
|
3248
|
+
// entries to iterate over. this is common when some parameters in a COMPLEX type have
|
|
3249
|
+
// default values and others do not.
|
|
3250
|
+
if (trial.type.info.parameters[param].array === true && Array.isArray(trial[param])) {
|
|
3189
3251
|
// iterate over each entry in the array
|
|
3190
3252
|
trial[param].forEach(function (ip, i) {
|
|
3191
3253
|
// check each parameter in the plugin description
|
|
3192
3254
|
for (const p in trial.type.info.parameters[param].nested) {
|
|
3193
3255
|
if (typeof trial[param][i][p] === "undefined" || trial[param][i][p] === null) {
|
|
3194
3256
|
if (typeof trial.type.info.parameters[param].nested[p].default === "undefined") {
|
|
3195
|
-
console.error(
|
|
3196
|
-
p +
|
|
3197
|
-
" parameter (nested in the " +
|
|
3198
|
-
param +
|
|
3199
|
-
" parameter) in the " +
|
|
3200
|
-
trial.type +
|
|
3201
|
-
" plugin.");
|
|
3257
|
+
console.error(`You must specify a value for the ${p} parameter (nested in the ${param} parameter) in the ${trial.type.info.name} plugin.`);
|
|
3202
3258
|
}
|
|
3203
3259
|
else {
|
|
3204
3260
|
trial[param][i][p] = trial.type.info.parameters[param].nested[p].default;
|
|
@@ -3211,11 +3267,7 @@ class JsPsych {
|
|
|
3211
3267
|
// if it's not nested, checking is much easier and do that here:
|
|
3212
3268
|
else if (typeof trial[param] === "undefined" || trial[param] === null) {
|
|
3213
3269
|
if (typeof trial.type.info.parameters[param].default === "undefined") {
|
|
3214
|
-
console.error(
|
|
3215
|
-
param +
|
|
3216
|
-
" parameter in the " +
|
|
3217
|
-
trial.type.info.name +
|
|
3218
|
-
" plugin.");
|
|
3270
|
+
console.error(`You must specify a value for the ${param} parameter in the ${trial.type.info.name} plugin.`);
|
|
3219
3271
|
}
|
|
3220
3272
|
else {
|
|
3221
3273
|
trial[param] = trial.type.info.parameters[param].default;
|