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 CHANGED
@@ -48,9 +48,15 @@ See the [contributing to jsPsych](https://www.jspsych.org/latest/developers/cont
48
48
 
49
49
  ## Citation
50
50
 
51
- If you use this library in academic work, please cite the [paper that describes jsPsych](http://link.springer.com/article/10.3758%2Fs13428-014-0458-y):
51
+ If you use this library in academic work, the preferred citation is:
52
52
 
53
- de Leeuw, J.R. (2015). jsPsych: A JavaScript library for creating behavioral experiments in a Web browser. *Behavior Research Methods*, _47_(1), 1-12. doi:10.3758/s13428-014-0458-y
53
+ de Leeuw, J.R., Gilbert, R.A., & Luchterhandt, B. (2023). jsPsych: Enabling an open-source collaborative ecosystem of behavioral experiments. *Journal of Open Source Software*, *8*(85), 5351, [https://joss.theoj.org/papers/10.21105/joss.05351](https://joss.theoj.org/papers/10.21105/joss.05351).
54
+
55
+ This paper is an updated description of jsPsych and includes all current core team members. It replaces the earlier paper that described jsPsych:
56
+
57
+ de Leeuw, J.R. (2015). jsPsych: A JavaScript library for creating behavioral experiments in a Web browser. *Behavior Research Methods*, _47_(1), 1-12. doi:[10.3758/s13428-014-0458-y](http://link.springer.com/article/10.3758%2Fs13428-014-0458-y)
58
+
59
+ Citations help us demonstrate that this library is used and valued, which allows us to continue working on it.
54
60
 
55
61
  ## Contributors
56
62
 
@@ -59,4 +65,4 @@ The project is currently managed by the core team of Josh de Leeuw ([@jodeleeuw]
59
65
 
60
66
  jsPsych was created by [Josh de Leeuw](http://www.twitter.com/joshdeleeuw).
61
67
 
62
- We're also 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-2021.
68
+ We're also 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.
@@ -70,7 +70,7 @@ var jsPsychModule = (function (exports) {
70
70
  return self;
71
71
  };
72
72
 
73
- var version = "7.3.1";
73
+ var version = "7.3.3";
74
74
 
75
75
  class MigrationError extends Error {
76
76
  constructor(message = "The global `jsPsych` variable is no longer available in jsPsych v7.") {
@@ -134,12 +134,45 @@ var jsPsychModule = (function (exports) {
134
134
  else {
135
135
  return obj;
136
136
  }
137
+ }
138
+ /**
139
+ * Merges two objects, recursively.
140
+ * @param obj1 Object to merge
141
+ * @param obj2 Object to merge
142
+ */
143
+ function deepMerge(obj1, obj2) {
144
+ let merged = {};
145
+ for (const key in obj1) {
146
+ if (obj1.hasOwnProperty(key)) {
147
+ if (typeof obj1[key] === "object" && obj2.hasOwnProperty(key)) {
148
+ merged[key] = deepMerge(obj1[key], obj2[key]);
149
+ }
150
+ else {
151
+ merged[key] = obj1[key];
152
+ }
153
+ }
154
+ }
155
+ for (const key in obj2) {
156
+ if (obj2.hasOwnProperty(key)) {
157
+ if (!merged.hasOwnProperty(key)) {
158
+ merged[key] = obj2[key];
159
+ }
160
+ else if (typeof obj2[key] === "object") {
161
+ merged[key] = deepMerge(merged[key], obj2[key]);
162
+ }
163
+ else {
164
+ merged[key] = obj2[key];
165
+ }
166
+ }
167
+ }
168
+ return merged;
137
169
  }
138
170
 
139
171
  var utils = /*#__PURE__*/Object.freeze({
140
172
  __proto__: null,
141
173
  unique: unique,
142
- deepCopy: deepCopy
174
+ deepCopy: deepCopy,
175
+ deepMerge: deepMerge
143
176
  });
144
177
 
145
178
  class DataColumn {
@@ -1105,8 +1138,12 @@ var jsPsychModule = (function (exports) {
1105
1138
  }
1106
1139
 
1107
1140
  class SimulationAPI {
1141
+ constructor(getDisplayContainerElement, setJsPsychTimeout) {
1142
+ this.getDisplayContainerElement = getDisplayContainerElement;
1143
+ this.setJsPsychTimeout = setJsPsychTimeout;
1144
+ }
1108
1145
  dispatchEvent(event) {
1109
- document.body.dispatchEvent(event);
1146
+ this.getDisplayContainerElement().dispatchEvent(event);
1110
1147
  }
1111
1148
  /**
1112
1149
  * Dispatches a `keydown` event for the specified key
@@ -1129,7 +1166,7 @@ var jsPsychModule = (function (exports) {
1129
1166
  */
1130
1167
  pressKey(key, delay = 0) {
1131
1168
  if (delay > 0) {
1132
- setTimeout(() => {
1169
+ this.setJsPsychTimeout(() => {
1133
1170
  this.keyDown(key);
1134
1171
  this.keyUp(key);
1135
1172
  }, delay);
@@ -1146,7 +1183,7 @@ var jsPsychModule = (function (exports) {
1146
1183
  */
1147
1184
  clickTarget(target, delay = 0) {
1148
1185
  if (delay > 0) {
1149
- setTimeout(() => {
1186
+ this.setJsPsychTimeout(() => {
1150
1187
  target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
1151
1188
  target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
1152
1189
  target.dispatchEvent(new MouseEvent("click", { bubbles: true }));
@@ -1166,7 +1203,7 @@ var jsPsychModule = (function (exports) {
1166
1203
  */
1167
1204
  fillTextInput(target, text, delay = 0) {
1168
1205
  if (delay > 0) {
1169
- setTimeout(() => {
1206
+ this.setJsPsychTimeout(() => {
1170
1207
  target.value = text;
1171
1208
  }, delay);
1172
1209
  }
@@ -1275,15 +1312,27 @@ var jsPsychModule = (function (exports) {
1275
1312
  }
1276
1313
  }
1277
1314
 
1315
+ /**
1316
+ * A class that provides a wrapper around the global setTimeout and clearTimeout functions.
1317
+ */
1278
1318
  class TimeoutAPI {
1279
1319
  constructor() {
1280
1320
  this.timeout_handlers = [];
1281
1321
  }
1322
+ /**
1323
+ * Calls a function after a specified delay, in milliseconds.
1324
+ * @param callback The function to call after the delay.
1325
+ * @param delay The number of milliseconds to wait before calling the function.
1326
+ * @returns A handle that can be used to clear the timeout with clearTimeout.
1327
+ */
1282
1328
  setTimeout(callback, delay) {
1283
1329
  const handle = window.setTimeout(callback, delay);
1284
1330
  this.timeout_handlers.push(handle);
1285
1331
  return handle;
1286
1332
  }
1333
+ /**
1334
+ * Clears all timeouts that have been created with setTimeout.
1335
+ */
1287
1336
  clearAllTimeouts() {
1288
1337
  for (const handler of this.timeout_handlers) {
1289
1338
  clearTimeout(handler);
@@ -1294,13 +1343,12 @@ var jsPsychModule = (function (exports) {
1294
1343
 
1295
1344
  function createJointPluginAPIObject(jsPsych) {
1296
1345
  const settings = jsPsych.getInitSettings();
1297
- return Object.assign({}, ...[
1298
- new KeyboardListenerAPI(jsPsych.getDisplayContainerElement, settings.case_sensitive_responses, settings.minimum_valid_rt),
1299
- new TimeoutAPI(),
1300
- new MediaAPI(settings.use_webaudio, jsPsych.webaudio_context),
1301
- new HardwareAPI(),
1302
- new SimulationAPI(),
1303
- ].map((object) => autoBind(object)));
1346
+ const keyboardListenerAPI = autoBind(new KeyboardListenerAPI(jsPsych.getDisplayContainerElement, settings.case_sensitive_responses, settings.minimum_valid_rt));
1347
+ const timeoutAPI = autoBind(new TimeoutAPI());
1348
+ const mediaAPI = autoBind(new MediaAPI(settings.use_webaudio, jsPsych.webaudio_context));
1349
+ const hardwareAPI = autoBind(new HardwareAPI());
1350
+ const simulationAPI = autoBind(new SimulationAPI(jsPsych.getDisplayContainerElement, timeoutAPI.setTimeout));
1351
+ return Object.assign({}, ...[keyboardListenerAPI, timeoutAPI, mediaAPI, hardwareAPI, simulationAPI]);
1304
1352
  }
1305
1353
 
1306
1354
  var wordList = [
@@ -3055,13 +3103,14 @@ var jsPsychModule = (function (exports) {
3055
3103
  }
3056
3104
  };
3057
3105
  let trial_complete;
3106
+ let trial_sim_opts;
3107
+ let trial_sim_opts_merged;
3058
3108
  if (!this.simulation_mode) {
3059
3109
  trial_complete = trial.type.trial(this.DOM_target, trial, load_callback);
3060
3110
  }
3061
3111
  if (this.simulation_mode) {
3062
3112
  // check if the trial supports simulation
3063
3113
  if (trial.type.simulate) {
3064
- let trial_sim_opts;
3065
3114
  if (!trial.simulation_options) {
3066
3115
  trial_sim_opts = this.simulation_options.default;
3067
3116
  }
@@ -3083,13 +3132,16 @@ var jsPsychModule = (function (exports) {
3083
3132
  trial_sim_opts = trial.simulation_options;
3084
3133
  }
3085
3134
  }
3086
- trial_sim_opts = this.utils.deepCopy(trial_sim_opts);
3087
- trial_sim_opts = this.replaceFunctionsWithValues(trial_sim_opts, null);
3088
- if ((trial_sim_opts === null || trial_sim_opts === void 0 ? void 0 : trial_sim_opts.simulate) === false) {
3135
+ // merge in default options that aren't overriden by the trial's simulation_options
3136
+ // including nested parameters in the simulation_options
3137
+ trial_sim_opts_merged = this.utils.deepMerge(this.simulation_options.default, trial_sim_opts);
3138
+ trial_sim_opts_merged = this.utils.deepCopy(trial_sim_opts_merged);
3139
+ trial_sim_opts_merged = this.replaceFunctionsWithValues(trial_sim_opts_merged, null);
3140
+ if ((trial_sim_opts_merged === null || trial_sim_opts_merged === void 0 ? void 0 : trial_sim_opts_merged.simulate) === false) {
3089
3141
  trial_complete = trial.type.trial(this.DOM_target, trial, load_callback);
3090
3142
  }
3091
3143
  else {
3092
- trial_complete = trial.type.simulate(trial, (trial_sim_opts === null || trial_sim_opts === void 0 ? void 0 : trial_sim_opts.mode) || this.simulation_mode, trial_sim_opts, load_callback);
3144
+ 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);
3093
3145
  }
3094
3146
  }
3095
3147
  else {
@@ -3099,8 +3151,11 @@ var jsPsychModule = (function (exports) {
3099
3151
  }
3100
3152
  // see if trial_complete is a Promise by looking for .then() function
3101
3153
  const is_promise = trial_complete && typeof trial_complete.then == "function";
3102
- // in simulation mode we let the simulate function call the load_callback always.
3103
- if (!is_promise && !this.simulation_mode) {
3154
+ // in simulation mode we let the simulate function call the load_callback always,
3155
+ // so we don't need to call it here. however, if we are in simulation mode but not simulating
3156
+ // this particular trial we need to call it.
3157
+ if (!is_promise &&
3158
+ (!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))) {
3104
3159
  load_callback();
3105
3160
  }
3106
3161
  // done with callbacks
@@ -3188,20 +3243,21 @@ var jsPsychModule = (function (exports) {
3188
3243
  for (const param in trial.type.info.parameters) {
3189
3244
  // check if parameter is complex with nested defaults
3190
3245
  if (trial.type.info.parameters[param].type === exports.ParameterType.COMPLEX) {
3191
- if (trial.type.info.parameters[param].array === true) {
3246
+ // check if parameter is undefined and has a default value
3247
+ if (typeof trial[param] === "undefined" && trial.type.info.parameters[param].default) {
3248
+ trial[param] = trial.type.info.parameters[param].default;
3249
+ }
3250
+ // if parameter is an array, iterate over each entry after confirming that there are
3251
+ // entries to iterate over. this is common when some parameters in a COMPLEX type have
3252
+ // default values and others do not.
3253
+ if (trial.type.info.parameters[param].array === true && Array.isArray(trial[param])) {
3192
3254
  // iterate over each entry in the array
3193
3255
  trial[param].forEach(function (ip, i) {
3194
3256
  // check each parameter in the plugin description
3195
3257
  for (const p in trial.type.info.parameters[param].nested) {
3196
3258
  if (typeof trial[param][i][p] === "undefined" || trial[param][i][p] === null) {
3197
3259
  if (typeof trial.type.info.parameters[param].nested[p].default === "undefined") {
3198
- console.error("You must specify a value for the " +
3199
- p +
3200
- " parameter (nested in the " +
3201
- param +
3202
- " parameter) in the " +
3203
- trial.type +
3204
- " plugin.");
3260
+ console.error(`You must specify a value for the ${p} parameter (nested in the ${param} parameter) in the ${trial.type.info.name} plugin.`);
3205
3261
  }
3206
3262
  else {
3207
3263
  trial[param][i][p] = trial.type.info.parameters[param].nested[p].default;
@@ -3214,11 +3270,7 @@ var jsPsychModule = (function (exports) {
3214
3270
  // if it's not nested, checking is much easier and do that here:
3215
3271
  else if (typeof trial[param] === "undefined" || trial[param] === null) {
3216
3272
  if (typeof trial.type.info.parameters[param].default === "undefined") {
3217
- console.error("You must specify a value for the " +
3218
- param +
3219
- " parameter in the " +
3220
- trial.type.info.name +
3221
- " plugin.");
3273
+ console.error(`You must specify a value for the ${param} parameter in the ${trial.type.info.name} plugin.`);
3222
3274
  }
3223
3275
  else {
3224
3276
  trial[param] = trial.type.info.parameters[param].default;