jspsych 7.2.3 → 7.3.1

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/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.2.3";
74
+ var version = "7.3.1";
75
75
 
76
76
  class MigrationError extends Error {
77
77
  constructor(message = "The global `jsPsych` variable is no longer available in jsPsych v7.") {
@@ -843,8 +843,13 @@ class MediaAPI {
843
843
  this.img_cache = {};
844
844
  this.preloadMap = new Map();
845
845
  this.microphone_recorder = null;
846
+ this.camera_stream = null;
847
+ this.camera_recorder = null;
846
848
  }
847
849
  getVideoBuffer(videoID) {
850
+ if (videoID.startsWith("blob:")) {
851
+ this.video_buffers[videoID] = videoID;
852
+ }
848
853
  return this.video_buffers[videoID];
849
854
  }
850
855
  initAudio() {
@@ -899,15 +904,15 @@ class MediaAPI {
899
904
  callback_error({ source: source, error: e });
900
905
  });
901
906
  };
902
- request.onerror = function (e) {
907
+ request.onerror = (e) => {
903
908
  let err = e;
904
- if (this.status == 404) {
909
+ if (request.status == 404) {
905
910
  err = "404";
906
911
  }
907
912
  callback_error({ source: source, error: err });
908
913
  };
909
- request.onloadend = function (e) {
910
- if (this.status == 404) {
914
+ request.onloadend = (e) => {
915
+ if (request.status == 404) {
911
916
  callback_error({ source: source, error: "404" });
912
917
  }
913
918
  };
@@ -964,20 +969,21 @@ class MediaAPI {
964
969
  callback_complete();
965
970
  return;
966
971
  }
967
- for (var i = 0; i < images.length; i++) {
968
- var img = new Image();
969
- img.onload = function () {
972
+ for (let i = 0; i < images.length; i++) {
973
+ const img = new Image();
974
+ const src = images[i];
975
+ img.onload = () => {
970
976
  n_loaded++;
971
- callback_load(img.src);
977
+ callback_load(src);
972
978
  if (n_loaded === images.length) {
973
979
  callback_complete();
974
980
  }
975
981
  };
976
- img.onerror = function (e) {
977
- callback_error({ source: img.src, error: e });
982
+ img.onerror = (e) => {
983
+ callback_error({ source: src, error: e });
978
984
  };
979
- img.src = images[i];
980
- this.img_cache[images[i]] = img;
985
+ img.src = src;
986
+ this.img_cache[src] = img;
981
987
  this.preload_requests.push(img);
982
988
  }
983
989
  }
@@ -995,9 +1001,9 @@ class MediaAPI {
995
1001
  const request = new XMLHttpRequest();
996
1002
  request.open("GET", video, true);
997
1003
  request.responseType = "blob";
998
- request.onload = function () {
999
- if (this.status === 200 || this.status === 0) {
1000
- const videoBlob = this.response;
1004
+ request.onload = () => {
1005
+ if (request.status === 200 || request.status === 0) {
1006
+ const videoBlob = request.response;
1001
1007
  video_buffers[video] = URL.createObjectURL(videoBlob); // IE10+
1002
1008
  n_loaded++;
1003
1009
  callback_load(video);
@@ -1006,15 +1012,15 @@ class MediaAPI {
1006
1012
  }
1007
1013
  }
1008
1014
  };
1009
- request.onerror = function (e) {
1015
+ request.onerror = (e) => {
1010
1016
  let err = e;
1011
- if (this.status == 404) {
1017
+ if (request.status == 404) {
1012
1018
  err = "404";
1013
1019
  }
1014
1020
  callback_error({ source: video, error: err });
1015
1021
  };
1016
- request.onloadend = function (e) {
1017
- if (this.status == 404) {
1022
+ request.onloadend = (e) => {
1023
+ if (request.status == 404) {
1018
1024
  callback_error({ source: video, error: "404" });
1019
1025
  }
1020
1026
  };
@@ -1086,6 +1092,17 @@ class MediaAPI {
1086
1092
  getMicrophoneRecorder() {
1087
1093
  return this.microphone_recorder;
1088
1094
  }
1095
+ initializeCameraRecorder(stream, opts) {
1096
+ this.camera_stream = stream;
1097
+ const recorder = new MediaRecorder(stream, opts);
1098
+ this.camera_recorder = recorder;
1099
+ }
1100
+ getCameraStream() {
1101
+ return this.camera_stream;
1102
+ }
1103
+ getCameraRecorder() {
1104
+ return this.camera_recorder;
1105
+ }
1089
1106
  }
1090
1107
 
1091
1108
  class SimulationAPI {
@@ -1860,7 +1877,8 @@ function shuffleNoRepeats(arr, equalityTest) {
1860
1877
  // test to make sure the new neighbor isn't equal to the old one
1861
1878
  while (equalityTest(random_shuffle[i + 1], random_shuffle[random_pick]) ||
1862
1879
  equalityTest(random_shuffle[i + 1], random_shuffle[random_pick + 1]) ||
1863
- equalityTest(random_shuffle[i + 1], random_shuffle[random_pick - 1])) {
1880
+ equalityTest(random_shuffle[i + 1], random_shuffle[random_pick - 1]) ||
1881
+ equalityTest(random_shuffle[i], random_shuffle[random_pick])) {
1864
1882
  random_pick = Math.floor(Math.random() * (random_shuffle.length - 2)) + 1;
1865
1883
  }
1866
1884
  const new_neighbor = random_shuffle[random_pick];
@@ -2410,7 +2428,11 @@ class TimelineNode {
2410
2428
  // recursive downward search for active trial to extract timeline variable
2411
2429
  timelineVariable(variable_name) {
2412
2430
  if (typeof this.timeline_parameters == "undefined") {
2413
- return this.findTimelineVariable(variable_name);
2431
+ const val = this.findTimelineVariable(variable_name);
2432
+ if (typeof val === "undefined") {
2433
+ console.warn("Timeline variable " + variable_name + " not found.");
2434
+ }
2435
+ return val;
2414
2436
  }
2415
2437
  else {
2416
2438
  // if progress.current_location is -1, then the timeline variable is being evaluated
@@ -2425,7 +2447,11 @@ class TimelineNode {
2425
2447
  loc = loc - 1;
2426
2448
  }
2427
2449
  // now find the variable
2428
- return this.timeline_parameters.timeline[loc].timelineVariable(variable_name);
2450
+ const val = this.timeline_parameters.timeline[loc].timelineVariable(variable_name);
2451
+ if (typeof val === "undefined") {
2452
+ console.warn("Timeline variable " + variable_name + " not found.");
2453
+ }
2454
+ return val;
2429
2455
  }
2430
2456
  }
2431
2457
  // recursively get all the timeline variables for this trial
@@ -2703,6 +2729,7 @@ class JsPsych {
2703
2729
  return this.DOM_container;
2704
2730
  }
2705
2731
  finishTrial(data = {}) {
2732
+ var _a;
2706
2733
  if (this.current_trial_finished) {
2707
2734
  return;
2708
2735
  }
@@ -2743,46 +2770,58 @@ class JsPsych {
2743
2770
  }
2744
2771
  }
2745
2772
  // handle extension callbacks
2746
- if (Array.isArray(current_trial.extensions)) {
2747
- for (const extension of current_trial.extensions) {
2748
- const ext_data_values = this.extensions[extension.type.info.name].on_finish(extension.params);
2749
- Object.assign(trial_data_values, ext_data_values);
2773
+ const extensionCallbackResults = ((_a = current_trial.extensions) !== null && _a !== void 0 ? _a : []).map((extension) => this.extensions[extension.type.info.name].on_finish(extension.params));
2774
+ const onExtensionCallbacksFinished = () => {
2775
+ // about to execute lots of callbacks, so switch context.
2776
+ this.internal.call_immediate = true;
2777
+ // handle callback at plugin level
2778
+ if (typeof current_trial.on_finish === "function") {
2779
+ current_trial.on_finish(trial_data_values);
2780
+ }
2781
+ // handle callback at whole-experiment level
2782
+ this.opts.on_trial_finish(trial_data_values);
2783
+ // after the above callbacks are complete, then the data should be finalized
2784
+ // for this trial. call the on_data_update handler, passing in the same
2785
+ // data object that just went through the trial's finish handlers.
2786
+ this.opts.on_data_update(trial_data_values);
2787
+ // done with callbacks
2788
+ this.internal.call_immediate = false;
2789
+ // wait for iti
2790
+ if (this.simulation_mode === "data-only") {
2791
+ this.nextTrial();
2750
2792
  }
2751
- }
2752
- // about to execute lots of callbacks, so switch context.
2753
- this.internal.call_immediate = true;
2754
- // handle callback at plugin level
2755
- if (typeof current_trial.on_finish === "function") {
2756
- current_trial.on_finish(trial_data_values);
2757
- }
2758
- // handle callback at whole-experiment level
2759
- this.opts.on_trial_finish(trial_data_values);
2760
- // after the above callbacks are complete, then the data should be finalized
2761
- // for this trial. call the on_data_update handler, passing in the same
2762
- // data object that just went through the trial's finish handlers.
2763
- this.opts.on_data_update(trial_data_values);
2764
- // done with callbacks
2765
- this.internal.call_immediate = false;
2766
- // wait for iti
2767
- if (this.simulation_mode === "data-only") {
2768
- this.nextTrial();
2769
- }
2770
- else if (typeof current_trial.post_trial_gap === null ||
2771
- typeof current_trial.post_trial_gap === "undefined") {
2772
- if (this.opts.default_iti > 0) {
2773
- setTimeout(this.nextTrial, this.opts.default_iti);
2793
+ else if (typeof current_trial.post_trial_gap === null ||
2794
+ typeof current_trial.post_trial_gap === "undefined") {
2795
+ if (this.opts.default_iti > 0) {
2796
+ setTimeout(this.nextTrial, this.opts.default_iti);
2797
+ }
2798
+ else {
2799
+ this.nextTrial();
2800
+ }
2774
2801
  }
2775
2802
  else {
2776
- this.nextTrial();
2803
+ if (current_trial.post_trial_gap > 0) {
2804
+ setTimeout(this.nextTrial, current_trial.post_trial_gap);
2805
+ }
2806
+ else {
2807
+ this.nextTrial();
2808
+ }
2777
2809
  }
2810
+ };
2811
+ // Strictly using Promise.resolve to turn all values into promises would be cleaner here, but it
2812
+ // would require user test code to make the event loop tick after every simulated key press even
2813
+ // if there are no async `on_finish` methods. Hence, in order to avoid a breaking change, we
2814
+ // only rely on the event loop if at least one `on_finish` method returns a promise.
2815
+ if (extensionCallbackResults.some((result) => typeof result.then === "function")) {
2816
+ Promise.all(extensionCallbackResults.map((result) => Promise.resolve(result).then((ext_data_values) => {
2817
+ Object.assign(trial_data_values, ext_data_values);
2818
+ }))).then(onExtensionCallbacksFinished);
2778
2819
  }
2779
2820
  else {
2780
- if (current_trial.post_trial_gap > 0) {
2781
- setTimeout(this.nextTrial, current_trial.post_trial_gap);
2782
- }
2783
- else {
2784
- this.nextTrial();
2821
+ for (const values of extensionCallbackResults) {
2822
+ Object.assign(trial_data_values, values);
2785
2823
  }
2824
+ onExtensionCallbacksFinished();
2786
2825
  }
2787
2826
  }
2788
2827
  endExperiment(end_message = "", data = {}) {
@@ -3070,16 +3109,16 @@ class JsPsych {
3070
3109
  }
3071
3110
  evaluateTimelineVariables(trial) {
3072
3111
  for (const key of Object.keys(trial)) {
3073
- // timeline variables on the root level
3074
3112
  if (typeof trial[key] === "object" &&
3075
3113
  trial[key] !== null &&
3076
3114
  typeof trial[key].timelineVariablePlaceholder !== "undefined") {
3077
- /*trial[key].toString().replace(/\s/g, "") ==
3078
- "function(){returntimeline.timelineVariable(varname);}"
3079
- )*/ trial[key] = trial[key].timelineVariableFunction();
3115
+ trial[key] = trial[key].timelineVariableFunction();
3080
3116
  }
3081
3117
  // timeline variables that are nested in objects
3082
- if (typeof trial[key] === "object" && trial[key] !== null) {
3118
+ if (typeof trial[key] === "object" &&
3119
+ trial[key] !== null &&
3120
+ key !== "timeline" &&
3121
+ key !== "timeline_variables") {
3083
3122
  this.evaluateTimelineVariables(trial[key]);
3084
3123
  }
3085
3124
  }
@@ -3122,9 +3161,11 @@ class JsPsych {
3122
3161
  else if (typeof obj === "object") {
3123
3162
  if (info === null || !info.nested) {
3124
3163
  for (const key of Object.keys(obj)) {
3125
- if (key === "type") {
3164
+ if (key === "type" || key === "timeline" || key === "timeline_variables") {
3126
3165
  // Ignore the object's `type` field because it contains a plugin and we do not want to
3127
- // call plugin functions
3166
+ // call plugin functions. Also ignore `timeline` and `timeline_variables` because they
3167
+ // are used in the `trials` parameter of the preload plugin and we don't want to actually
3168
+ // evaluate those in that context.
3128
3169
  continue;
3129
3170
  }
3130
3171
  obj[key] = this.replaceFunctionsWithValues(obj[key], null);