Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
7841109
Setup inital spr template
vzhang03 Oct 7, 2024
ea27f01
Inital working version of SPR complete
vzhang03 Oct 7, 2024
4d4e9f3
Working version of SPR with multiple words
vzhang03 Oct 7, 2024
6c169d4
Commited working fix to us styling using span, need to finalize update
vzhang03 Oct 7, 2024
c3f5323
All modes working, need to work on error handling and making code mor…
vzhang03 Oct 13, 2024
e866583
Successfully added JsPsych integrated keyboard listener
vzhang03 Oct 13, 2024
b302f12
Successfully refactored mode 1 and 2 to be simpler and more efficient
vzhang03 Oct 13, 2024
2c321e4
Completed mode 3, moving onto ending the trial and data handling
vzhang03 Oct 13, 2024
af9dcd2
working data collection and end trial, updated examples to showfinal …
vzhang03 Oct 13, 2024
ebd4b0d
Documentation updates and fixing the modes refactoring code
vzhang03 Oct 18, 2024
51c1a67
Updated generateBlanks to account for chunks including multiple words
vzhang03 Oct 18, 2024
e1c16fc
Finished splitting parameters, finalizing documentation
vzhang03 Oct 18, 2024
4c57593
Tested styling to be workign
vzhang03 Oct 18, 2024
c44db43
Finished up documentation for the SPR md file
vzhang03 Oct 18, 2024
273ed1a
Finishing up documentation within the trial parameters
vzhang03 Oct 18, 2024
30769b4
Pushing changeset
vzhang03 Oct 18, 2024
7e7303b
Added initial data model, missing docuemntation
vzhang03 Oct 21, 2024
66a5ab4
Initial delimiter model
vzhang03 Oct 21, 2024
fb8d507
fix desc and update to v8
jadeddelta Oct 23, 2024
39c28de
flesh out docs and add choices param
jadeddelta Oct 26, 2024
23e839b
remove delimiter param, added logic to handle it instead
jadeddelta Oct 26, 2024
bcfa327
Finished up majority of data saving - issues with extra and keypress …
vzhang03 Nov 1, 2024
ca8830e
Key press is working, time elapsed since last is working, only now is…
vzhang03 Nov 2, 2024
57bb8f2
document data types, fix stimulus data issue
jadeddelta Nov 3, 2024
241aac9
Fixed mode three, removed unneccessary comments and slight logic changes
vzhang03 Nov 4, 2024
644436f
cleanup code, add some error handling, revise docs
jadeddelta Feb 1, 2025
88cd637
code rewrite, consolidate text input and remove `line_size`
jadeddelta Feb 2, 2025
617aa02
cleanup docs, handle edge cases in mode 2 and 3
jadeddelta Feb 2, 2025
44373fc
fix sentence presentation to match size of words
jadeddelta Feb 4, 2025
d9843e0
get some basic tests out there
jadeddelta Feb 27, 2025
d580a4a
added gap character btwn masked words
jadeddelta May 18, 2025
cd55630
added intra-gap character toggle and newline handling
jadeddelta May 21, 2025
3e45a6c
got some delimiter tests
jadeddelta May 29, 2025
d33aaf6
revise docs, flesh out examples, add custom masks
jadeddelta Jun 1, 2025
b76eb4f
noted spr vs self-paced-reading plugins
jadeddelta Jun 1, 2025
45f5757
fix changeset
jadeddelta Jun 13, 2025
fe6e92a
fix type in canvas spr example
jadeddelta Jun 14, 2025
c5d150e
port inter_word_interval to spr
jadeddelta Jun 20, 2025
0a3c531
downgrade to pre-release via minor changeset
jadeddelta Oct 10, 2025
1174076
Merge remote-tracking branch 'origin/main' into pr/vzhang03/145
jadeddelta Oct 10, 2025
4221ab0
regenerate lockfile
jadeddelta Oct 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/rich-tomatoes-marry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@jspsych-contrib/plugin-spr": minor
---

initial version of self-paced reading (DOM version) plugin
6,028 changes: 2,148 additions & 3,880 deletions package-lock.json

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions packages/plugin-self-paced-reading/examples/example2.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
// defaults
for (let sent_num = 0; sent_num < sentences.length; sent_num++) {
let moving_window = {
type: SelfPacedReadingPlugin,
type: jsPsychSelfPacedReading,
sentence: sentences[sent_num],
mask_type: 2,
};
Expand All @@ -43,7 +43,7 @@
// change some mask properties
for (let sent_num = 0; sent_num < sentences.length; sent_num++) {
let moving_window = {
type: SelfPacedReadingPlugin,
type: jsPsychSelfPacedReading,
sentence: sentences[sent_num],
mask_type: 2,
mask_gap_character: '|',
Expand All @@ -68,7 +68,7 @@
// multiline text
for (let sent_num = 0; sent_num < sentences_with_line_breaks.length; sent_num++) {
let moving_window = {
type: SelfPacedReadingPlugin,
type: jsPsychSelfPacedReading,
sentence: sentences_with_line_breaks[sent_num],
mask_type: 2,
mask_on_word: true,
Expand Down
38 changes: 38 additions & 0 deletions packages/plugin-spr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# spr

## Overview

This is a plugin built to conduct self-paced reading trials. It supports different reading modes including single-word mask and multi-word (hide/show). This package allows you to also customize the format of the text displayed by using CSS, and how many words display per key press.

### Why `plugin-spr` over `plugin-self-paced-reading`?
This plugin primarily works within the context of the DOM- allowing for a much more flexible usage of this plugin with respect to differing screen sizes. On the other hand, `plugin-self-paced-reading` primarily handles appearance via `<canvas>` elements, which are much more strict, appearing as the same for all screen sizes. However, `plugin-self-paced-reading` provides an easier way to customize the look of your plugin through parameters, while `plugin-spr`, outside of the parameters given, require you to inject CSS classes to better customize.

## Loading

### In browser

```js
<script src="https://unpkg.com/@jspsych-contrib/[email protected]"></script>
```

### Via NPM

```
npm install @jspsych-contrib/plugin-spr
```

```js
import jsPsychSpr from '@jspsych-contrib/plugin-spr';
```

## Compatibility

jsPsych 8.0.0

## Documentation

See [documentation](https://github.com/jspsych/jspsych-contrib/blob/main/packages/plugin-spr/docs/jspsych-spr.md)

## Author / Citation

Initial sketch by [Victor Zhang](https://github.com/vzhang03). Full implementation by [jade](https://github.com/jadeddelta).
91 changes: 91 additions & 0 deletions packages/plugin-spr/docs/spr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# spr

The self-paced-reading plugin displays a text with three distinct modes of word masking
to allow for varied modes of self paced reading trials.

## Parameters

In addition to the [parameters available in all plugins](https://jspsych.org/latest/overview/plugins.md#parameters-available-in-all-plugins), this plugin accepts the following parameters. Parameters with a default value of undefined must be specified. Other parameters can be left unspecified if the default value is acceptable.

| Parameter | Type | Default Value | Description |
| ------------------- | ---------------- | ------------------ | ---------------------------------------- |
| sentence | string | `undefined` | This is the string of text that will be displayed to the participant during the trial. The text will be split up into segments based on either the space bar or the `delimiter` character if it is present. Line breaks can be displayed, but must be attached to the start or end of a word, not as its own word. |
| delimiter | string | `"^"` | If this character is present in the `sentence` parameter, the text will be split up based on the `delimiter` character. If the `delimiter` character is not present, the text will be split up based on the space character. |
| mode | numeric | 1 | Indicates the mode of text displaying used by the SPR plugin. Mode 1 is a masked presentation where a valid key press hides the previous shown words, mode 2 reveals one chunk at time but the chunks but previous ones remain visible, and mode 3 is when one word is displayed with no mask. |
| segments_per_key_press | numeric | 1 | Indicates how many segments will be revealed upon a key press. |
| show_first_blank | bool | `true` | If `true`, everything will be blanked out initially, and the user will need to press a key to show the first segment. If `false`, the first segment will be shown immediately. |
| gap_character | string | `" "` | Character that will be used to separate each word of text. This is only used in mode 1 and 2. |
| intra_segment_character | bool | `true` | If `true`, the gap character will replace the space between segments. Otherwise, the gap character within a segment will be a space. |
| mask_character | string | `""` | If this character is not an empty string, it will fill the masked segments of the stimulus. This can be multiple characters, and will be repeated to fill the entire segment. It is important to note, this will force all text to be monospaced in order to ensure alignment. |
| mask_underline | bool | `true` | If `true`, the masked segments will be underlined. It's recommended to only make this `false` if the `mask_character` is not an empty string, as it will be difficult for participants to distinguish between the masked and unmasked segments. |
| choices | array of keys | `[" "]` | This array contains the key(s) that the participant is allowed to press in order to advance to the next chunk. Keys should be specified as characters (e.g., `'a'`, `'q'`, `' '`, `'Enter'`, `'ArrowDown'`) - see [this page](https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values) and [this page (event.key column)](https://www.freecodecamp.org/news/javascript-keycode-list-keypress-event-key-codes/) for more examples. Any key presses that are not listed in the array will be ignored. The value of `"ALL_KEYS"` means that all keys will be accepted as valid responses. |
| inter_word_interval | numeric | 0 | Delay in milliseconds between a valid key press and the next segment showing. |

## Data Generated

In addition to the [default data collected by all plugins](https://jspsych.org/latest/overview/plugins.md#data-collected-by-all-plugins), this plugin collects the following data for each trial.

| Name | Type | Value |
| --------- | ------- | ---------------------------------------- |
| stimulus | array of string | The individual segments that are displayed per key press. |
| mode | number | The mode that the trial was run with. |
| results | array of object | The results of the trial, sorted into objects with keys: <br> `results.rt`: The response time in milliseconds from when the stimulus was displayed to when a valid key was pressed. <br> `results.segment`: The segment that was displayed to the participant. <br> `results.key_pressed`: The key that was pressed by the participant. |

## Install

Using the CDN-hosted JavaScript file:

```js
<script src="https://unpkg.com/@jspsych-contrib/plugin-spr@3"></script>
```

Using the JavaScript file downloaded from a GitHub release dist archive:

```js
<script src="jspsych/plugin-spr.js"></script>
```

Using NPM:

```
npm install @jspsych-contrib/plugin-spr
```

```js
import Spr from '@jspsych-contrib/plugin-spr';
```

## Examples
A more exhaustive list of all the different parameters you can use are found in the `examples` folder.

### Self-paced-reading trial with simple word-by-word masking

```javascript
const trial = {
type: jsPsychSpr,
sentence: "The quick brown fox jumps over the lazy dog.",
mode: 1,
};
```

### Custom unmasking of words via delimiter

```javascript
const trial = {
type: jsPsychSpr,
sentence: "Portez^ce vieux whisky^au juge blond^qui fume.",
mode: 1
};
```

### Display two words at a time with no other mask present

```javascript
const trial = {
type: jsPsychSpr,
sentence: "Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich.",
mode: 3,
segments_per_key_press: 2
}
```

49 changes: 49 additions & 0 deletions packages/plugin-spr/examples/mode1.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<!DOCTYPE html>
<html>

<head>
<script src="https://unpkg.com/jspsych"></script>
<!-- The plugin is loaded here -->
<script src="https://unpkg.com/@jspsych/plugin-spr"></script>
<script src="../dist/index.browser.js"></script>
<link rel="stylesheet" href="https://unpkg.com/jspsych/css/jspsych.css">
</head>

<body></body>
<script>
const jsPsych = initJsPsych({
on_finish: async function() {
jsPsych.data.displayData();
},
default_iti: 250
});

// basic trial, each key press advances the sentence by one word (segment)
// mode 1: all segments after the current segment are masked
const regular_trial = {
type: jsPsychSpr,
sentence: "The quick brown fox jumps over the lazy dog.",
mode: 1,
}

// the delimiter "^" is used to separate words into segments
const delimiter_trial = {
type: jsPsychSpr,
sentence: "Portez^ce vieux whisky^au juge blond^qui fume.",
mode: 1,
}

// each key press will advance the trial by two segments.
const segment_size_trial = {
type: jsPsychSpr,
sentence: "Victor jagt zwölf Boxkämpfer quer über den großen Sylter Deich.",
mode: 1,
segments_per_key_press: 2,
gap_character: "|", // special gap character between each segment
intra_segment_character: false, // inside the segments, no character is shown
}

jsPsych.run([regular_trial, delimiter_trial, segment_size_trial]);
</script>

</html>
51 changes: 51 additions & 0 deletions packages/plugin-spr/examples/mode2.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>

<head>
<script src="https://unpkg.com/jspsych"></script>
<!-- The plugin is loaded here -->
<script src="https://unpkg.com/@jspsych/plugin-spr"></script>
<script src="../dist/index.browser.js"></script>
<link rel="stylesheet" href="https://unpkg.com/jspsych/css/jspsych.css">
</head>

<body></body>
<script>
const jsPsych = initJsPsych({
on_finish: async function() {
jsPsych.data.displayData();
},
default_iti: 250
});

// basic trial, each key press advances the sentence by one word (segment)
// mode 2: all segments after the current segment are unmasked.
const regular_trial = {
type: jsPsychSpr,
sentence: "D'ith cat mór dubh na héisc lofa go pras.",
mode: 2,
}

// you can change the delimiter to any character you like, including none
// for character-by-character presentation
const line_break_trial = {
type: jsPsychSpr,
sentence: "石室诗士施氏",
delimiter: "",
mode: 2,
}

// you can even use a custom mask character instead of underlines!
// note: this will force the fonts to be monospaced, to align the characters properly
const custom_mask_character = {
type: jsPsychSpr,
sentence: "Cantami o Diva del pelide Achille l'ira funesta.",
mode: 2,
mask_character: "X",
mask_underline: false // remove the underlines
}

jsPsych.run([regular_trial, line_break_trial, custom_mask_character]);
</script>

</html>
57 changes: 57 additions & 0 deletions packages/plugin-spr/examples/mode3.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
<!DOCTYPE html>
<html>

<head>
<script src="https://unpkg.com/jspsych"></script>
<!-- The plugin is loaded here -->
<script src="https://unpkg.com/@jspsych/plugin-spr"></script>
<script src="../dist/index.browser.js"></script>
<script src="https://unpkg.com/@jspsych/[email protected]"></script>
<link rel="stylesheet" href="https://unpkg.com/jspsych/css/jspsych.css">
</head>

<body></body>
<script>
const jsPsych = initJsPsych({
on_finish: async function() {
jsPsych.data.displayData();
},
default_iti: 250
});

// basic trial, each key press advances the sentence by one word (segment)
// mode 3: each segment is displayed individually, switching to the next on key press
const regular_trial = {
type: jsPsychSpr,
sentence: "Pa's wijze lynx bezag vroom het fikse aquaduct.",
mode: 3,
}

const choice_primer = {
type: jsPsychHtmlKeyboardResponse,
stimulus: '<p>Press F if the word has an even amount of characters, J if odd.<br>Press SPACE to continue on.</p>',
choices: [' ']
}

// you can have multiple choices per segment!
const choice_trial = {
type: jsPsychSpr,
sentence: "Příliš žluťoučký kůň úpěl ďábelské ódy.",
mode: 3,
choices: ["f", "j"],
show_first_blank: false // do not show the first segment as a blank
}

// for fun, you can also use this display method to display vertical lines bit by bit
const iroha_trial = {
type: jsPsychSpr,
sentence: "い\nろ\nは\nに\nほ\nへ\nと ち\nり\nぬ\nる\nを わ\nか\nよ\nた\nれ\nそ つ\nね\nな\nら\nむ う\nゐ\nの\nお\nく\nや\nま け\nふ\nこ\nえ\nて あ\nさ\nき\nゆ\nめ\nみ\nし ゑ\nひ\nも\nせ\nす",
mode: 3,
show_first_blank: false,
inter_word_interval: 500, // wait 500ms before a user can advance
}

jsPsych.run([regular_trial, choice_primer, choice_trial, iroha_trial]);
</script>

</html>
1 change: 1 addition & 0 deletions packages/plugin-spr/jest.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require("@jspsych/config/jest").makePackageConfig(__dirname);
53 changes: 53 additions & 0 deletions packages/plugin-spr/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"name": "@jspsych-contrib/plugin-spr",
"version": "0.0.1",
"description": "Self-paced reading trials using the DOM.",
"type": "module",
"main": "dist/index.cjs",
"exports": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
},
"typings": "dist/index.d.ts",
"unpkg": "dist/index.browser.min.js",
"files": [
"src",
"dist"
],
"source": "src/index.ts",
"scripts": {
"test": "jest",
"test:watch": "npm test -- --watch",
"tsc": "tsc",
"build": "rollup --config",
"build:watch": "npm run build -- --watch"
},
"repository": {
"type": "git",
"url": "git+https://github.com/jspsych/jspsych-contrib.git",
"directory": "packages/plugin-spr"
},
"author": {
"name": "jade",
"url": "https://github.com/jadeddelta"
},
"contributors": [
{
"name": "Victor Zhang",
"url": "https://github.com/vzhang03"
}
],
"license": "MIT",
"bugs": {
"url": "https://github.com/jspsych/jspsych-contrib/issues"
},
"homepage": "https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-spr",
"peerDependencies": {
"jspsych": ">=8.0.0"
},
"devDependencies": {
"@jspsych/config": "^3.0.0",
"@jspsych/test-utils": "^1.0.0",
"jspsych": "8.0.0"
}
}
3 changes: 3 additions & 0 deletions packages/plugin-spr/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { makeRollupConfig } from "@jspsych/config/rollup";

export default makeRollupConfig("jsPsychSpr");
Loading