jspsych 6.2.0 → 7.1.0

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 (360) hide show
  1. package/README.md +43 -29
  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 -661
  59. package/docs/core_library/jspsych-data.md +0 -589
  60. package/docs/core_library/jspsych-pluginAPI.md +0 -510
  61. package/docs/core_library/jspsych-randomization.md +0 -397
  62. package/docs/core_library/jspsych-turk.md +0 -102
  63. package/docs/img/blue.png +0 -0
  64. package/docs/img/folder-setup.png +0 -0
  65. package/docs/img/folder-with-html.png +0 -0
  66. package/docs/img/githubreleases.jpg +0 -0
  67. package/docs/img/jspsych-favicon.png +0 -0
  68. package/docs/img/jspsych-logo-no-text-mono.svg +0 -493
  69. package/docs/img/jspsych-logo.jpg +0 -0
  70. package/docs/img/orange.png +0 -0
  71. package/docs/img/palmer_stim.png +0 -0
  72. package/docs/img/progress_bar.png +0 -0
  73. package/docs/img/visual_search_example.jpg +0 -0
  74. package/docs/index.md +0 -9
  75. package/docs/overview/browser-device-support.md +0 -35
  76. package/docs/overview/callbacks.md +0 -140
  77. package/docs/overview/data.md +0 -281
  78. package/docs/overview/exclude-browser.md +0 -32
  79. package/docs/overview/experiment-options.md +0 -121
  80. package/docs/overview/fullscreen.md +0 -36
  81. package/docs/overview/media-preloading.md +0 -91
  82. package/docs/overview/mturk.md +0 -77
  83. package/docs/overview/progress-bar.md +0 -110
  84. package/docs/overview/record-browser-interactions.md +0 -23
  85. package/docs/overview/running-experiments.md +0 -95
  86. package/docs/overview/timeline.md +0 -387
  87. package/docs/overview/trial.md +0 -142
  88. package/docs/plugins/creating-a-plugin.md +0 -79
  89. package/docs/plugins/jspsych-animation.md +0 -40
  90. package/docs/plugins/jspsych-audio-button-response.md +0 -60
  91. package/docs/plugins/jspsych-audio-keyboard-response.md +0 -58
  92. package/docs/plugins/jspsych-audio-slider-response.md +0 -53
  93. package/docs/plugins/jspsych-call-function.md +0 -81
  94. package/docs/plugins/jspsych-canvas-button-response.md +0 -66
  95. package/docs/plugins/jspsych-canvas-keyboard-response.md +0 -68
  96. package/docs/plugins/jspsych-canvas-slider-response.md +0 -89
  97. package/docs/plugins/jspsych-categorize-animation.md +0 -60
  98. package/docs/plugins/jspsych-categorize-html.md +0 -52
  99. package/docs/plugins/jspsych-categorize-image.md +0 -53
  100. package/docs/plugins/jspsych-cloze.md +0 -45
  101. package/docs/plugins/jspsych-external-html.md +0 -70
  102. package/docs/plugins/jspsych-free-sort.md +0 -55
  103. package/docs/plugins/jspsych-fullscreen.md +0 -57
  104. package/docs/plugins/jspsych-html-button-response.md +0 -42
  105. package/docs/plugins/jspsych-html-keyboard-response.md +0 -51
  106. package/docs/plugins/jspsych-html-slider-response.md +0 -45
  107. package/docs/plugins/jspsych-iat-html.md +0 -64
  108. package/docs/plugins/jspsych-iat-image.md +0 -64
  109. package/docs/plugins/jspsych-image-button-response.md +0 -46
  110. package/docs/plugins/jspsych-image-keyboard-response.md +0 -57
  111. package/docs/plugins/jspsych-image-slider-response.md +0 -52
  112. package/docs/plugins/jspsych-instructions.md +0 -58
  113. package/docs/plugins/jspsych-maxdiff.md +0 -42
  114. package/docs/plugins/jspsych-rdk.md +0 -119
  115. package/docs/plugins/jspsych-reconstruction.md +0 -48
  116. package/docs/plugins/jspsych-resize.md +0 -39
  117. package/docs/plugins/jspsych-same-different-html.md +0 -53
  118. package/docs/plugins/jspsych-same-different-image.md +0 -66
  119. package/docs/plugins/jspsych-serial-reaction-time-mouse.md +0 -50
  120. package/docs/plugins/jspsych-serial-reaction-time.md +0 -57
  121. package/docs/plugins/jspsych-survey-html-form.md +0 -50
  122. package/docs/plugins/jspsych-survey-likert.md +0 -70
  123. package/docs/plugins/jspsych-survey-multi-choice.md +0 -48
  124. package/docs/plugins/jspsych-survey-multi-select.md +0 -53
  125. package/docs/plugins/jspsych-survey-text.md +0 -63
  126. package/docs/plugins/jspsych-video-button-response.md +0 -52
  127. package/docs/plugins/jspsych-video-keyboard-response.md +0 -48
  128. package/docs/plugins/jspsych-video-slider-response.md +0 -58
  129. package/docs/plugins/jspsych-visual-search-circle.md +0 -52
  130. package/docs/plugins/jspsych-vsl-animate-occlusion.md +0 -55
  131. package/docs/plugins/jspsych-vsl-grid-scene.md +0 -62
  132. package/docs/plugins/overview.md +0 -111
  133. package/docs/tutorials/hello-world.md +0 -144
  134. package/docs/tutorials/rt-task.md +0 -1107
  135. package/examples/add-to-end-of-timeline.html +0 -32
  136. package/examples/conditional-and-loop-functions.html +0 -63
  137. package/examples/css/jquery-ui.css +0 -1225
  138. package/examples/data-add-properties.html +0 -40
  139. package/examples/data-as-function.html +0 -36
  140. package/examples/data-from-timeline.html +0 -45
  141. package/examples/data-from-url.html +0 -21
  142. package/examples/demo-flanker.html +0 -108
  143. package/examples/demo-simple-rt-task.html +0 -104
  144. package/examples/demos/demo_1.html +0 -29
  145. package/examples/demos/demo_2.html +0 -43
  146. package/examples/demos/demo_3.html +0 -58
  147. package/examples/display-element-to-embed-experiment.html +0 -73
  148. package/examples/end-active-node.html +0 -52
  149. package/examples/end-experiment.html +0 -43
  150. package/examples/exclusions.html +0 -32
  151. package/examples/external_html/simple_consent.html +0 -4
  152. package/examples/img/1.gif +0 -0
  153. package/examples/img/10.gif +0 -0
  154. package/examples/img/11.gif +0 -0
  155. package/examples/img/12.gif +0 -0
  156. package/examples/img/2.gif +0 -0
  157. package/examples/img/3.gif +0 -0
  158. package/examples/img/4.gif +0 -0
  159. package/examples/img/5.gif +0 -0
  160. package/examples/img/6.gif +0 -0
  161. package/examples/img/7.gif +0 -0
  162. package/examples/img/8.gif +0 -0
  163. package/examples/img/9.gif +0 -0
  164. package/examples/img/age/of1.jpg +0 -0
  165. package/examples/img/age/of2.jpg +0 -0
  166. package/examples/img/age/of3.jpg +0 -0
  167. package/examples/img/age/om1.jpg +0 -0
  168. package/examples/img/age/om2.jpg +0 -0
  169. package/examples/img/age/om3.jpg +0 -0
  170. package/examples/img/age/yf1.jpg +0 -0
  171. package/examples/img/age/yf4.jpg +0 -0
  172. package/examples/img/age/yf5.jpg +0 -0
  173. package/examples/img/age/ym2.jpg +0 -0
  174. package/examples/img/age/ym3.jpg +0 -0
  175. package/examples/img/age/ym5.jpg +0 -0
  176. package/examples/img/backwardN.gif +0 -0
  177. package/examples/img/blue.png +0 -0
  178. package/examples/img/con1.png +0 -0
  179. package/examples/img/con2.png +0 -0
  180. package/examples/img/fixation.gif +0 -0
  181. package/examples/img/happy_face_1.jpg +0 -0
  182. package/examples/img/happy_face_2.jpg +0 -0
  183. package/examples/img/happy_face_3.jpg +0 -0
  184. package/examples/img/happy_face_4.jpg +0 -0
  185. package/examples/img/inc1.png +0 -0
  186. package/examples/img/inc2.png +0 -0
  187. package/examples/img/normalN.gif +0 -0
  188. package/examples/img/orange.png +0 -0
  189. package/examples/img/redX.png +0 -0
  190. package/examples/img/ribbon.jpg +0 -0
  191. package/examples/img/sad_face_1.jpg +0 -0
  192. package/examples/img/sad_face_2.jpg +0 -0
  193. package/examples/img/sad_face_3.jpg +0 -0
  194. package/examples/img/sad_face_4.jpg +0 -0
  195. package/examples/js/snap.svg-min.js +0 -21
  196. package/examples/jspsych-RDK.html +0 -58
  197. package/examples/jspsych-animation.html +0 -33
  198. package/examples/jspsych-audio-button-response.html +0 -52
  199. package/examples/jspsych-audio-keyboard-response.html +0 -62
  200. package/examples/jspsych-audio-slider-response.html +0 -55
  201. package/examples/jspsych-call-function.html +0 -32
  202. package/examples/jspsych-canvas-button-response.html +0 -95
  203. package/examples/jspsych-canvas-keyboard-response.html +0 -78
  204. package/examples/jspsych-canvas-slider-response.html +0 -67
  205. package/examples/jspsych-categorize-animation.html +0 -46
  206. package/examples/jspsych-categorize-html.html +0 -38
  207. package/examples/jspsych-categorize-image.html +0 -38
  208. package/examples/jspsych-cloze.html +0 -42
  209. package/examples/jspsych-free-sort.html +0 -97
  210. package/examples/jspsych-fullscreen.html +0 -44
  211. package/examples/jspsych-html-button-response.html +0 -46
  212. package/examples/jspsych-html-keyboard-response.html +0 -42
  213. package/examples/jspsych-html-slider-response.html +0 -53
  214. package/examples/jspsych-iat.html +0 -510
  215. package/examples/jspsych-image-button-response.html +0 -84
  216. package/examples/jspsych-image-keyboard-response.html +0 -78
  217. package/examples/jspsych-image-slider-response.html +0 -76
  218. package/examples/jspsych-instructions.html +0 -37
  219. package/examples/jspsych-maxdiff.html +0 -33
  220. package/examples/jspsych-reconstruction.html +0 -43
  221. package/examples/jspsych-resize.html +0 -34
  222. package/examples/jspsych-same-different-html.html +0 -28
  223. package/examples/jspsych-same-different-image.html +0 -33
  224. package/examples/jspsych-serial-reaction-time-mouse.html +0 -98
  225. package/examples/jspsych-serial-reaction-time.html +0 -54
  226. package/examples/jspsych-survey-html-form.html +0 -33
  227. package/examples/jspsych-survey-likert.html +0 -42
  228. package/examples/jspsych-survey-multi-choice.html +0 -40
  229. package/examples/jspsych-survey-multi-select.html +0 -42
  230. package/examples/jspsych-survey-text.html +0 -34
  231. package/examples/jspsych-video-button-response.html +0 -57
  232. package/examples/jspsych-video-keyboard-response.html +0 -53
  233. package/examples/jspsych-video-slider-response.html +0 -55
  234. package/examples/jspsych-visual-search-circle.html +0 -58
  235. package/examples/jspsych-vsl-animate-occlusion.html +0 -29
  236. package/examples/jspsych-vsl-grid-scene.html +0 -41
  237. package/examples/lexical-decision.html +0 -132
  238. package/examples/manual-preloading.html +0 -53
  239. package/examples/pause-unpause.html +0 -33
  240. package/examples/progress-bar.html +0 -62
  241. package/examples/sound/hammer.mp3 +0 -0
  242. package/examples/sound/sound.mp3 +0 -0
  243. package/examples/sound/speech_blue.mp3 +0 -0
  244. package/examples/sound/speech_green.mp3 +0 -0
  245. package/examples/sound/speech_joke.mp3 +0 -0
  246. package/examples/sound/speech_red.mp3 +0 -0
  247. package/examples/sound/tone.mp3 +0 -0
  248. package/examples/timeline-variables-sampling.html +0 -50
  249. package/examples/timeline-variables.html +0 -55
  250. package/examples/video/sample_video.mp4 +0 -0
  251. package/jspsych.js +0 -2796
  252. package/license.txt +0 -21
  253. package/mkdocs.yml +0 -104
  254. package/plugins/jspsych-animation.js +0 -189
  255. package/plugins/jspsych-audio-button-response.js +0 -247
  256. package/plugins/jspsych-audio-keyboard-response.js +0 -204
  257. package/plugins/jspsych-audio-slider-response.js +0 -262
  258. package/plugins/jspsych-call-function.js +0 -58
  259. package/plugins/jspsych-canvas-button-response.js +0 -199
  260. package/plugins/jspsych-canvas-keyboard-response.js +0 -155
  261. package/plugins/jspsych-canvas-slider-response.js +0 -207
  262. package/plugins/jspsych-categorize-animation.js +0 -266
  263. package/plugins/jspsych-categorize-html.js +0 -220
  264. package/plugins/jspsych-categorize-image.js +0 -222
  265. package/plugins/jspsych-cloze.js +0 -112
  266. package/plugins/jspsych-external-html.js +0 -112
  267. package/plugins/jspsych-free-sort.js +0 -444
  268. package/plugins/jspsych-fullscreen.js +0 -104
  269. package/plugins/jspsych-html-button-response.js +0 -188
  270. package/plugins/jspsych-html-keyboard-response.js +0 -149
  271. package/plugins/jspsych-html-slider-response.js +0 -202
  272. package/plugins/jspsych-iat-html.js +0 -284
  273. package/plugins/jspsych-iat-image.js +0 -286
  274. package/plugins/jspsych-image-button-response.js +0 -311
  275. package/plugins/jspsych-image-keyboard-response.js +0 -247
  276. package/plugins/jspsych-image-slider-response.js +0 -353
  277. package/plugins/jspsych-instructions.js +0 -237
  278. package/plugins/jspsych-maxdiff.js +0 -174
  279. package/plugins/jspsych-rdk.js +0 -1373
  280. package/plugins/jspsych-reconstruction.js +0 -134
  281. package/plugins/jspsych-resize.js +0 -166
  282. package/plugins/jspsych-same-different-html.js +0 -168
  283. package/plugins/jspsych-same-different-image.js +0 -169
  284. package/plugins/jspsych-serial-reaction-time-mouse.js +0 -213
  285. package/plugins/jspsych-serial-reaction-time.js +0 -247
  286. package/plugins/jspsych-survey-html-form.js +0 -171
  287. package/plugins/jspsych-survey-likert.js +0 -195
  288. package/plugins/jspsych-survey-multi-choice.js +0 -208
  289. package/plugins/jspsych-survey-multi-select.js +0 -232
  290. package/plugins/jspsych-survey-text.js +0 -185
  291. package/plugins/jspsych-video-button-response.js +0 -320
  292. package/plugins/jspsych-video-keyboard-response.js +0 -279
  293. package/plugins/jspsych-video-slider-response.js +0 -351
  294. package/plugins/jspsych-visual-search-circle.js +0 -259
  295. package/plugins/jspsych-vsl-animate-occlusion.js +0 -196
  296. package/plugins/jspsych-vsl-grid-scene.js +0 -103
  297. package/plugins/template/jspsych-plugin-template.js +0 -35
  298. package/tests/README.md +0 -7
  299. package/tests/jsPsych/default-iti.test.js +0 -51
  300. package/tests/jsPsych/default-parameters.test.js +0 -58
  301. package/tests/jsPsych/endexperiment.test.js +0 -49
  302. package/tests/jsPsych/events.test.js +0 -369
  303. package/tests/jsPsych/init.test.js +0 -48
  304. package/tests/jsPsych/loads.test.js +0 -7
  305. package/tests/jsPsych/min-rt.test.js +0 -58
  306. package/tests/jsPsych/progressbar.test.js +0 -202
  307. package/tests/jsPsych/timeline-variables.test.js +0 -254
  308. package/tests/jsPsych/timelines.test.js +0 -498
  309. package/tests/jsPsych.data/datacollection.test.js +0 -116
  310. package/tests/jsPsych.data/datacolumn.test.js +0 -50
  311. package/tests/jsPsych.data/datamodule.test.js +0 -152
  312. package/tests/jsPsych.data/dataparameter.test.js +0 -251
  313. package/tests/jsPsych.data/interactions.test.js +0 -109
  314. package/tests/jsPsych.pluginAPI/pluginapi.test.js +0 -144
  315. package/tests/jsPsych.randomization/randomziation.test.js +0 -27
  316. package/tests/jsPsych.utils/utils.test.js +0 -58
  317. package/tests/media/blue.png +0 -0
  318. package/tests/media/orange.png +0 -0
  319. package/tests/media/sample_video.mp4 +0 -0
  320. package/tests/media/sound.mp3 +0 -0
  321. package/tests/plugins/plugin-animation.test.js +0 -35
  322. package/tests/plugins/plugin-audio-button-response.test.js +0 -15
  323. package/tests/plugins/plugin-audio-keyboard-response.test.js +0 -15
  324. package/tests/plugins/plugin-audio-slider-response.test.js +0 -15
  325. package/tests/plugins/plugin-call-function.test.js +0 -49
  326. package/tests/plugins/plugin-categorize-animation.test.js +0 -274
  327. package/tests/plugins/plugin-categorize-html.test.js +0 -17
  328. package/tests/plugins/plugin-categorize-image.test.js +0 -17
  329. package/tests/plugins/plugin-cloze.test.js +0 -140
  330. package/tests/plugins/plugin-free-sort.test.js +0 -112
  331. package/tests/plugins/plugin-fullscreen.test.js +0 -41
  332. package/tests/plugins/plugin-html-button-response.test.js +0 -161
  333. package/tests/plugins/plugin-html-keyboard-response.test.js +0 -139
  334. package/tests/plugins/plugin-html-slider-response.test.js +0 -155
  335. package/tests/plugins/plugin-iat-html.test.js +0 -328
  336. package/tests/plugins/plugin-iat-image.test.js +0 -308
  337. package/tests/plugins/plugin-image-button-response.test.js +0 -183
  338. package/tests/plugins/plugin-image-keyboard-response.test.js +0 -154
  339. package/tests/plugins/plugin-image-slider-response.test.js +0 -183
  340. package/tests/plugins/plugin-instructions.test.js +0 -66
  341. package/tests/plugins/plugin-maxdiff.test.js +0 -39
  342. package/tests/plugins/plugin-rdk.test.js +0 -17
  343. package/tests/plugins/plugin-reconstruction.test.js +0 -16
  344. package/tests/plugins/plugin-resize.test.js +0 -16
  345. package/tests/plugins/plugin-same-different-html.test.js +0 -17
  346. package/tests/plugins/plugin-same-different-image.test.js +0 -17
  347. package/tests/plugins/plugin-serial-reaction-time-mouse.test.js +0 -42
  348. package/tests/plugins/plugin-serial-reaction-time.test.js +0 -69
  349. package/tests/plugins/plugin-survey-html-form.test.js +0 -44
  350. package/tests/plugins/plugin-survey-likert.test.js +0 -48
  351. package/tests/plugins/plugin-survey-multi-choice.test.js +0 -48
  352. package/tests/plugins/plugin-survey-multi-select.test.js +0 -72
  353. package/tests/plugins/plugin-survey-text.test.js +0 -115
  354. package/tests/plugins/plugin-video-button-response.test.js +0 -35
  355. package/tests/plugins/plugin-video-keyboard-response.test.js +0 -35
  356. package/tests/plugins/plugin-video-slider-response.test.js +0 -34
  357. package/tests/plugins/plugin-visual-search-circle.test.js +0 -16
  358. package/tests/plugins/plugin-vsl-animate-occlusion.test.js +0 -16
  359. package/tests/plugins/plugin-vsl-grid-scene.test.js +0 -16
  360. package/tests/testing-utils.js +0 -13
@@ -0,0 +1,536 @@
1
+ import { JsPsych } from "./JsPsych";
2
+ import {
3
+ repeat,
4
+ sampleWithReplacement,
5
+ sampleWithoutReplacement,
6
+ shuffle,
7
+ shuffleAlternateGroups,
8
+ } from "./modules/randomization";
9
+ import { deepCopy } from "./modules/utils";
10
+
11
+ export class TimelineNode {
12
+ // a unique ID for this node, relative to the parent
13
+ relative_id;
14
+
15
+ // store the parent for this node
16
+ parent_node;
17
+
18
+ // parameters for the trial if the node contains a trial
19
+ trial_parameters;
20
+
21
+ // parameters for nodes that contain timelines
22
+ timeline_parameters;
23
+
24
+ // stores trial information on a node that contains a timeline
25
+ // used for adding new trials
26
+ node_trial_data;
27
+
28
+ // track progress through the node
29
+ progress = <any>{
30
+ current_location: -1, // where on the timeline (which timelinenode)
31
+ current_variable_set: 0, // which set of variables to use from timeline_variables
32
+ current_repetition: 0, // how many times through the variable set on this run of the node
33
+ current_iteration: 0, // how many times this node has been revisited
34
+ done: false,
35
+ };
36
+
37
+ end_message?: string;
38
+
39
+ // constructor
40
+ constructor(private jsPsych: JsPsych, parameters, parent?, relativeID?) {
41
+ // store a link to the parent of this node
42
+ this.parent_node = parent;
43
+
44
+ // create the ID for this node
45
+ this.relative_id = typeof parent === "undefined" ? 0 : relativeID;
46
+
47
+ // check if there is a timeline parameter
48
+ // if there is, then this node has its own timeline
49
+ if (typeof parameters.timeline !== "undefined") {
50
+ // create timeline properties
51
+ this.timeline_parameters = {
52
+ timeline: [],
53
+ loop_function: parameters.loop_function,
54
+ conditional_function: parameters.conditional_function,
55
+ sample: parameters.sample,
56
+ randomize_order:
57
+ typeof parameters.randomize_order == "undefined" ? false : parameters.randomize_order,
58
+ repetitions: typeof parameters.repetitions == "undefined" ? 1 : parameters.repetitions,
59
+ timeline_variables:
60
+ typeof parameters.timeline_variables == "undefined"
61
+ ? [{}]
62
+ : parameters.timeline_variables,
63
+ on_timeline_finish: parameters.on_timeline_finish,
64
+ on_timeline_start: parameters.on_timeline_start,
65
+ };
66
+
67
+ this.setTimelineVariablesOrder();
68
+
69
+ // extract all of the node level data and parameters
70
+ // but remove all of the timeline-level specific information
71
+ // since this will be used to copy things down hierarchically
72
+ var node_data = Object.assign({}, parameters);
73
+ delete node_data.timeline;
74
+ delete node_data.conditional_function;
75
+ delete node_data.loop_function;
76
+ delete node_data.randomize_order;
77
+ delete node_data.repetitions;
78
+ delete node_data.timeline_variables;
79
+ delete node_data.sample;
80
+ delete node_data.on_timeline_start;
81
+ delete node_data.on_timeline_finish;
82
+ this.node_trial_data = node_data; // store for later...
83
+
84
+ // create a TimelineNode for each element in the timeline
85
+ for (var i = 0; i < parameters.timeline.length; i++) {
86
+ // merge parameters
87
+ var merged_parameters = Object.assign({}, node_data, parameters.timeline[i]);
88
+ // merge any data from the parent node into child nodes
89
+ if (typeof node_data.data == "object" && typeof parameters.timeline[i].data == "object") {
90
+ var merged_data = Object.assign({}, node_data.data, parameters.timeline[i].data);
91
+ merged_parameters.data = merged_data;
92
+ }
93
+ this.timeline_parameters.timeline.push(
94
+ new TimelineNode(this.jsPsych, merged_parameters, this, i)
95
+ );
96
+ }
97
+ }
98
+ // if there is no timeline parameter, then this node is a trial node
99
+ else {
100
+ // check to see if a valid trial type is defined
101
+ if (typeof parameters.type === "undefined") {
102
+ console.error(
103
+ 'Trial level node is missing the "type" parameter. The parameters for the node are: ' +
104
+ JSON.stringify(parameters)
105
+ );
106
+ }
107
+ // create a deep copy of the parameters for the trial
108
+ this.trial_parameters = { ...parameters };
109
+ }
110
+ }
111
+
112
+ // recursively get the next trial to run.
113
+ // if this node is a leaf (trial), then return the trial.
114
+ // otherwise, recursively find the next trial in the child timeline.
115
+ trial() {
116
+ if (typeof this.timeline_parameters == "undefined") {
117
+ // returns a clone of the trial_parameters to
118
+ // protect functions.
119
+ return deepCopy(this.trial_parameters);
120
+ } else {
121
+ if (this.progress.current_location >= this.timeline_parameters.timeline.length) {
122
+ return null;
123
+ } else {
124
+ return this.timeline_parameters.timeline[this.progress.current_location].trial();
125
+ }
126
+ }
127
+ }
128
+
129
+ markCurrentTrialComplete() {
130
+ if (typeof this.timeline_parameters === "undefined") {
131
+ this.progress.done = true;
132
+ } else {
133
+ this.timeline_parameters.timeline[this.progress.current_location].markCurrentTrialComplete();
134
+ }
135
+ }
136
+
137
+ nextRepetiton() {
138
+ this.setTimelineVariablesOrder();
139
+ this.progress.current_location = -1;
140
+ this.progress.current_variable_set = 0;
141
+ this.progress.current_repetition++;
142
+ for (var i = 0; i < this.timeline_parameters.timeline.length; i++) {
143
+ this.timeline_parameters.timeline[i].reset();
144
+ }
145
+ }
146
+
147
+ // set the order for going through the timeline variables array
148
+ setTimelineVariablesOrder() {
149
+ const timeline_parameters = this.timeline_parameters;
150
+
151
+ // check to make sure this node has variables
152
+ if (
153
+ typeof timeline_parameters === "undefined" ||
154
+ typeof timeline_parameters.timeline_variables === "undefined"
155
+ ) {
156
+ return;
157
+ }
158
+
159
+ var order = [];
160
+ for (var i = 0; i < timeline_parameters.timeline_variables.length; i++) {
161
+ order.push(i);
162
+ }
163
+
164
+ if (typeof timeline_parameters.sample !== "undefined") {
165
+ if (timeline_parameters.sample.type == "custom") {
166
+ order = timeline_parameters.sample.fn(order);
167
+ } else if (timeline_parameters.sample.type == "with-replacement") {
168
+ order = sampleWithReplacement(
169
+ order,
170
+ timeline_parameters.sample.size,
171
+ timeline_parameters.sample.weights
172
+ );
173
+ } else if (timeline_parameters.sample.type == "without-replacement") {
174
+ order = sampleWithoutReplacement(order, timeline_parameters.sample.size);
175
+ } else if (timeline_parameters.sample.type == "fixed-repetitions") {
176
+ order = repeat(order, timeline_parameters.sample.size, false);
177
+ } else if (timeline_parameters.sample.type == "alternate-groups") {
178
+ order = shuffleAlternateGroups(
179
+ timeline_parameters.sample.groups,
180
+ timeline_parameters.sample.randomize_group_order
181
+ );
182
+ } else {
183
+ console.error(
184
+ 'Invalid type in timeline sample parameters. Valid options for type are "custom", "with-replacement", "without-replacement", "fixed-repetitions", and "alternate-groups"'
185
+ );
186
+ }
187
+ }
188
+
189
+ if (timeline_parameters.randomize_order) {
190
+ order = shuffle(order);
191
+ }
192
+
193
+ this.progress.order = order;
194
+ }
195
+
196
+ // next variable set
197
+ nextSet() {
198
+ this.progress.current_location = -1;
199
+ this.progress.current_variable_set++;
200
+ for (var i = 0; i < this.timeline_parameters.timeline.length; i++) {
201
+ this.timeline_parameters.timeline[i].reset();
202
+ }
203
+ }
204
+
205
+ // update the current trial node to be completed
206
+ // returns true if the node is complete after advance (all subnodes are also complete)
207
+ // returns false otherwise
208
+ advance() {
209
+ const progress = this.progress;
210
+ const timeline_parameters = this.timeline_parameters;
211
+ const internal = this.jsPsych.internal;
212
+
213
+ // first check to see if done
214
+ if (progress.done) {
215
+ return true;
216
+ }
217
+
218
+ // if node has not started yet (progress.current_location == -1),
219
+ // then try to start the node.
220
+ if (progress.current_location == -1) {
221
+ // check for on_timeline_start and conditonal function on nodes with timelines
222
+ if (typeof timeline_parameters !== "undefined") {
223
+ // only run the conditional function if this is the first repetition of the timeline when
224
+ // repetitions > 1, and only when on the first variable set
225
+ if (
226
+ typeof timeline_parameters.conditional_function !== "undefined" &&
227
+ progress.current_repetition == 0 &&
228
+ progress.current_variable_set == 0
229
+ ) {
230
+ internal.call_immediate = true;
231
+ var conditional_result = timeline_parameters.conditional_function();
232
+ internal.call_immediate = false;
233
+ // if the conditional_function() returns false, then the timeline
234
+ // doesn't run and is marked as complete.
235
+ if (conditional_result == false) {
236
+ progress.done = true;
237
+ return true;
238
+ }
239
+ }
240
+
241
+ // if we reach this point then the node has its own timeline and will start
242
+ // so we need to check if there is an on_timeline_start function if we are on the first variable set
243
+ if (
244
+ typeof timeline_parameters.on_timeline_start !== "undefined" &&
245
+ progress.current_variable_set == 0
246
+ ) {
247
+ timeline_parameters.on_timeline_start();
248
+ }
249
+ }
250
+ // if we reach this point, then either the node doesn't have a timeline of the
251
+ // conditional function returned true and it can start
252
+ progress.current_location = 0;
253
+ // call advance again on this node now that it is pointing to a new location
254
+ return this.advance();
255
+ }
256
+
257
+ // if this node has a timeline, propogate down to the current trial.
258
+ if (typeof timeline_parameters !== "undefined") {
259
+ var have_node_to_run = false;
260
+ // keep incrementing the location in the timeline until one of the nodes reached is incomplete
261
+ while (
262
+ progress.current_location < timeline_parameters.timeline.length &&
263
+ have_node_to_run == false
264
+ ) {
265
+ // check to see if the node currently pointed at is done
266
+ var target_complete = timeline_parameters.timeline[progress.current_location].advance();
267
+ if (!target_complete) {
268
+ have_node_to_run = true;
269
+ return false;
270
+ } else {
271
+ progress.current_location++;
272
+ }
273
+ }
274
+
275
+ // if we've reached the end of the timeline (which, if the code is here, we have)
276
+
277
+ // there are a few steps to see what to do next...
278
+
279
+ // first, check the timeline_variables to see if we need to loop through again
280
+ // with a new set of variables
281
+ if (progress.current_variable_set < progress.order.length - 1) {
282
+ // reset the progress of the node to be with the new set
283
+ this.nextSet();
284
+ // then try to advance this node again.
285
+ return this.advance();
286
+ }
287
+
288
+ // if we're all done with the timeline_variables, then check to see if there are more repetitions
289
+ else if (progress.current_repetition < timeline_parameters.repetitions - 1) {
290
+ this.nextRepetiton();
291
+ // check to see if there is an on_timeline_finish function
292
+ if (typeof timeline_parameters.on_timeline_finish !== "undefined") {
293
+ timeline_parameters.on_timeline_finish();
294
+ }
295
+ return this.advance();
296
+ }
297
+
298
+ // if we're all done with the repetitions...
299
+ else {
300
+ // check to see if there is an on_timeline_finish function
301
+ if (typeof timeline_parameters.on_timeline_finish !== "undefined") {
302
+ timeline_parameters.on_timeline_finish();
303
+ }
304
+
305
+ // if we're all done with the repetitions, check if there is a loop function.
306
+ if (typeof timeline_parameters.loop_function !== "undefined") {
307
+ internal.call_immediate = true;
308
+ if (timeline_parameters.loop_function(this.generatedData())) {
309
+ this.reset();
310
+ internal.call_immediate = false;
311
+ return this.parent_node.advance();
312
+ } else {
313
+ progress.done = true;
314
+ internal.call_immediate = false;
315
+ return true;
316
+ }
317
+ }
318
+ }
319
+
320
+ // no more loops on this timeline, we're done!
321
+ progress.done = true;
322
+ return true;
323
+ }
324
+ }
325
+
326
+ // check the status of the done flag
327
+ isComplete() {
328
+ return this.progress.done;
329
+ }
330
+
331
+ // getter method for timeline variables
332
+ getTimelineVariableValue(variable_name: string) {
333
+ if (typeof this.timeline_parameters == "undefined") {
334
+ return undefined;
335
+ }
336
+ var v =
337
+ this.timeline_parameters.timeline_variables[
338
+ this.progress.order[this.progress.current_variable_set]
339
+ ][variable_name];
340
+ return v;
341
+ }
342
+
343
+ // recursive upward search for timeline variables
344
+ findTimelineVariable(variable_name) {
345
+ var v = this.getTimelineVariableValue(variable_name);
346
+ if (typeof v == "undefined") {
347
+ if (typeof this.parent_node !== "undefined") {
348
+ return this.parent_node.findTimelineVariable(variable_name);
349
+ } else {
350
+ return undefined;
351
+ }
352
+ } else {
353
+ return v;
354
+ }
355
+ }
356
+
357
+ // recursive downward search for active trial to extract timeline variable
358
+ timelineVariable(variable_name: string) {
359
+ if (typeof this.timeline_parameters == "undefined") {
360
+ return this.findTimelineVariable(variable_name);
361
+ } else {
362
+ // if progress.current_location is -1, then the timeline variable is being evaluated
363
+ // in a function that runs prior to the trial starting, so we should treat that trial
364
+ // as being the active trial for purposes of finding the value of the timeline variable
365
+ var loc = Math.max(0, this.progress.current_location);
366
+ // if loc is greater than the number of elements on this timeline, then the timeline
367
+ // variable is being evaluated in a function that runs after the trial on the timeline
368
+ // are complete but before advancing to the next (like a loop_function).
369
+ // treat the last active trial as the active trial for this purpose.
370
+ if (loc == this.timeline_parameters.timeline.length) {
371
+ loc = loc - 1;
372
+ }
373
+ // now find the variable
374
+ return this.timeline_parameters.timeline[loc].timelineVariable(variable_name);
375
+ }
376
+ }
377
+
378
+ // recursively get all the timeline variables for this trial
379
+ allTimelineVariables() {
380
+ var all_tvs = this.allTimelineVariablesNames();
381
+ var all_tvs_vals = <any>{};
382
+ for (var i = 0; i < all_tvs.length; i++) {
383
+ all_tvs_vals[all_tvs[i]] = this.timelineVariable(all_tvs[i]);
384
+ }
385
+ return all_tvs_vals;
386
+ }
387
+
388
+ // helper to get all the names at this stage.
389
+ allTimelineVariablesNames(so_far = []) {
390
+ if (typeof this.timeline_parameters !== "undefined") {
391
+ so_far = so_far.concat(
392
+ Object.keys(
393
+ this.timeline_parameters.timeline_variables[
394
+ this.progress.order[this.progress.current_variable_set]
395
+ ]
396
+ )
397
+ );
398
+ // if progress.current_location is -1, then the timeline variable is being evaluated
399
+ // in a function that runs prior to the trial starting, so we should treat that trial
400
+ // as being the active trial for purposes of finding the value of the timeline variable
401
+ var loc = Math.max(0, this.progress.current_location);
402
+ // if loc is greater than the number of elements on this timeline, then the timeline
403
+ // variable is being evaluated in a function that runs after the trial on the timeline
404
+ // are complete but before advancing to the next (like a loop_function).
405
+ // treat the last active trial as the active trial for this purpose.
406
+ if (loc == this.timeline_parameters.timeline.length) {
407
+ loc = loc - 1;
408
+ }
409
+ // now find the variable
410
+ return this.timeline_parameters.timeline[loc].allTimelineVariablesNames(so_far);
411
+ }
412
+ if (typeof this.timeline_parameters == "undefined") {
413
+ return so_far;
414
+ }
415
+ }
416
+
417
+ // recursively get the number of **trials** contained in the timeline
418
+ // assuming that while loops execute exactly once and if conditionals
419
+ // always run
420
+ length() {
421
+ var length = 0;
422
+ if (typeof this.timeline_parameters !== "undefined") {
423
+ for (var i = 0; i < this.timeline_parameters.timeline.length; i++) {
424
+ length += this.timeline_parameters.timeline[i].length();
425
+ }
426
+ } else {
427
+ return 1;
428
+ }
429
+ return length;
430
+ }
431
+
432
+ // return the percentage of trials completed, grouped at the first child level
433
+ // counts a set of trials as complete when the child node is done
434
+ percentComplete() {
435
+ var total_trials = this.length();
436
+ var completed_trials = 0;
437
+ for (var i = 0; i < this.timeline_parameters.timeline.length; i++) {
438
+ if (this.timeline_parameters.timeline[i].isComplete()) {
439
+ completed_trials += this.timeline_parameters.timeline[i].length();
440
+ }
441
+ }
442
+ return (completed_trials / total_trials) * 100;
443
+ }
444
+
445
+ // resets the node and all subnodes to original state
446
+ // but increments the current_iteration counter
447
+ reset() {
448
+ this.progress.current_location = -1;
449
+ this.progress.current_repetition = 0;
450
+ this.progress.current_variable_set = 0;
451
+ this.progress.current_iteration++;
452
+ this.progress.done = false;
453
+ this.setTimelineVariablesOrder();
454
+ if (typeof this.timeline_parameters != "undefined") {
455
+ for (var i = 0; i < this.timeline_parameters.timeline.length; i++) {
456
+ this.timeline_parameters.timeline[i].reset();
457
+ }
458
+ }
459
+ }
460
+
461
+ // mark this node as finished
462
+ end() {
463
+ this.progress.done = true;
464
+ }
465
+
466
+ // recursively end whatever sub-node is running the current trial
467
+ endActiveNode() {
468
+ if (typeof this.timeline_parameters == "undefined") {
469
+ this.end();
470
+ this.parent_node.end();
471
+ } else {
472
+ this.timeline_parameters.timeline[this.progress.current_location].endActiveNode();
473
+ }
474
+ }
475
+
476
+ // get a unique ID associated with this node
477
+ // the ID reflects the current iteration through this node.
478
+ ID() {
479
+ var id = "";
480
+ if (typeof this.parent_node == "undefined") {
481
+ return "0." + this.progress.current_iteration;
482
+ } else {
483
+ id += this.parent_node.ID() + "-";
484
+ id += this.relative_id + "." + this.progress.current_iteration;
485
+ return id;
486
+ }
487
+ }
488
+
489
+ // get the ID of the active trial
490
+ activeID() {
491
+ if (typeof this.timeline_parameters == "undefined") {
492
+ return this.ID();
493
+ } else {
494
+ return this.timeline_parameters.timeline[this.progress.current_location].activeID();
495
+ }
496
+ }
497
+
498
+ // get all the data generated within this node
499
+ generatedData() {
500
+ return this.jsPsych.data.getDataByTimelineNode(this.ID());
501
+ }
502
+
503
+ // get all the trials of a particular type
504
+ trialsOfType(type) {
505
+ if (typeof this.timeline_parameters == "undefined") {
506
+ if (this.trial_parameters.type == type) {
507
+ return this.trial_parameters;
508
+ } else {
509
+ return [];
510
+ }
511
+ } else {
512
+ var trials = [];
513
+ for (var i = 0; i < this.timeline_parameters.timeline.length; i++) {
514
+ var t = this.timeline_parameters.timeline[i].trialsOfType(type);
515
+ trials = trials.concat(t);
516
+ }
517
+ return trials;
518
+ }
519
+ }
520
+
521
+ // add new trials to end of this timeline
522
+ insert(parameters) {
523
+ if (typeof this.timeline_parameters === "undefined") {
524
+ console.error("Cannot add new trials to a trial-level node.");
525
+ } else {
526
+ this.timeline_parameters.timeline.push(
527
+ new TimelineNode(
528
+ this.jsPsych,
529
+ { ...this.node_trial_data, ...parameters },
530
+ this,
531
+ this.timeline_parameters.timeline.length
532
+ )
533
+ );
534
+ }
535
+ }
536
+ }
package/src/index.ts ADDED
@@ -0,0 +1,71 @@
1
+ import { JsPsych } from "./JsPsych";
2
+ import { MigrationError } from "./migration";
3
+
4
+ // temporary patch for Safari
5
+ if (
6
+ typeof window !== "undefined" &&
7
+ window.hasOwnProperty("webkitAudioContext") &&
8
+ !window.hasOwnProperty("AudioContext")
9
+ ) {
10
+ // @ts-expect-error
11
+ window.AudioContext = webkitAudioContext;
12
+ }
13
+ // end patch
14
+
15
+ // The following function provides a uniform interface to initialize jsPsych, no matter whether a
16
+ // browser supports ES6 classes or not (and whether the ES6 build or the Babel build is used).
17
+ /**
18
+ * Creates a new JsPsych instance using the provided options.
19
+ *
20
+ * @param options The options to pass to the JsPsych constructor
21
+ * @returns A new JsPsych instance
22
+ */
23
+ export function initJsPsych(options?) {
24
+ const jsPsych = new JsPsych(options);
25
+
26
+ // Handle invocations of non-existent v6 methods with migration errors
27
+ const migrationMessages = {
28
+ init: "`jsPsych.init()` was replaced by `initJsPsych()` in jsPsych v7.",
29
+
30
+ ALL_KEYS: 'jsPsych.ALL_KEYS was replaced by the "ALL_KEYS" string in jsPsych v7.',
31
+ NO_KEYS: 'jsPsych.NO_KEYS was replaced by the "NO_KEYS" string in jsPsych v7.',
32
+
33
+ // Getter functions that were renamed
34
+ currentTimelineNodeID:
35
+ "`currentTimelineNodeID()` was renamed to `getCurrentTimelineNodeID()` in jsPsych v7.",
36
+ progress: "`progress()` was renamed to `getProgress()` in jsPsych v7.",
37
+ startTime: "`startTime()` was renamed to `getStartTime()` in jsPsych v7.",
38
+ totalTime: "`totalTime()` was renamed to `getTotalTime()` in jsPsych v7.",
39
+ currentTrial: "`currentTrial()` was renamed to `getCurrentTrial()` in jsPsych v7.",
40
+ initSettings: "`initSettings()` was renamed to `getInitSettings()` in jsPsych v7.",
41
+ allTimelineVariables:
42
+ "`allTimelineVariables()` was renamed to `getAllTimelineVariables()` in jsPsych v7.",
43
+ };
44
+
45
+ Object.defineProperties(
46
+ jsPsych,
47
+ Object.fromEntries(
48
+ Object.entries(migrationMessages).map(([key, message]) => [
49
+ key,
50
+ {
51
+ get() {
52
+ throw new MigrationError(message);
53
+ },
54
+ },
55
+ ])
56
+ )
57
+ );
58
+
59
+ return jsPsych;
60
+ }
61
+
62
+ export { JsPsych } from "./JsPsych";
63
+ export {
64
+ JsPsychPlugin,
65
+ PluginInfo,
66
+ TrialType,
67
+ ParameterType,
68
+ universalPluginParameters,
69
+ UniversalPluginParameters,
70
+ } from "./modules/plugins";
71
+ export { JsPsychExtension, JsPsychExtensionInfo } from "./modules/extensions";
@@ -0,0 +1,37 @@
1
+ export class MigrationError extends Error {
2
+ constructor(message = "The global `jsPsych` variable is no longer available in jsPsych v7.") {
3
+ super(
4
+ `${message} Please follow the migration guide at https://www.jspsych.org/7.0/support/migration-v7/ to update your experiment.`
5
+ );
6
+ this.name = "MigrationError";
7
+ }
8
+ }
9
+
10
+ // Define a global jsPsych object to handle invocations on it with migration errors
11
+ (window as any).jsPsych = {
12
+ get init() {
13
+ throw new MigrationError("`jsPsych.init()` was replaced by `initJsPsych()` in jsPsych v7.");
14
+ },
15
+
16
+ get data() {
17
+ throw new MigrationError();
18
+ },
19
+ get randomization() {
20
+ throw new MigrationError();
21
+ },
22
+ get turk() {
23
+ throw new MigrationError();
24
+ },
25
+ get pluginAPI() {
26
+ throw new MigrationError();
27
+ },
28
+
29
+ get ALL_KEYS() {
30
+ throw new MigrationError(
31
+ 'jsPsych.ALL_KEYS was replaced by the "ALL_KEYS" string in jsPsych v7.'
32
+ );
33
+ },
34
+ get NO_KEYS() {
35
+ throw new MigrationError('jsPsych.NO_KEYS was replaced by the "NO_KEYS" string in jsPsych v7.');
36
+ },
37
+ };