jspsych 6.3.0 → 7.1.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.
Files changed (397) hide show
  1. package/README.md +36 -37
  2. package/css/jspsych.css +39 -39
  3. package/dist/JsPsych.d.ts +112 -0
  4. package/dist/TimelineNode.d.ts +34 -0
  5. package/dist/index.browser.js +3164 -0
  6. package/dist/index.browser.js.map +1 -0
  7. package/dist/index.browser.min.js +2 -0
  8. package/dist/index.browser.min.js.map +1 -0
  9. package/dist/index.cjs +3158 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.d.ts +11 -0
  12. package/dist/index.js +3152 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/migration.d.ts +3 -0
  15. package/dist/modules/data/DataCollection.d.ts +45 -0
  16. package/dist/modules/data/DataColumn.d.ts +15 -0
  17. package/dist/modules/data/index.d.ts +25 -0
  18. package/dist/modules/data/utils.d.ts +3 -0
  19. package/dist/modules/extensions.d.ts +22 -0
  20. package/dist/modules/plugin-api/HardwareAPI.d.ts +15 -0
  21. package/dist/modules/plugin-api/KeyboardListenerAPI.d.ts +34 -0
  22. package/dist/modules/plugin-api/MediaAPI.d.ts +27 -0
  23. package/dist/modules/plugin-api/SimulationAPI.d.ts +41 -0
  24. package/dist/modules/plugin-api/TimeoutAPI.d.ts +5 -0
  25. package/dist/modules/plugin-api/index.d.ts +8 -0
  26. package/dist/modules/plugins.d.ts +129 -0
  27. package/dist/modules/randomization.d.ts +35 -0
  28. package/dist/modules/turk.d.ts +40 -0
  29. package/dist/modules/utils.d.ts +7 -0
  30. package/package.json +32 -15
  31. package/src/JsPsych.ts +884 -0
  32. package/src/TimelineNode.ts +536 -0
  33. package/src/index.ts +71 -0
  34. package/src/migration.ts +37 -0
  35. package/src/modules/data/DataCollection.ts +198 -0
  36. package/src/modules/data/DataColumn.ts +86 -0
  37. package/src/modules/data/index.ts +174 -0
  38. package/src/modules/data/utils.ts +75 -0
  39. package/src/modules/extensions.ts +23 -0
  40. package/src/modules/plugin-api/HardwareAPI.ts +32 -0
  41. package/src/modules/plugin-api/KeyboardListenerAPI.ts +165 -0
  42. package/src/modules/plugin-api/MediaAPI.ts +337 -0
  43. package/src/modules/plugin-api/SimulationAPI.ts +181 -0
  44. package/src/modules/plugin-api/TimeoutAPI.ts +16 -0
  45. package/src/modules/plugin-api/index.ts +28 -0
  46. package/src/modules/plugins.ts +158 -0
  47. package/src/modules/randomization.ts +327 -0
  48. package/src/modules/turk.ts +99 -0
  49. package/src/modules/utils.ts +30 -0
  50. package/.github/workflows/jest.yml +0 -20
  51. package/code-of-conduct.md +0 -56
  52. package/contributors.md +0 -61
  53. package/docs/CNAME +0 -1
  54. package/docs/about/about.md +0 -18
  55. package/docs/about/contributing.md +0 -43
  56. package/docs/about/license.md +0 -25
  57. package/docs/about/support.md +0 -7
  58. package/docs/core_library/jspsych-core.md +0 -734
  59. package/docs/core_library/jspsych-data.md +0 -589
  60. package/docs/core_library/jspsych-pluginAPI.md +0 -610
  61. package/docs/core_library/jspsych-randomization.md +0 -397
  62. package/docs/core_library/jspsych-turk.md +0 -102
  63. package/docs/extensions/extensions.md +0 -83
  64. package/docs/extensions/jspsych-ext-webgazer.md +0 -106
  65. package/docs/img/blue.png +0 -0
  66. package/docs/img/devtools-change-css.png +0 -0
  67. package/docs/img/devtools-css-errors.png +0 -0
  68. package/docs/img/devtools-inspect-element.png +0 -0
  69. package/docs/img/folder-setup.png +0 -0
  70. package/docs/img/folder-with-html.png +0 -0
  71. package/docs/img/githubreleases.jpg +0 -0
  72. package/docs/img/jspsych-favicon.png +0 -0
  73. package/docs/img/jspsych-logo-no-text-mono.svg +0 -493
  74. package/docs/img/jspsych-logo.jpg +0 -0
  75. package/docs/img/orange.png +0 -0
  76. package/docs/img/palmer_stim.png +0 -0
  77. package/docs/img/progress_bar.png +0 -0
  78. package/docs/img/prolific-study-completion.png +0 -0
  79. package/docs/img/prolific-study-link.png +0 -0
  80. package/docs/img/visual_search_example.jpg +0 -0
  81. package/docs/index.md +0 -9
  82. package/docs/overview/browser-device-support.md +0 -35
  83. package/docs/overview/callbacks.md +0 -184
  84. package/docs/overview/data.md +0 -281
  85. package/docs/overview/dynamic-parameters.md +0 -147
  86. package/docs/overview/exclude-browser.md +0 -32
  87. package/docs/overview/experiment-options.md +0 -149
  88. package/docs/overview/eye-tracking.md +0 -237
  89. package/docs/overview/fullscreen.md +0 -36
  90. package/docs/overview/media-preloading.md +0 -369
  91. package/docs/overview/mturk.md +0 -77
  92. package/docs/overview/plugins.md +0 -320
  93. package/docs/overview/progress-bar.md +0 -110
  94. package/docs/overview/prolific.md +0 -78
  95. package/docs/overview/record-browser-interactions.md +0 -23
  96. package/docs/overview/running-experiments.md +0 -95
  97. package/docs/overview/style.md +0 -293
  98. package/docs/overview/timeline.md +0 -457
  99. package/docs/plugins/jspsych-animation.md +0 -40
  100. package/docs/plugins/jspsych-audio-button-response.md +0 -60
  101. package/docs/plugins/jspsych-audio-keyboard-response.md +0 -58
  102. package/docs/plugins/jspsych-audio-slider-response.md +0 -53
  103. package/docs/plugins/jspsych-call-function.md +0 -81
  104. package/docs/plugins/jspsych-canvas-button-response.md +0 -66
  105. package/docs/plugins/jspsych-canvas-keyboard-response.md +0 -68
  106. package/docs/plugins/jspsych-canvas-slider-response.md +0 -89
  107. package/docs/plugins/jspsych-categorize-animation.md +0 -60
  108. package/docs/plugins/jspsych-categorize-html.md +0 -53
  109. package/docs/plugins/jspsych-categorize-image.md +0 -53
  110. package/docs/plugins/jspsych-cloze.md +0 -45
  111. package/docs/plugins/jspsych-external-html.md +0 -70
  112. package/docs/plugins/jspsych-free-sort.md +0 -56
  113. package/docs/plugins/jspsych-fullscreen.md +0 -57
  114. package/docs/plugins/jspsych-html-button-response.md +0 -42
  115. package/docs/plugins/jspsych-html-keyboard-response.md +0 -51
  116. package/docs/plugins/jspsych-html-slider-response.md +0 -45
  117. package/docs/plugins/jspsych-iat-html.md +0 -64
  118. package/docs/plugins/jspsych-iat-image.md +0 -64
  119. package/docs/plugins/jspsych-image-button-response.md +0 -48
  120. package/docs/plugins/jspsych-image-keyboard-response.md +0 -58
  121. package/docs/plugins/jspsych-image-slider-response.md +0 -54
  122. package/docs/plugins/jspsych-instructions.md +0 -58
  123. package/docs/plugins/jspsych-maxdiff.md +0 -41
  124. package/docs/plugins/jspsych-preload.md +0 -128
  125. package/docs/plugins/jspsych-rdk.md +0 -119
  126. package/docs/plugins/jspsych-reconstruction.md +0 -48
  127. package/docs/plugins/jspsych-resize.md +0 -39
  128. package/docs/plugins/jspsych-same-different-html.md +0 -53
  129. package/docs/plugins/jspsych-same-different-image.md +0 -66
  130. package/docs/plugins/jspsych-serial-reaction-time-mouse.md +0 -52
  131. package/docs/plugins/jspsych-serial-reaction-time.md +0 -57
  132. package/docs/plugins/jspsych-survey-html-form.md +0 -50
  133. package/docs/plugins/jspsych-survey-likert.md +0 -70
  134. package/docs/plugins/jspsych-survey-multi-choice.md +0 -48
  135. package/docs/plugins/jspsych-survey-multi-select.md +0 -53
  136. package/docs/plugins/jspsych-survey-text.md +0 -63
  137. package/docs/plugins/jspsych-video-button-response.md +0 -54
  138. package/docs/plugins/jspsych-video-keyboard-response.md +0 -50
  139. package/docs/plugins/jspsych-video-slider-response.md +0 -60
  140. package/docs/plugins/jspsych-virtual-chinrest.md +0 -105
  141. package/docs/plugins/jspsych-visual-search-circle.md +0 -52
  142. package/docs/plugins/jspsych-vsl-animate-occlusion.md +0 -55
  143. package/docs/plugins/jspsych-vsl-grid-scene.md +0 -62
  144. package/docs/plugins/jspsych-webgazer-calibrate.md +0 -60
  145. package/docs/plugins/jspsych-webgazer-init-camera.md +0 -31
  146. package/docs/plugins/jspsych-webgazer-validate.md +0 -43
  147. package/docs/plugins/list-of-plugins.md +0 -54
  148. package/docs/tutorials/hello-world.md +0 -162
  149. package/docs/tutorials/rt-task.md +0 -1334
  150. package/docs/tutorials/video-tutorials.md +0 -11
  151. package/examples/add-to-end-of-timeline.html +0 -38
  152. package/examples/case-sensitive-responses.html +0 -45
  153. package/examples/conditional-and-loop-functions.html +0 -64
  154. package/examples/css/jquery-ui.css +0 -1225
  155. package/examples/css-classes-parameter.html +0 -145
  156. package/examples/data-add-properties.html +0 -44
  157. package/examples/data-as-function.html +0 -39
  158. package/examples/data-from-timeline.html +0 -52
  159. package/examples/data-from-url.html +0 -21
  160. package/examples/demo-flanker.html +0 -117
  161. package/examples/demo-simple-rt-task.html +0 -120
  162. package/examples/demos/demo_1.html +0 -35
  163. package/examples/demos/demo_2.html +0 -50
  164. package/examples/demos/demo_3.html +0 -63
  165. package/examples/display-element-to-embed-experiment.html +0 -79
  166. package/examples/end-active-node.html +0 -52
  167. package/examples/end-experiment.html +0 -45
  168. package/examples/exclusions.html +0 -32
  169. package/examples/external_html/simple_consent.html +0 -4
  170. package/examples/img/1.gif +0 -0
  171. package/examples/img/10.gif +0 -0
  172. package/examples/img/11.gif +0 -0
  173. package/examples/img/12.gif +0 -0
  174. package/examples/img/2.gif +0 -0
  175. package/examples/img/3.gif +0 -0
  176. package/examples/img/4.gif +0 -0
  177. package/examples/img/5.gif +0 -0
  178. package/examples/img/6.gif +0 -0
  179. package/examples/img/7.gif +0 -0
  180. package/examples/img/8.gif +0 -0
  181. package/examples/img/9.gif +0 -0
  182. package/examples/img/age/of1.jpg +0 -0
  183. package/examples/img/age/of2.jpg +0 -0
  184. package/examples/img/age/of3.jpg +0 -0
  185. package/examples/img/age/om1.jpg +0 -0
  186. package/examples/img/age/om2.jpg +0 -0
  187. package/examples/img/age/om3.jpg +0 -0
  188. package/examples/img/age/yf1.jpg +0 -0
  189. package/examples/img/age/yf4.jpg +0 -0
  190. package/examples/img/age/yf5.jpg +0 -0
  191. package/examples/img/age/ym2.jpg +0 -0
  192. package/examples/img/age/ym3.jpg +0 -0
  193. package/examples/img/age/ym5.jpg +0 -0
  194. package/examples/img/backwardN.gif +0 -0
  195. package/examples/img/blue.png +0 -0
  196. package/examples/img/card.png +0 -0
  197. package/examples/img/con1.png +0 -0
  198. package/examples/img/con2.png +0 -0
  199. package/examples/img/fixation.gif +0 -0
  200. package/examples/img/happy_face_1.jpg +0 -0
  201. package/examples/img/happy_face_2.jpg +0 -0
  202. package/examples/img/happy_face_3.jpg +0 -0
  203. package/examples/img/happy_face_4.jpg +0 -0
  204. package/examples/img/inc1.png +0 -0
  205. package/examples/img/inc2.png +0 -0
  206. package/examples/img/normalN.gif +0 -0
  207. package/examples/img/orange.png +0 -0
  208. package/examples/img/redX.png +0 -0
  209. package/examples/img/ribbon.jpg +0 -0
  210. package/examples/img/sad_face_1.jpg +0 -0
  211. package/examples/img/sad_face_2.jpg +0 -0
  212. package/examples/img/sad_face_3.jpg +0 -0
  213. package/examples/img/sad_face_4.jpg +0 -0
  214. package/examples/js/snap.svg-min.js +0 -21
  215. package/examples/js/webgazer.js +0 -88886
  216. package/examples/jspsych-RDK.html +0 -58
  217. package/examples/jspsych-animation.html +0 -39
  218. package/examples/jspsych-audio-button-response.html +0 -58
  219. package/examples/jspsych-audio-keyboard-response.html +0 -68
  220. package/examples/jspsych-audio-slider-response.html +0 -61
  221. package/examples/jspsych-call-function.html +0 -32
  222. package/examples/jspsych-canvas-button-response.html +0 -95
  223. package/examples/jspsych-canvas-keyboard-response.html +0 -78
  224. package/examples/jspsych-canvas-slider-response.html +0 -67
  225. package/examples/jspsych-categorize-animation.html +0 -49
  226. package/examples/jspsych-categorize-html.html +0 -33
  227. package/examples/jspsych-categorize-image.html +0 -44
  228. package/examples/jspsych-cloze.html +0 -37
  229. package/examples/jspsych-free-sort.html +0 -109
  230. package/examples/jspsych-fullscreen.html +0 -45
  231. package/examples/jspsych-html-button-response.html +0 -43
  232. package/examples/jspsych-html-keyboard-response.html +0 -42
  233. package/examples/jspsych-html-slider-response.html +0 -53
  234. package/examples/jspsych-iat.html +0 -520
  235. package/examples/jspsych-image-button-response.html +0 -91
  236. package/examples/jspsych-image-keyboard-response.html +0 -85
  237. package/examples/jspsych-image-slider-response.html +0 -85
  238. package/examples/jspsych-instructions.html +0 -37
  239. package/examples/jspsych-maxdiff.html +0 -33
  240. package/examples/jspsych-preload.html +0 -140
  241. package/examples/jspsych-reconstruction.html +0 -43
  242. package/examples/jspsych-resize.html +0 -34
  243. package/examples/jspsych-same-different-html.html +0 -28
  244. package/examples/jspsych-same-different-image.html +0 -39
  245. package/examples/jspsych-serial-reaction-time-mouse.html +0 -98
  246. package/examples/jspsych-serial-reaction-time.html +0 -54
  247. package/examples/jspsych-survey-html-form.html +0 -33
  248. package/examples/jspsych-survey-likert.html +0 -42
  249. package/examples/jspsych-survey-multi-choice.html +0 -40
  250. package/examples/jspsych-survey-multi-select.html +0 -42
  251. package/examples/jspsych-survey-text.html +0 -34
  252. package/examples/jspsych-video-button-response.html +0 -65
  253. package/examples/jspsych-video-keyboard-response.html +0 -61
  254. package/examples/jspsych-video-slider-response.html +0 -63
  255. package/examples/jspsych-virtual-chinrest.html +0 -69
  256. package/examples/jspsych-visual-search-circle.html +0 -64
  257. package/examples/jspsych-vsl-animate-occlusion.html +0 -35
  258. package/examples/jspsych-vsl-grid-scene.html +0 -47
  259. package/examples/lexical-decision.html +0 -134
  260. package/examples/manual-preloading.html +0 -59
  261. package/examples/pause-unpause.html +0 -33
  262. package/examples/progress-bar.html +0 -68
  263. package/examples/save-trial-parameters.html +0 -98
  264. package/examples/sound/hammer.mp3 +0 -0
  265. package/examples/sound/sound.mp3 +0 -0
  266. package/examples/sound/speech_blue.mp3 +0 -0
  267. package/examples/sound/speech_green.mp3 +0 -0
  268. package/examples/sound/speech_joke.mp3 +0 -0
  269. package/examples/sound/speech_red.mp3 +0 -0
  270. package/examples/sound/tone.mp3 +0 -0
  271. package/examples/timeline-variables-sampling.html +0 -50
  272. package/examples/timeline-variables.html +0 -64
  273. package/examples/video/sample_video.mp4 +0 -0
  274. package/examples/webgazer.html +0 -162
  275. package/examples/webgazer_image.html +0 -60
  276. package/extensions/jspsych-ext-webgazer.js +0 -185
  277. package/jspsych.js +0 -3015
  278. package/license.txt +0 -21
  279. package/mkdocs.yml +0 -118
  280. package/plugins/jspsych-animation.js +0 -189
  281. package/plugins/jspsych-audio-button-response.js +0 -269
  282. package/plugins/jspsych-audio-keyboard-response.js +0 -212
  283. package/plugins/jspsych-audio-slider-response.js +0 -278
  284. package/plugins/jspsych-call-function.js +0 -58
  285. package/plugins/jspsych-canvas-button-response.js +0 -199
  286. package/plugins/jspsych-canvas-keyboard-response.js +0 -155
  287. package/plugins/jspsych-canvas-slider-response.js +0 -207
  288. package/plugins/jspsych-categorize-animation.js +0 -266
  289. package/plugins/jspsych-categorize-html.js +0 -220
  290. package/plugins/jspsych-categorize-image.js +0 -222
  291. package/plugins/jspsych-cloze.js +0 -112
  292. package/plugins/jspsych-external-html.js +0 -112
  293. package/plugins/jspsych-free-sort.js +0 -478
  294. package/plugins/jspsych-fullscreen.js +0 -106
  295. package/plugins/jspsych-html-button-response.js +0 -188
  296. package/plugins/jspsych-html-keyboard-response.js +0 -149
  297. package/plugins/jspsych-html-slider-response.js +0 -202
  298. package/plugins/jspsych-iat-html.js +0 -284
  299. package/plugins/jspsych-iat-image.js +0 -286
  300. package/plugins/jspsych-image-button-response.js +0 -327
  301. package/plugins/jspsych-image-keyboard-response.js +0 -263
  302. package/plugins/jspsych-image-slider-response.js +0 -369
  303. package/plugins/jspsych-instructions.js +0 -237
  304. package/plugins/jspsych-maxdiff.js +0 -173
  305. package/plugins/jspsych-preload.js +0 -345
  306. package/plugins/jspsych-rdk.js +0 -1373
  307. package/plugins/jspsych-reconstruction.js +0 -134
  308. package/plugins/jspsych-resize.js +0 -166
  309. package/plugins/jspsych-same-different-html.js +0 -168
  310. package/plugins/jspsych-same-different-image.js +0 -169
  311. package/plugins/jspsych-serial-reaction-time-mouse.js +0 -212
  312. package/plugins/jspsych-serial-reaction-time.js +0 -247
  313. package/plugins/jspsych-survey-html-form.js +0 -171
  314. package/plugins/jspsych-survey-likert.js +0 -195
  315. package/plugins/jspsych-survey-multi-choice.js +0 -208
  316. package/plugins/jspsych-survey-multi-select.js +0 -232
  317. package/plugins/jspsych-survey-text.js +0 -185
  318. package/plugins/jspsych-video-button-response.js +0 -335
  319. package/plugins/jspsych-video-keyboard-response.js +0 -279
  320. package/plugins/jspsych-video-slider-response.js +0 -351
  321. package/plugins/jspsych-virtual-chinrest.js +0 -471
  322. package/plugins/jspsych-visual-search-circle.js +0 -259
  323. package/plugins/jspsych-vsl-animate-occlusion.js +0 -196
  324. package/plugins/jspsych-vsl-grid-scene.js +0 -103
  325. package/plugins/jspsych-webgazer-calibrate.js +0 -166
  326. package/plugins/jspsych-webgazer-init-camera.js +0 -95
  327. package/plugins/jspsych-webgazer-validate.js +0 -304
  328. package/plugins/template/jspsych-plugin-template.js +0 -35
  329. package/tests/README.md +0 -7
  330. package/tests/jsPsych/case-sensitive-responses.test.js +0 -53
  331. package/tests/jsPsych/css-classes-parameter.test.js +0 -107
  332. package/tests/jsPsych/default-iti.test.js +0 -51
  333. package/tests/jsPsych/default-parameters.test.js +0 -58
  334. package/tests/jsPsych/endexperiment.test.js +0 -49
  335. package/tests/jsPsych/events.test.js +0 -606
  336. package/tests/jsPsych/functions-as-parameters.test.js +0 -210
  337. package/tests/jsPsych/init.test.js +0 -48
  338. package/tests/jsPsych/loads.test.js +0 -7
  339. package/tests/jsPsych/min-rt.test.js +0 -58
  340. package/tests/jsPsych/progressbar.test.js +0 -202
  341. package/tests/jsPsych/timeline-variables.test.js +0 -531
  342. package/tests/jsPsych/timelines.test.js +0 -569
  343. package/tests/jsPsych.data/data-csv-conversion.test.js +0 -85
  344. package/tests/jsPsych.data/data-json-conversion.test.js +0 -120
  345. package/tests/jsPsych.data/datacollection.test.js +0 -117
  346. package/tests/jsPsych.data/datacolumn.test.js +0 -50
  347. package/tests/jsPsych.data/datamodule.test.js +0 -152
  348. package/tests/jsPsych.data/dataparameter.test.js +0 -251
  349. package/tests/jsPsych.data/interactions.test.js +0 -109
  350. package/tests/jsPsych.data/trialparameters.test.js +0 -175
  351. package/tests/jsPsych.extensions/extensions.test.js +0 -207
  352. package/tests/jsPsych.extensions/test-extension.js +0 -42
  353. package/tests/jsPsych.pluginAPI/pluginapi.test.js +0 -341
  354. package/tests/jsPsych.pluginAPI/preloads.test.js +0 -43
  355. package/tests/jsPsych.randomization/randomziation.test.js +0 -27
  356. package/tests/jsPsych.utils/utils.test.js +0 -58
  357. package/tests/plugins/plugin-animation.test.js +0 -34
  358. package/tests/plugins/plugin-audio-button-response.test.js +0 -15
  359. package/tests/plugins/plugin-audio-keyboard-response.test.js +0 -15
  360. package/tests/plugins/plugin-audio-slider-response.test.js +0 -15
  361. package/tests/plugins/plugin-call-function.test.js +0 -49
  362. package/tests/plugins/plugin-categorize-animation.test.js +0 -263
  363. package/tests/plugins/plugin-categorize-html.test.js +0 -17
  364. package/tests/plugins/plugin-categorize-image.test.js +0 -17
  365. package/tests/plugins/plugin-cloze.test.js +0 -157
  366. package/tests/plugins/plugin-free-sort.test.js +0 -106
  367. package/tests/plugins/plugin-fullscreen.test.js +0 -41
  368. package/tests/plugins/plugin-html-button-response.test.js +0 -161
  369. package/tests/plugins/plugin-html-keyboard-response.test.js +0 -139
  370. package/tests/plugins/plugin-html-slider-response.test.js +0 -155
  371. package/tests/plugins/plugin-iat-html.test.js +0 -299
  372. package/tests/plugins/plugin-iat-image.test.js +0 -298
  373. package/tests/plugins/plugin-image-button-response.test.js +0 -174
  374. package/tests/plugins/plugin-image-keyboard-response.test.js +0 -147
  375. package/tests/plugins/plugin-image-slider-response.test.js +0 -174
  376. package/tests/plugins/plugin-instructions.test.js +0 -85
  377. package/tests/plugins/plugin-maxdiff.test.js +0 -39
  378. package/tests/plugins/plugin-preload.test.js +0 -916
  379. package/tests/plugins/plugin-rdk.test.js +0 -61
  380. package/tests/plugins/plugin-reconstruction.test.js +0 -16
  381. package/tests/plugins/plugin-resize.test.js +0 -16
  382. package/tests/plugins/plugin-same-different-html.test.js +0 -17
  383. package/tests/plugins/plugin-same-different-image.test.js +0 -17
  384. package/tests/plugins/plugin-serial-reaction-time-mouse.test.js +0 -42
  385. package/tests/plugins/plugin-serial-reaction-time.test.js +0 -109
  386. package/tests/plugins/plugin-survey-html-form.test.js +0 -44
  387. package/tests/plugins/plugin-survey-likert.test.js +0 -48
  388. package/tests/plugins/plugin-survey-multi-choice.test.js +0 -47
  389. package/tests/plugins/plugin-survey-multi-select.test.js +0 -71
  390. package/tests/plugins/plugin-survey-text.test.js +0 -115
  391. package/tests/plugins/plugin-video-button-response.test.js +0 -32
  392. package/tests/plugins/plugin-video-keyboard-response.test.js +0 -32
  393. package/tests/plugins/plugin-video-slider-response.test.js +0 -31
  394. package/tests/plugins/plugin-visual-search-circle.test.js +0 -16
  395. package/tests/plugins/plugin-vsl-animate-occlusion.test.js +0 -16
  396. package/tests/plugins/plugin-vsl-grid-scene.test.js +0 -16
  397. package/tests/testing-utils.js +0 -13
@@ -0,0 +1,165 @@
1
+ import autoBind from "auto-bind";
2
+
3
+ export type KeyboardListener = (e: KeyboardEvent) => void;
4
+
5
+ export type ValidResponses = string[] | "ALL_KEYS" | "NO_KEYS";
6
+
7
+ export interface GetKeyboardResponseOptions {
8
+ callback_function: any;
9
+ valid_responses?: ValidResponses;
10
+ rt_method?: "performance" | "audio";
11
+ persist?: boolean;
12
+ audio_context?: AudioContext;
13
+ audio_context_start_time?: number;
14
+ allow_held_key?: boolean;
15
+ minimum_valid_rt?: number;
16
+ }
17
+
18
+ export class KeyboardListenerAPI {
19
+ constructor(
20
+ private getRootElement: () => Element | undefined,
21
+ private areResponsesCaseSensitive: boolean = false,
22
+ private minimumValidRt = 0
23
+ ) {
24
+ autoBind(this);
25
+ this.registerRootListeners();
26
+ }
27
+
28
+ private listeners = new Set<KeyboardListener>();
29
+ private heldKeys = new Set<string>();
30
+
31
+ private areRootListenersRegistered = false;
32
+
33
+ /**
34
+ * If not previously done and `this.getRootElement()` returns an element, adds the root key
35
+ * listeners to that element.
36
+ */
37
+ private registerRootListeners() {
38
+ if (!this.areRootListenersRegistered) {
39
+ const rootElement = this.getRootElement();
40
+ if (rootElement) {
41
+ rootElement.addEventListener("keydown", this.rootKeydownListener);
42
+ rootElement.addEventListener("keyup", this.rootKeyupListener);
43
+ this.areRootListenersRegistered = true;
44
+ }
45
+ }
46
+ }
47
+
48
+ private rootKeydownListener(e: KeyboardEvent) {
49
+ // Iterate over a static copy of the listeners set because listeners might add other listeners
50
+ // that we do not want to be included in the loop
51
+ for (const listener of Array.from(this.listeners)) {
52
+ listener(e);
53
+ }
54
+ this.heldKeys.add(this.toLowerCaseIfInsensitive(e.key));
55
+ }
56
+
57
+ private toLowerCaseIfInsensitive(string: string) {
58
+ return this.areResponsesCaseSensitive ? string : string.toLowerCase();
59
+ }
60
+
61
+ private rootKeyupListener(e: KeyboardEvent) {
62
+ this.heldKeys.delete(this.toLowerCaseIfInsensitive(e.key));
63
+ }
64
+
65
+ private isResponseValid(validResponses: ValidResponses, allowHeldKey: boolean, key: string) {
66
+ // check if key was already held down
67
+ if (!allowHeldKey && this.heldKeys.has(key)) {
68
+ return false;
69
+ }
70
+
71
+ if (validResponses === "ALL_KEYS") {
72
+ return true;
73
+ }
74
+ if (validResponses === "NO_KEYS") {
75
+ return false;
76
+ }
77
+
78
+ return validResponses.includes(key);
79
+ }
80
+
81
+ getKeyboardResponse({
82
+ callback_function,
83
+ valid_responses = "ALL_KEYS",
84
+ rt_method = "performance",
85
+ persist,
86
+ audio_context,
87
+ audio_context_start_time,
88
+ allow_held_key = false,
89
+ minimum_valid_rt = this.minimumValidRt,
90
+ }: GetKeyboardResponseOptions) {
91
+ if (rt_method !== "performance" && rt_method !== "audio") {
92
+ console.log(
93
+ 'Invalid RT method specified in getKeyboardResponse. Defaulting to "performance" method.'
94
+ );
95
+ rt_method = "performance";
96
+ }
97
+
98
+ const usePerformanceRt = rt_method === "performance";
99
+ const startTime = usePerformanceRt ? performance.now() : audio_context_start_time * 1000;
100
+
101
+ this.registerRootListeners();
102
+
103
+ if (!this.areResponsesCaseSensitive && typeof valid_responses !== "string") {
104
+ valid_responses = valid_responses.map((r) => r.toLowerCase());
105
+ }
106
+
107
+ const listener: KeyboardListener = (e) => {
108
+ const rt = Math.round(
109
+ (rt_method == "performance" ? performance.now() : audio_context.currentTime * 1000) -
110
+ startTime
111
+ );
112
+ if (rt < minimum_valid_rt) {
113
+ return;
114
+ }
115
+
116
+ const key = this.toLowerCaseIfInsensitive(e.key);
117
+
118
+ if (this.isResponseValid(valid_responses, allow_held_key, key)) {
119
+ // if this is a valid response, then we don't want the key event to trigger other actions
120
+ // like scrolling via the spacebar.
121
+ e.preventDefault();
122
+
123
+ if (!persist) {
124
+ // remove keyboard listener if it exists
125
+ this.cancelKeyboardResponse(listener);
126
+ }
127
+
128
+ callback_function({ key, rt });
129
+ }
130
+ };
131
+
132
+ this.listeners.add(listener);
133
+ return listener;
134
+ }
135
+
136
+ cancelKeyboardResponse(listener: KeyboardListener) {
137
+ // remove the listener from the set of listeners if it is contained
138
+ this.listeners.delete(listener);
139
+ }
140
+
141
+ cancelAllKeyboardResponses() {
142
+ this.listeners.clear();
143
+ }
144
+
145
+ compareKeys(key1: string | null, key2: string | null) {
146
+ if (
147
+ (typeof key1 !== "string" && key1 !== null) ||
148
+ (typeof key2 !== "string" && key2 !== null)
149
+ ) {
150
+ console.error(
151
+ "Error in jsPsych.pluginAPI.compareKeys: arguments must be key strings or null."
152
+ );
153
+ return undefined;
154
+ }
155
+
156
+ if (typeof key1 === "string" && typeof key2 === "string") {
157
+ // if both values are strings, then check whether or not letter case should be converted before comparing (case_sensitive_responses in initJsPsych)
158
+ return this.areResponsesCaseSensitive
159
+ ? key1 === key2
160
+ : key1.toLowerCase() === key2.toLowerCase();
161
+ }
162
+
163
+ return key1 === null && key2 === null;
164
+ }
165
+ }
@@ -0,0 +1,337 @@
1
+ import { ParameterType } from "../../modules/plugins";
2
+ import { unique } from "../utils";
3
+
4
+ const preloadParameterTypes = <const>[
5
+ ParameterType.AUDIO,
6
+ ParameterType.IMAGE,
7
+ ParameterType.VIDEO,
8
+ ];
9
+ type PreloadType = typeof preloadParameterTypes[number];
10
+
11
+ export class MediaAPI {
12
+ constructor(private useWebaudio: boolean, private webaudioContext?: AudioContext) {}
13
+
14
+ // video //
15
+ private video_buffers = {};
16
+ getVideoBuffer(videoID) {
17
+ return this.video_buffers[videoID];
18
+ }
19
+
20
+ // audio //
21
+ private context = null;
22
+ private audio_buffers = [];
23
+
24
+ initAudio() {
25
+ this.context = this.useWebaudio ? this.webaudioContext : null;
26
+ }
27
+
28
+ audioContext() {
29
+ if (this.context !== null) {
30
+ if (this.context.state !== "running") {
31
+ this.context.resume();
32
+ }
33
+ }
34
+ return this.context;
35
+ }
36
+
37
+ getAudioBuffer(audioID) {
38
+ return new Promise((resolve, reject) => {
39
+ // check whether audio file already preloaded
40
+ if (
41
+ typeof this.audio_buffers[audioID] == "undefined" ||
42
+ this.audio_buffers[audioID] == "tmp"
43
+ ) {
44
+ // if audio is not already loaded, try to load it
45
+ this.preloadAudio(
46
+ [audioID],
47
+ () => {
48
+ resolve(this.audio_buffers[audioID]);
49
+ },
50
+ () => {},
51
+ (e) => {
52
+ reject(e.error);
53
+ }
54
+ );
55
+ } else {
56
+ // audio is already loaded
57
+ resolve(this.audio_buffers[audioID]);
58
+ }
59
+ });
60
+ }
61
+
62
+ // preloading stimuli //
63
+ private preload_requests = [];
64
+
65
+ private img_cache = {};
66
+
67
+ preloadAudio(
68
+ files,
69
+ callback_complete = () => {},
70
+ callback_load = (filepath) => {},
71
+ callback_error = (error_msg) => {}
72
+ ) {
73
+ files = unique(files.flat());
74
+
75
+ let n_loaded = 0;
76
+
77
+ if (files.length == 0) {
78
+ callback_complete();
79
+ return;
80
+ }
81
+
82
+ const load_audio_file_webaudio = (source, count = 1) => {
83
+ const request = new XMLHttpRequest();
84
+ request.open("GET", source, true);
85
+ request.responseType = "arraybuffer";
86
+ request.onload = () => {
87
+ this.context.decodeAudioData(
88
+ request.response,
89
+ (buffer) => {
90
+ this.audio_buffers[source] = buffer;
91
+ n_loaded++;
92
+ callback_load(source);
93
+ if (n_loaded == files.length) {
94
+ callback_complete();
95
+ }
96
+ },
97
+ (e) => {
98
+ callback_error({ source: source, error: e });
99
+ }
100
+ );
101
+ };
102
+ request.onerror = function (e) {
103
+ let err: ProgressEvent | string = e;
104
+ if (this.status == 404) {
105
+ err = "404";
106
+ }
107
+ callback_error({ source: source, error: err });
108
+ };
109
+ request.onloadend = function (e) {
110
+ if (this.status == 404) {
111
+ callback_error({ source: source, error: "404" });
112
+ }
113
+ };
114
+ request.send();
115
+ this.preload_requests.push(request);
116
+ };
117
+
118
+ const load_audio_file_html5audio = (source, count = 1) => {
119
+ const audio = new Audio();
120
+ const handleCanPlayThrough = () => {
121
+ this.audio_buffers[source] = audio;
122
+ n_loaded++;
123
+ callback_load(source);
124
+ if (n_loaded == files.length) {
125
+ callback_complete();
126
+ }
127
+ audio.removeEventListener("canplaythrough", handleCanPlayThrough);
128
+ };
129
+ audio.addEventListener("canplaythrough", handleCanPlayThrough);
130
+ audio.addEventListener("error", function handleError(e) {
131
+ callback_error({ source: audio.src, error: e });
132
+ audio.removeEventListener("error", handleError);
133
+ });
134
+ audio.addEventListener("abort", function handleAbort(e) {
135
+ callback_error({ source: audio.src, error: e });
136
+ audio.removeEventListener("abort", handleAbort);
137
+ });
138
+ audio.src = source;
139
+ this.preload_requests.push(audio);
140
+ };
141
+
142
+ for (const file of files) {
143
+ if (typeof this.audio_buffers[file] !== "undefined") {
144
+ n_loaded++;
145
+ callback_load(file);
146
+ if (n_loaded == files.length) {
147
+ callback_complete();
148
+ }
149
+ } else {
150
+ this.audio_buffers[file] = "tmp";
151
+ if (this.audioContext() !== null) {
152
+ load_audio_file_webaudio(file);
153
+ } else {
154
+ load_audio_file_html5audio(file);
155
+ }
156
+ }
157
+ }
158
+ }
159
+
160
+ preloadImages(
161
+ images,
162
+ callback_complete = () => {},
163
+ callback_load = (filepath) => {},
164
+ callback_error = (error_msg) => {}
165
+ ) {
166
+ // flatten the images array
167
+ images = unique(images.flat());
168
+
169
+ var n_loaded = 0;
170
+
171
+ if (images.length === 0) {
172
+ callback_complete();
173
+ return;
174
+ }
175
+
176
+ for (var i = 0; i < images.length; i++) {
177
+ var img = new Image();
178
+
179
+ img.onload = function () {
180
+ n_loaded++;
181
+ callback_load(img.src);
182
+ if (n_loaded === images.length) {
183
+ callback_complete();
184
+ }
185
+ };
186
+
187
+ img.onerror = function (e) {
188
+ callback_error({ source: img.src, error: e });
189
+ };
190
+
191
+ img.src = images[i];
192
+
193
+ this.img_cache[images[i]] = img;
194
+ this.preload_requests.push(img);
195
+ }
196
+ }
197
+
198
+ preloadVideo(
199
+ videos,
200
+ callback_complete = () => {},
201
+ callback_load = (filepath) => {},
202
+ callback_error = (error_msg) => {}
203
+ ) {
204
+ // flatten the video array
205
+ videos = unique(videos.flat());
206
+
207
+ let n_loaded = 0;
208
+
209
+ if (videos.length === 0) {
210
+ callback_complete();
211
+ return;
212
+ }
213
+
214
+ for (const video of videos) {
215
+ const video_buffers = this.video_buffers;
216
+
217
+ //based on option 4 here: http://dinbror.dk/blog/how-to-preload-entire-html5-video-before-play-solved/
218
+ const request = new XMLHttpRequest();
219
+ request.open("GET", video, true);
220
+ request.responseType = "blob";
221
+ request.onload = function () {
222
+ if (this.status === 200 || this.status === 0) {
223
+ const videoBlob = this.response;
224
+ video_buffers[video] = URL.createObjectURL(videoBlob); // IE10+
225
+ n_loaded++;
226
+ callback_load(video);
227
+ if (n_loaded === videos.length) {
228
+ callback_complete();
229
+ }
230
+ }
231
+ };
232
+ request.onerror = function (e) {
233
+ let err: ProgressEvent | string = e;
234
+ if (this.status == 404) {
235
+ err = "404";
236
+ }
237
+ callback_error({ source: video, error: err });
238
+ };
239
+ request.onloadend = function (e) {
240
+ if (this.status == 404) {
241
+ callback_error({ source: video, error: "404" });
242
+ }
243
+ };
244
+ request.send();
245
+ this.preload_requests.push(request);
246
+ }
247
+ }
248
+
249
+ private preloadMap = new Map<string, Record<string, PreloadType>>();
250
+
251
+ getAutoPreloadList(timeline_description: any[]) {
252
+ /** Map each preload parameter type to a set of paths to be preloaded */
253
+ const preloadPaths = Object.fromEntries(
254
+ preloadParameterTypes.map((type) => [type, new Set<string>()])
255
+ );
256
+
257
+ const traverseTimeline = (node, inheritedTrialType?) => {
258
+ const isTimeline = typeof node.timeline !== "undefined";
259
+
260
+ if (isTimeline) {
261
+ for (const childNode of node.timeline) {
262
+ traverseTimeline(childNode, node.type ?? inheritedTrialType);
263
+ }
264
+ } else if ((node.type ?? inheritedTrialType)?.info) {
265
+ // node is a trial with type.info set
266
+
267
+ // Get the plugin name and parameters object from the info object
268
+ const { name: pluginName, parameters } = (node.type ?? inheritedTrialType).info;
269
+
270
+ // Extract parameters to be preloaded and their types from parameter info if this has not
271
+ // yet been done for `pluginName`
272
+ if (!this.preloadMap.has(pluginName)) {
273
+ this.preloadMap.set(
274
+ pluginName,
275
+ Object.fromEntries(
276
+ Object.entries<any>(parameters)
277
+ // Filter out parameter entries with media types and a non-false `preload` option
278
+ .filter(
279
+ ([_name, { type, preload }]) =>
280
+ preloadParameterTypes.includes(type) && (preload ?? true)
281
+ )
282
+ // Map each entry's value to its parameter type
283
+ .map(([name, { type }]) => [name, type])
284
+ )
285
+ );
286
+ }
287
+
288
+ // Add preload paths from this trial
289
+ for (const [parameterName, parameterType] of Object.entries(
290
+ this.preloadMap.get(pluginName)
291
+ )) {
292
+ const parameterValue = node[parameterName];
293
+ const elements = preloadPaths[parameterType];
294
+
295
+ if (typeof parameterValue === "string") {
296
+ elements.add(parameterValue);
297
+ } else if (Array.isArray(parameterValue)) {
298
+ for (const element of parameterValue.flat()) {
299
+ if (typeof element === "string") {
300
+ elements.add(element);
301
+ }
302
+ }
303
+ }
304
+ }
305
+ }
306
+ };
307
+
308
+ traverseTimeline({ timeline: timeline_description });
309
+
310
+ return {
311
+ images: [...preloadPaths[ParameterType.IMAGE]],
312
+ audio: [...preloadPaths[ParameterType.AUDIO]],
313
+ video: [...preloadPaths[ParameterType.VIDEO]],
314
+ };
315
+ }
316
+
317
+ cancelPreloads() {
318
+ for (const request of this.preload_requests) {
319
+ request.onload = () => {};
320
+ request.onerror = () => {};
321
+ request.oncanplaythrough = () => {};
322
+ request.onabort = () => {};
323
+ }
324
+ this.preload_requests = [];
325
+ }
326
+
327
+ private microphone_recorder: MediaRecorder = null;
328
+
329
+ initializeMicrophoneRecorder(stream: MediaStream) {
330
+ const recorder = new MediaRecorder(stream);
331
+ this.microphone_recorder = recorder;
332
+ }
333
+
334
+ getMicrophoneRecorder(): MediaRecorder {
335
+ return this.microphone_recorder;
336
+ }
337
+ }
@@ -0,0 +1,181 @@
1
+ export class SimulationAPI {
2
+ dispatchEvent(event: Event) {
3
+ document.body.dispatchEvent(event);
4
+ }
5
+
6
+ /**
7
+ * Dispatches a `keydown` event for the specified key
8
+ * @param key Character code (`.key` property) for the key to press.
9
+ */
10
+ keyDown(key: string) {
11
+ this.dispatchEvent(new KeyboardEvent("keydown", { key }));
12
+ }
13
+
14
+ /**
15
+ * Dispatches a `keyup` event for the specified key
16
+ * @param key Character code (`.key` property) for the key to press.
17
+ */
18
+ keyUp(key: string) {
19
+ this.dispatchEvent(new KeyboardEvent("keyup", { key }));
20
+ }
21
+
22
+ /**
23
+ * Dispatches a `keydown` and `keyup` event in sequence to simulate pressing a key.
24
+ * @param key Character code (`.key` property) for the key to press.
25
+ * @param delay Length of time to wait (ms) before executing action
26
+ */
27
+ pressKey(key: string, delay = 0) {
28
+ if (delay > 0) {
29
+ setTimeout(() => {
30
+ this.keyDown(key);
31
+ this.keyUp(key);
32
+ }, delay);
33
+ } else {
34
+ this.keyDown(key);
35
+ this.keyUp(key);
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Dispatches `mousedown`, `mouseup`, and `click` events on the target element
41
+ * @param target The element to click
42
+ * @param delay Length of time to wait (ms) before executing action
43
+ */
44
+ clickTarget(target: Element, delay = 0) {
45
+ if (delay > 0) {
46
+ setTimeout(() => {
47
+ target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
48
+ target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
49
+ target.dispatchEvent(new MouseEvent("click", { bubbles: true }));
50
+ }, delay);
51
+ } else {
52
+ target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true }));
53
+ target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true }));
54
+ target.dispatchEvent(new MouseEvent("click", { bubbles: true }));
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Sets the value of a target text input
60
+ * @param target A text input element to fill in
61
+ * @param text Text to input
62
+ * @param delay Length of time to wait (ms) before executing action
63
+ */
64
+ fillTextInput(target: HTMLInputElement, text: string, delay = 0) {
65
+ if (delay > 0) {
66
+ setTimeout(() => {
67
+ target.value = text;
68
+ }, delay);
69
+ } else {
70
+ target.value = text;
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Picks a valid key from `choices`, taking into account jsPsych-specific
76
+ * identifiers like "NO_KEYS" and "ALL_KEYS".
77
+ * @param choices Which keys are valid.
78
+ * @returns A key selected at random from the valid keys.
79
+ */
80
+ getValidKey(choices: "NO_KEYS" | "ALL_KEYS" | Array<string> | Array<Array<string>>) {
81
+ const possible_keys = [
82
+ "a",
83
+ "b",
84
+ "c",
85
+ "d",
86
+ "e",
87
+ "f",
88
+ "g",
89
+ "h",
90
+ "i",
91
+ "j",
92
+ "k",
93
+ "l",
94
+ "m",
95
+ "n",
96
+ "o",
97
+ "p",
98
+ "q",
99
+ "r",
100
+ "s",
101
+ "t",
102
+ "u",
103
+ "v",
104
+ "w",
105
+ "x",
106
+ "y",
107
+ "z",
108
+ "0",
109
+ "1",
110
+ "2",
111
+ "3",
112
+ "4",
113
+ "5",
114
+ "6",
115
+ "7",
116
+ "8",
117
+ "9",
118
+ " ",
119
+ ];
120
+
121
+ let key;
122
+ if (choices == "NO_KEYS") {
123
+ key = null;
124
+ } else if (choices == "ALL_KEYS") {
125
+ key = possible_keys[Math.floor(Math.random() * possible_keys.length)];
126
+ } else {
127
+ const flat_choices = choices.flat();
128
+ key = flat_choices[Math.floor(Math.random() * flat_choices.length)];
129
+ }
130
+
131
+ return key;
132
+ }
133
+
134
+ mergeSimulationData(default_data, simulation_options) {
135
+ // override any data with data from simulation object
136
+ return {
137
+ ...default_data,
138
+ ...simulation_options?.data,
139
+ };
140
+ }
141
+
142
+ ensureSimulationDataConsistency(trial, data) {
143
+ // All RTs must be rounded
144
+ if (data.rt) {
145
+ data.rt = Math.round(data.rt);
146
+ }
147
+
148
+ // If a trial_duration and rt exist, make sure that the RT is not longer than the trial.
149
+ if (trial.trial_duration && data.rt && data.rt > trial.trial_duration) {
150
+ data.rt = null;
151
+ if (data.response) {
152
+ data.response = null;
153
+ }
154
+ if (data.correct) {
155
+ data.correct = false;
156
+ }
157
+ }
158
+
159
+ // If trial.choices is NO_KEYS make sure that response and RT are null
160
+ if (trial.choices && trial.choices == "NO_KEYS") {
161
+ if (data.rt) {
162
+ data.rt = null;
163
+ }
164
+ if (data.response) {
165
+ data.response = null;
166
+ }
167
+ }
168
+
169
+ // If response is not allowed before stimulus display complete, ensure RT
170
+ // is longer than display time.
171
+ if (trial.allow_response_before_complete) {
172
+ if (trial.sequence_reps && trial.frame_time) {
173
+ const min_time = trial.sequence_reps * trial.frame_time * trial.stimuli.length;
174
+ if (data.rt < min_time) {
175
+ data.rt = null;
176
+ data.response = null;
177
+ }
178
+ }
179
+ }
180
+ }
181
+ }
@@ -0,0 +1,16 @@
1
+ export class TimeoutAPI {
2
+ private timeout_handlers = [];
3
+
4
+ setTimeout(callback, delay) {
5
+ const handle = window.setTimeout(callback, delay);
6
+ this.timeout_handlers.push(handle);
7
+ return handle;
8
+ }
9
+
10
+ clearAllTimeouts() {
11
+ for (const handler of this.timeout_handlers) {
12
+ clearTimeout(handler);
13
+ }
14
+ this.timeout_handlers = [];
15
+ }
16
+ }