@jspsych-contrib/plugin-trail-making 0.1.0 → 0.2.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.
@@ -1,7 +1,7 @@
1
1
  var jsPsychTrailMaking = (function (jspsych) {
2
2
  'use strict';
3
3
 
4
- var version = "0.1.0";
4
+ var version = "0.2.0";
5
5
 
6
6
  const info = {
7
7
  name: "trail-making",
@@ -117,6 +117,13 @@ var jsPsychTrailMaking = (function (jspsych) {
117
117
  type: jspsych.ParameterType.HTML_STRING,
118
118
  default: null
119
119
  },
120
+ /**
121
+ * Duration in milliseconds to wait after the last circle is clicked before ending the trial.
122
+ */
123
+ end_delay: {
124
+ type: jspsych.ParameterType.INT,
125
+ default: 500
126
+ },
120
127
  /**
121
128
  * Random seed for reproducible target layouts. If null, uses random seed.
122
129
  */
@@ -194,6 +201,9 @@ var jsPsychTrailMaking = (function (jspsych) {
194
201
  this.drawCanvas(ctx, targets, [], trial);
195
202
  const handleClick = (e) => {
196
203
  if (isShowingError) return;
204
+ if (e instanceof TouchEvent) {
205
+ e.preventDefault();
206
+ }
197
207
  const rect = canvas.getBoundingClientRect();
198
208
  let clientX, clientY;
199
209
  if (e instanceof TouchEvent) {
@@ -230,7 +240,9 @@ var jsPsychTrailMaking = (function (jspsych) {
230
240
  const visitedLabels = sequence.slice(0, currentTargetIndex);
231
241
  this.drawCanvas(ctx, targets, visitedLabels, trial);
232
242
  if (currentTargetIndex >= sequence.length) {
233
- this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);
243
+ this.jsPsych.pluginAPI.setTimeout(() => {
244
+ this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);
245
+ }, trial.end_delay);
234
246
  }
235
247
  } else {
236
248
  clickEvent.correct = false;
@@ -238,7 +250,8 @@ var jsPsychTrailMaking = (function (jspsych) {
238
250
  numErrors++;
239
251
  isShowingError = true;
240
252
  const visitedLabels = sequence.slice(0, currentTargetIndex);
241
- this.drawCanvas(ctx, targets, visitedLabels, trial, true);
253
+ const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;
254
+ this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);
242
255
  this.jsPsych.pluginAPI.setTimeout(() => {
243
256
  isShowingError = false;
244
257
  this.drawCanvas(ctx, targets, visitedLabels, trial);
@@ -337,7 +350,7 @@ var jsPsychTrailMaking = (function (jspsych) {
337
350
  }
338
351
  return null;
339
352
  }
340
- drawCanvas(ctx, targets, visitedLabels, trial, showError = false) {
353
+ drawCanvas(ctx, targets, visitedLabels, trial, errorLabel = null) {
341
354
  const width = trial.canvas_width;
342
355
  const height = trial.canvas_height;
343
356
  ctx.fillStyle = "#f5f5f5";
@@ -360,7 +373,7 @@ var jsPsychTrailMaking = (function (jspsych) {
360
373
  }
361
374
  for (const target of targets) {
362
375
  const isVisited = visitedLabels.includes(target.label);
363
- if (showError && !isVisited) {
376
+ if (errorLabel !== null && target.label === errorLabel) {
364
377
  ctx.fillStyle = trial.error_color;
365
378
  } else if (isVisited) {
366
379
  ctx.fillStyle = trial.visited_color;
@@ -406,4 +419,4 @@ var jsPsychTrailMaking = (function (jspsych) {
406
419
  return TrailMakingPlugin;
407
420
 
408
421
  })(jsPsychModule);
409
- //# sourceMappingURL=https://unpkg.com/@jspsych-contrib/plugin-trail-making@0.1.0/dist/index.browser.js.map
422
+ //# sourceMappingURL=https://unpkg.com/@jspsych-contrib/plugin-trail-making@0.2.0/dist/index.browser.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.1.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial, true);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n showError: boolean = false\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (showError && !isVisited) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":["ParameterType"],"mappings":"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"}
1
+ {"version":3,"file":"index.browser.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.2.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Duration in milliseconds to wait after the last circle is clicked before ending the trial.\n */\n end_delay: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n // Prevent the synthetic click event that browsers fire after touchend\n if (e instanceof TouchEvent) {\n e.preventDefault();\n }\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }, trial.end_delay);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;\n this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n errorLabel: string | null = null\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (errorLabel !== null && target.label === errorLabel) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":["ParameterType"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- var jsPsychTrailMaking=function(i){"use strict";var N="0.1.0",T=Math.pow;const k={name:"trail-making",version:N,parameters:{test_type:{type:i.ParameterType.STRING,default:"A"},num_targets:{type:i.ParameterType.INT,default:25},canvas_width:{type:i.ParameterType.INT,default:600},canvas_height:{type:i.ParameterType.INT,default:600},target_radius:{type:i.ParameterType.INT,default:25},min_separation:{type:i.ParameterType.INT,default:80},target_color:{type:i.ParameterType.STRING,default:"#ffffff"},target_border_color:{type:i.ParameterType.STRING,default:"#000000"},visited_color:{type:i.ParameterType.STRING,default:"#90EE90"},line_color:{type:i.ParameterType.STRING,default:"#000000"},line_width:{type:i.ParameterType.INT,default:2},error_color:{type:i.ParameterType.STRING,default:"#FF6B6B"},error_duration:{type:i.ParameterType.INT,default:500},targets:{type:i.ParameterType.COMPLEX,default:null},prompt:{type:i.ParameterType.HTML_STRING,default:null},seed:{type:i.ParameterType.INT,default:null}},data:{test_type:{type:i.ParameterType.STRING},targets:{type:i.ParameterType.COMPLEX,array:!0},clicks:{type:i.ParameterType.COMPLEX,array:!0},completion_time:{type:i.ParameterType.INT},num_errors:{type:i.ParameterType.INT},total_path_distance:{type:i.ParameterType.FLOAT},inter_click_times:{type:i.ParameterType.INT,array:!0}}};class I{constructor(t){this.jsPsych=t}trial(t,l){const r=l.targets?l.targets:this.generateTargets(l),a=this.getCorrectSequence(l.test_type,r);let n=0,c=null,e=null;const o=[],d=[];let h=0,u=!1;const f=document.createElement("div");if(f.style.cssText="display: flex; flex-direction: column; align-items: center;",l.prompt){const m=document.createElement("div");m.innerHTML=l.prompt,m.style.marginBottom="10px",f.appendChild(m)}const s=document.createElement("canvas");s.width=l.canvas_width,s.height=l.canvas_height,s.style.cssText="border: 1px solid #ccc; cursor: pointer; touch-action: none;",f.appendChild(s),t.appendChild(f);const p=s.getContext("2d");this.drawCanvas(p,r,[],l);const y=m=>{if(u)return;const x=s.getBoundingClientRect();let M,b;m instanceof TouchEvent?(M=m.changedTouches[0].clientX,b=m.changedTouches[0].clientY):(M=m.clientX,b=m.clientY);const S=M-x.left,C=b-x.top,g=performance.now();c===null&&(c=g);const _=this.findClickedTarget(S,C,r,l.target_radius),w=a[n],P={target_index:_,label:_!==null?r[_].label:null,time:Math.round(g-c),x:Math.round(S),y:Math.round(C),correct:!1};if(_!==null&&r[_].label===w){P.correct=!0,e!==null&&d.push(Math.round(g-e)),e=g,n++,o.push(P);const v=a.slice(0,n);this.drawCanvas(p,r,v,l),n>=a.length&&this.endTrial(r,o,c,g,h,d,l)}else{P.correct=!1,o.push(P),h++,u=!0;const v=a.slice(0,n);this.drawCanvas(p,r,v,l,!0),this.jsPsych.pluginAPI.setTimeout(()=>{u=!1,this.drawCanvas(p,r,v,l)},l.error_duration)}};s.addEventListener("click",y),s.addEventListener("touchend",y)}generateTargets(t){var l;const r=this.generateLabels(t.test_type,t.num_targets),a=[],n=t.target_radius+10,c=1e3;let e=(l=t.seed)!=null?l:Math.floor(Math.random()*1e6);const o=()=>(e=e*1103515245+12345&2147483647,e/2147483647);for(const d of r){let h=!1,u=0;for(;!h&&u<c;){const f=n+o()*(t.canvas_width-2*n),s=n+o()*(t.canvas_height-2*n);let p=!0;for(const y of a)if(Math.sqrt(T(f-y.x,2)+T(s-y.y,2))<t.min_separation){p=!1;break}p&&(a.push({x:Math.round(f),y:Math.round(s),label:d}),h=!0),u++}if(!h){const f=n+o()*(t.canvas_width-2*n),s=n+o()*(t.canvas_height-2*n);a.push({x:Math.round(f),y:Math.round(s),label:d})}}return a}generateLabels(t,l){const r=[];if(t==="A")for(let a=1;a<=l;a++)r.push(a.toString());else if(t==="B"){const a=[],n=[],c=Math.floor(l/2);for(let e=1;e<=c;e++)a.push(e.toString());for(let e=0;e<c;e++)n.push(String.fromCharCode(65+e));for(let e=0;e<c;e++)r.push(a[e]),r.push(n[e]);l%2===1&&r.push((c+1).toString())}return r}getCorrectSequence(t,l){if(t==="A")return l.map(r=>r.label).sort((r,a)=>parseInt(r)-parseInt(a));{const r=l.filter(e=>/^\d+$/.test(e.label)).map(e=>e.label).sort((e,o)=>parseInt(e)-parseInt(o)),a=l.filter(e=>/^[A-Z]$/.test(e.label)).map(e=>e.label).sort(),n=[],c=Math.max(r.length,a.length);for(let e=0;e<c;e++)e<r.length&&n.push(r[e]),e<a.length&&n.push(a[e]);return n}}findClickedTarget(t,l,r,a){const n=a+5;for(let c=0;c<r.length;c++)if(Math.sqrt(T(t-r[c].x,2)+T(l-r[c].y,2))<=n)return c;return null}drawCanvas(t,l,r,a,n=!1){const c=a.canvas_width,e=a.canvas_height;if(t.fillStyle="#f5f5f5",t.fillRect(0,0,c,e),r.length>1){t.strokeStyle=a.line_color,t.lineWidth=a.line_width,t.beginPath();for(let o=0;o<r.length;o++){const d=l.find(h=>h.label===r[o]);d&&(o===0?t.moveTo(d.x,d.y):t.lineTo(d.x,d.y))}t.stroke()}for(const o of l){const d=r.includes(o.label);n&&!d?t.fillStyle=a.error_color:d?t.fillStyle=a.visited_color:t.fillStyle=a.target_color,t.beginPath(),t.arc(o.x,o.y,a.target_radius,0,Math.PI*2),t.fill(),t.strokeStyle=a.target_border_color,t.lineWidth=2,t.stroke(),t.fillStyle="#000000",t.font=`bold ${Math.round(a.target_radius*.8)}px Arial`,t.textAlign="center",t.textBaseline="middle",t.fillText(o.label,o.x,o.y)}}endTrial(t,l,r,a,n,c,e){const o=l.filter(u=>u.correct);let d=0;for(let u=1;u<o.length;u++){const f=t.find(p=>p.label===o[u-1].label),s=t.find(p=>p.label===o[u].label);f&&s&&(d+=Math.sqrt(T(s.x-f.x,2)+T(s.y-f.y,2)))}const h={test_type:e.test_type,targets:t,clicks:l,completion_time:Math.round(a-r),num_errors:n,total_path_distance:Math.round(d*100)/100,inter_click_times:c};this.jsPsych.finishTrial(h)}}return I.info=k,I}(jsPsychModule);
2
- //# sourceMappingURL=https://unpkg.com/@jspsych-contrib/plugin-trail-making@0.1.0/dist/index.browser.min.js.map
1
+ var jsPsychTrailMaking=function(i){"use strict";var C="0.2.0",y=Math.pow;const w={name:"trail-making",version:C,parameters:{test_type:{type:i.ParameterType.STRING,default:"A"},num_targets:{type:i.ParameterType.INT,default:25},canvas_width:{type:i.ParameterType.INT,default:600},canvas_height:{type:i.ParameterType.INT,default:600},target_radius:{type:i.ParameterType.INT,default:25},min_separation:{type:i.ParameterType.INT,default:80},target_color:{type:i.ParameterType.STRING,default:"#ffffff"},target_border_color:{type:i.ParameterType.STRING,default:"#000000"},visited_color:{type:i.ParameterType.STRING,default:"#90EE90"},line_color:{type:i.ParameterType.STRING,default:"#000000"},line_width:{type:i.ParameterType.INT,default:2},error_color:{type:i.ParameterType.STRING,default:"#FF6B6B"},error_duration:{type:i.ParameterType.INT,default:500},targets:{type:i.ParameterType.COMPLEX,default:null},prompt:{type:i.ParameterType.HTML_STRING,default:null},end_delay:{type:i.ParameterType.INT,default:500},seed:{type:i.ParameterType.INT,default:null}},data:{test_type:{type:i.ParameterType.STRING},targets:{type:i.ParameterType.COMPLEX,array:!0},clicks:{type:i.ParameterType.COMPLEX,array:!0},completion_time:{type:i.ParameterType.INT},num_errors:{type:i.ParameterType.INT},total_path_distance:{type:i.ParameterType.FLOAT},inter_click_times:{type:i.ParameterType.INT,array:!0}}};class M{constructor(t){this.jsPsych=t}trial(t,n){const a=n.targets?n.targets:this.generateTargets(n),r=this.getCorrectSequence(n.test_type,a);let l=0,s=null,e=null;const o=[],d=[];let T=0,u=!1;const f=document.createElement("div");if(f.style.cssText="display: flex; flex-direction: column; align-items: center;",n.prompt){const h=document.createElement("div");h.innerHTML=n.prompt,h.style.marginBottom="10px",f.appendChild(h)}const c=document.createElement("canvas");c.width=n.canvas_width,c.height=n.canvas_height,c.style.cssText="border: 1px solid #ccc; cursor: pointer; touch-action: none;",f.appendChild(c),t.appendChild(f);const p=c.getContext("2d");this.drawCanvas(p,a,[],n);const g=h=>{if(u)return;h instanceof TouchEvent&&h.preventDefault();const x=c.getBoundingClientRect();let b,I;h instanceof TouchEvent?(b=h.changedTouches[0].clientX,I=h.changedTouches[0].clientY):(b=h.clientX,I=h.clientY);const S=b-x.left,N=I-x.top,_=performance.now();s===null&&(s=_);const m=this.findClickedTarget(S,N,a,n.target_radius),k=r[l],P={target_index:m,label:m!==null?a[m].label:null,time:Math.round(_-s),x:Math.round(S),y:Math.round(N),correct:!1};if(m!==null&&a[m].label===k){P.correct=!0,e!==null&&d.push(Math.round(_-e)),e=_,l++,o.push(P);const v=r.slice(0,l);this.drawCanvas(p,a,v,n),l>=r.length&&this.jsPsych.pluginAPI.setTimeout(()=>{this.endTrial(a,o,s,_,T,d,n)},n.end_delay)}else{P.correct=!1,o.push(P),T++,u=!0;const v=r.slice(0,l),E=m!==null?a[m].label:null;this.drawCanvas(p,a,v,n,E),this.jsPsych.pluginAPI.setTimeout(()=>{u=!1,this.drawCanvas(p,a,v,n)},n.error_duration)}};c.addEventListener("click",g),c.addEventListener("touchend",g)}generateTargets(t){var n;const a=this.generateLabels(t.test_type,t.num_targets),r=[],l=t.target_radius+10,s=1e3;let e=(n=t.seed)!=null?n:Math.floor(Math.random()*1e6);const o=()=>(e=e*1103515245+12345&2147483647,e/2147483647);for(const d of a){let T=!1,u=0;for(;!T&&u<s;){const f=l+o()*(t.canvas_width-2*l),c=l+o()*(t.canvas_height-2*l);let p=!0;for(const g of r)if(Math.sqrt(y(f-g.x,2)+y(c-g.y,2))<t.min_separation){p=!1;break}p&&(r.push({x:Math.round(f),y:Math.round(c),label:d}),T=!0),u++}if(!T){const f=l+o()*(t.canvas_width-2*l),c=l+o()*(t.canvas_height-2*l);r.push({x:Math.round(f),y:Math.round(c),label:d})}}return r}generateLabels(t,n){const a=[];if(t==="A")for(let r=1;r<=n;r++)a.push(r.toString());else if(t==="B"){const r=[],l=[],s=Math.floor(n/2);for(let e=1;e<=s;e++)r.push(e.toString());for(let e=0;e<s;e++)l.push(String.fromCharCode(65+e));for(let e=0;e<s;e++)a.push(r[e]),a.push(l[e]);n%2===1&&a.push((s+1).toString())}return a}getCorrectSequence(t,n){if(t==="A")return n.map(a=>a.label).sort((a,r)=>parseInt(a)-parseInt(r));{const a=n.filter(e=>/^\d+$/.test(e.label)).map(e=>e.label).sort((e,o)=>parseInt(e)-parseInt(o)),r=n.filter(e=>/^[A-Z]$/.test(e.label)).map(e=>e.label).sort(),l=[],s=Math.max(a.length,r.length);for(let e=0;e<s;e++)e<a.length&&l.push(a[e]),e<r.length&&l.push(r[e]);return l}}findClickedTarget(t,n,a,r){const l=r+5;for(let s=0;s<a.length;s++)if(Math.sqrt(y(t-a[s].x,2)+y(n-a[s].y,2))<=l)return s;return null}drawCanvas(t,n,a,r,l=null){const s=r.canvas_width,e=r.canvas_height;if(t.fillStyle="#f5f5f5",t.fillRect(0,0,s,e),a.length>1){t.strokeStyle=r.line_color,t.lineWidth=r.line_width,t.beginPath();for(let o=0;o<a.length;o++){const d=n.find(T=>T.label===a[o]);d&&(o===0?t.moveTo(d.x,d.y):t.lineTo(d.x,d.y))}t.stroke()}for(const o of n){const d=a.includes(o.label);l!==null&&o.label===l?t.fillStyle=r.error_color:d?t.fillStyle=r.visited_color:t.fillStyle=r.target_color,t.beginPath(),t.arc(o.x,o.y,r.target_radius,0,Math.PI*2),t.fill(),t.strokeStyle=r.target_border_color,t.lineWidth=2,t.stroke(),t.fillStyle="#000000",t.font=`bold ${Math.round(r.target_radius*.8)}px Arial`,t.textAlign="center",t.textBaseline="middle",t.fillText(o.label,o.x,o.y)}}endTrial(t,n,a,r,l,s,e){const o=n.filter(u=>u.correct);let d=0;for(let u=1;u<o.length;u++){const f=t.find(p=>p.label===o[u-1].label),c=t.find(p=>p.label===o[u].label);f&&c&&(d+=Math.sqrt(y(c.x-f.x,2)+y(c.y-f.y,2)))}const T={test_type:e.test_type,targets:t,clicks:n,completion_time:Math.round(r-a),num_errors:l,total_path_distance:Math.round(d*100)/100,inter_click_times:s};this.jsPsych.finishTrial(T)}}return M.info=w,M}(jsPsychModule);
2
+ //# sourceMappingURL=https://unpkg.com/@jspsych-contrib/plugin-trail-making@0.2.0/dist/index.browser.min.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.min.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.1.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial, true);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n showError: boolean = false\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (showError && !isVisited) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":["version","m","info","ParameterType","TrailMakingPlugin","jsPsych","display_element","trial","targets","sequence","currentTargetIndex","startTime","lastClickTime","clicks","interClickTimes","numErrors","isShowingError","container","promptDiv","canvas","ctx","handleClick","e","rect","clientX","clientY","x","y","now","clickedTargetIndex","expectedLabel","clickEvent","visitedLabels","_a","labels","padding","maxAttempts","seed","random","label","placed","attempts","validPosition","target","__pow","type","numTargets","i","numbers","letters","numPairs","t","a","b","maxLen","radius","hitRadius","showError","width","height","isVisited","endTime","correctClicks","c","totalPathDistance","prev","curr","trial_data"],"mappings":"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"}
1
+ {"version":3,"file":"index.browser.min.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.2.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Duration in milliseconds to wait after the last circle is clicked before ending the trial.\n */\n end_delay: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n // Prevent the synthetic click event that browsers fire after touchend\n if (e instanceof TouchEvent) {\n e.preventDefault();\n }\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }, trial.end_delay);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;\n this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n errorLabel: string | null = null\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (errorLabel !== null && target.label === errorLabel) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":["version","info","ParameterType","TrailMakingPlugin","jsPsych","display_element","trial","targets","sequence","currentTargetIndex","startTime","lastClickTime","clicks","interClickTimes","numErrors","isShowingError","container","promptDiv","canvas","ctx","handleClick","e","rect","clientX","clientY","x","y","now","clickedTargetIndex","expectedLabel","clickEvent","visitedLabels","errorLabel","_a","labels","padding","maxAttempts","seed","random","label","placed","attempts","validPosition","target","__pow","type","numTargets","i","numbers","letters","numPairs","t","a","b","maxLen","radius","hitRadius","width","height","isVisited","endTime","correctClicks","c","totalPathDistance","prev","curr","trial_data"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  var jspsych = require('jspsych');
4
4
 
5
- var version = "0.1.0";
5
+ var version = "0.2.0";
6
6
 
7
7
  const info = {
8
8
  name: "trail-making",
@@ -118,6 +118,13 @@ const info = {
118
118
  type: jspsych.ParameterType.HTML_STRING,
119
119
  default: null
120
120
  },
121
+ /**
122
+ * Duration in milliseconds to wait after the last circle is clicked before ending the trial.
123
+ */
124
+ end_delay: {
125
+ type: jspsych.ParameterType.INT,
126
+ default: 500
127
+ },
121
128
  /**
122
129
  * Random seed for reproducible target layouts. If null, uses random seed.
123
130
  */
@@ -195,6 +202,9 @@ class TrailMakingPlugin {
195
202
  this.drawCanvas(ctx, targets, [], trial);
196
203
  const handleClick = (e) => {
197
204
  if (isShowingError) return;
205
+ if (e instanceof TouchEvent) {
206
+ e.preventDefault();
207
+ }
198
208
  const rect = canvas.getBoundingClientRect();
199
209
  let clientX, clientY;
200
210
  if (e instanceof TouchEvent) {
@@ -231,7 +241,9 @@ class TrailMakingPlugin {
231
241
  const visitedLabels = sequence.slice(0, currentTargetIndex);
232
242
  this.drawCanvas(ctx, targets, visitedLabels, trial);
233
243
  if (currentTargetIndex >= sequence.length) {
234
- this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);
244
+ this.jsPsych.pluginAPI.setTimeout(() => {
245
+ this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);
246
+ }, trial.end_delay);
235
247
  }
236
248
  } else {
237
249
  clickEvent.correct = false;
@@ -239,7 +251,8 @@ class TrailMakingPlugin {
239
251
  numErrors++;
240
252
  isShowingError = true;
241
253
  const visitedLabels = sequence.slice(0, currentTargetIndex);
242
- this.drawCanvas(ctx, targets, visitedLabels, trial, true);
254
+ const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;
255
+ this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);
243
256
  this.jsPsych.pluginAPI.setTimeout(() => {
244
257
  isShowingError = false;
245
258
  this.drawCanvas(ctx, targets, visitedLabels, trial);
@@ -338,7 +351,7 @@ class TrailMakingPlugin {
338
351
  }
339
352
  return null;
340
353
  }
341
- drawCanvas(ctx, targets, visitedLabels, trial, showError = false) {
354
+ drawCanvas(ctx, targets, visitedLabels, trial, errorLabel = null) {
342
355
  const width = trial.canvas_width;
343
356
  const height = trial.canvas_height;
344
357
  ctx.fillStyle = "#f5f5f5";
@@ -361,7 +374,7 @@ class TrailMakingPlugin {
361
374
  }
362
375
  for (const target of targets) {
363
376
  const isVisited = visitedLabels.includes(target.label);
364
- if (showError && !isVisited) {
377
+ if (errorLabel !== null && target.label === errorLabel) {
365
378
  ctx.fillStyle = trial.error_color;
366
379
  } else if (isVisited) {
367
380
  ctx.fillStyle = trial.visited_color;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.1.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial, true);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n showError: boolean = false\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (showError && !isVisited) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":["ParameterType"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.2.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Duration in milliseconds to wait after the last circle is clicked before ending the trial.\n */\n end_delay: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n // Prevent the synthetic click event that browsers fire after touchend\n if (e instanceof TouchEvent) {\n e.preventDefault();\n }\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }, trial.end_delay);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;\n this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n errorLabel: string | null = null\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (errorLabel !== null && target.label === errorLabel) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":["ParameterType"],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -114,6 +114,13 @@ declare const info: {
114
114
  readonly type: ParameterType.HTML_STRING;
115
115
  readonly default: any;
116
116
  };
117
+ /**
118
+ * Duration in milliseconds to wait after the last circle is clicked before ending the trial.
119
+ */
120
+ readonly end_delay: {
121
+ readonly type: ParameterType.INT;
122
+ readonly default: 500;
123
+ };
117
124
  /**
118
125
  * Random seed for reproducible target layouts. If null, uses random seed.
119
126
  */
@@ -286,6 +293,13 @@ declare class TrailMakingPlugin implements JsPsychPlugin<Info> {
286
293
  readonly type: ParameterType.HTML_STRING;
287
294
  readonly default: any;
288
295
  };
296
+ /**
297
+ * Duration in milliseconds to wait after the last circle is clicked before ending the trial.
298
+ */
299
+ readonly end_delay: {
300
+ readonly type: ParameterType.INT;
301
+ readonly default: 500;
302
+ };
289
303
  /**
290
304
  * Random seed for reproducible target layouts. If null, uses random seed.
291
305
  */
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ParameterType } from 'jspsych';
2
2
 
3
- var version = "0.1.0";
3
+ var version = "0.2.0";
4
4
 
5
5
  const info = {
6
6
  name: "trail-making",
@@ -116,6 +116,13 @@ const info = {
116
116
  type: ParameterType.HTML_STRING,
117
117
  default: null
118
118
  },
119
+ /**
120
+ * Duration in milliseconds to wait after the last circle is clicked before ending the trial.
121
+ */
122
+ end_delay: {
123
+ type: ParameterType.INT,
124
+ default: 500
125
+ },
119
126
  /**
120
127
  * Random seed for reproducible target layouts. If null, uses random seed.
121
128
  */
@@ -193,6 +200,9 @@ class TrailMakingPlugin {
193
200
  this.drawCanvas(ctx, targets, [], trial);
194
201
  const handleClick = (e) => {
195
202
  if (isShowingError) return;
203
+ if (e instanceof TouchEvent) {
204
+ e.preventDefault();
205
+ }
196
206
  const rect = canvas.getBoundingClientRect();
197
207
  let clientX, clientY;
198
208
  if (e instanceof TouchEvent) {
@@ -229,7 +239,9 @@ class TrailMakingPlugin {
229
239
  const visitedLabels = sequence.slice(0, currentTargetIndex);
230
240
  this.drawCanvas(ctx, targets, visitedLabels, trial);
231
241
  if (currentTargetIndex >= sequence.length) {
232
- this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);
242
+ this.jsPsych.pluginAPI.setTimeout(() => {
243
+ this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);
244
+ }, trial.end_delay);
233
245
  }
234
246
  } else {
235
247
  clickEvent.correct = false;
@@ -237,7 +249,8 @@ class TrailMakingPlugin {
237
249
  numErrors++;
238
250
  isShowingError = true;
239
251
  const visitedLabels = sequence.slice(0, currentTargetIndex);
240
- this.drawCanvas(ctx, targets, visitedLabels, trial, true);
252
+ const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;
253
+ this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);
241
254
  this.jsPsych.pluginAPI.setTimeout(() => {
242
255
  isShowingError = false;
243
256
  this.drawCanvas(ctx, targets, visitedLabels, trial);
@@ -336,7 +349,7 @@ class TrailMakingPlugin {
336
349
  }
337
350
  return null;
338
351
  }
339
- drawCanvas(ctx, targets, visitedLabels, trial, showError = false) {
352
+ drawCanvas(ctx, targets, visitedLabels, trial, errorLabel = null) {
340
353
  const width = trial.canvas_width;
341
354
  const height = trial.canvas_height;
342
355
  ctx.fillStyle = "#f5f5f5";
@@ -359,7 +372,7 @@ class TrailMakingPlugin {
359
372
  }
360
373
  for (const target of targets) {
361
374
  const isVisited = visitedLabels.includes(target.label);
362
- if (showError && !isVisited) {
375
+ if (errorLabel !== null && target.label === errorLabel) {
363
376
  ctx.fillStyle = trial.error_color;
364
377
  } else if (isVisited) {
365
378
  ctx.fillStyle = trial.visited_color;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.1.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial, true);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n showError: boolean = false\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (showError && !isVisited) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sources":["../package.json","../src/index.ts"],"sourcesContent":["{\n \"name\": \"@jspsych-contrib/plugin-trail-making\",\n \"version\": \"0.2.0\",\n \"description\": \"A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).\",\n \"type\": \"module\",\n \"main\": \"dist/index.cjs\",\n \"exports\": {\n \"import\": \"./dist/index.js\",\n \"require\": \"./dist/index.cjs\"\n },\n \"typings\": \"dist/index.d.ts\",\n \"unpkg\": \"dist/index.browser.min.js\",\n \"files\": [\n \"src\",\n \"dist\"\n ],\n \"source\": \"src/index.ts\",\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"npm test -- --watch\",\n \"tsc\": \"tsc\",\n \"build\": \"rollup --config\",\n \"build:watch\": \"npm run build -- --watch\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"https://github.com/jspsych/jspsych-contrib.git\",\n \"directory\": \"packages/plugin-trail-making\"\n },\n \"keywords\": [\n \"jsPsych\",\n \"trail-making\",\n \"TMT\",\n \"neuropsychology\",\n \"cognitive-assessment\"\n ],\n \"author\": {\n \"name\": \"Josh de Leeuw\",\n \"url\": \"https://github.com/jodeleeuw\"\n },\n \"license\": \"MIT\",\n \"bugs\": {\n \"url\": \"https://github.com/jspsych/jspsych-contrib/issues\"\n },\n \"homepage\": \"https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making\",\n \"peerDependencies\": {\n \"jspsych\": \">=8.0.0\"\n },\n \"devDependencies\": {\n \"@jspsych/config\": \"^3.2.2\",\n \"@jspsych/test-utils\": \"^1.0.0\",\n \"jspsych\": \"^8.0.0\"\n }\n}\n","import { JsPsych, JsPsychPlugin, ParameterType, TrialType } from \"jspsych\";\n\nimport { version } from \"../package.json\";\n\nconst info = <const>{\n name: \"trail-making\",\n version: version,\n parameters: {\n /**\n * The type of trail making test to run.\n * \"A\" = numbers only (1-2-3-4...)\n * \"B\" = alternating numbers and letters (1-A-2-B-3-C...)\n */\n test_type: {\n type: ParameterType.STRING,\n default: \"A\",\n },\n /**\n * The number of targets to display. For type \"B\", this should be an even number\n * to have equal numbers and letters.\n */\n num_targets: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The width of the display area in pixels.\n */\n canvas_width: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The height of the display area in pixels.\n */\n canvas_height: {\n type: ParameterType.INT,\n default: 600,\n },\n /**\n * The radius of each target circle in pixels.\n */\n target_radius: {\n type: ParameterType.INT,\n default: 25,\n },\n /**\n * The minimum distance between target centers in pixels.\n */\n min_separation: {\n type: ParameterType.INT,\n default: 80,\n },\n /**\n * The color of unvisited target circles.\n */\n target_color: {\n type: ParameterType.STRING,\n default: \"#ffffff\",\n },\n /**\n * The border color of target circles.\n */\n target_border_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The color of visited (correctly clicked) target circles.\n */\n visited_color: {\n type: ParameterType.STRING,\n default: \"#90EE90\",\n },\n /**\n * The color of the line connecting targets.\n */\n line_color: {\n type: ParameterType.STRING,\n default: \"#000000\",\n },\n /**\n * The width of the connecting line in pixels.\n */\n line_width: {\n type: ParameterType.INT,\n default: 2,\n },\n /**\n * The color to flash when an error is made.\n */\n error_color: {\n type: ParameterType.STRING,\n default: \"#FF6B6B\",\n },\n /**\n * Duration in milliseconds to show error feedback.\n */\n error_duration: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Optional array of {x, y, label} objects to specify exact target positions.\n * If provided, overrides num_targets and random positioning.\n * Coordinates are in pixels from top-left of canvas.\n */\n targets: {\n type: ParameterType.COMPLEX,\n default: null,\n },\n /**\n * Text prompt displayed above the canvas.\n */\n prompt: {\n type: ParameterType.HTML_STRING,\n default: null,\n },\n /**\n * Duration in milliseconds to wait after the last circle is clicked before ending the trial.\n */\n end_delay: {\n type: ParameterType.INT,\n default: 500,\n },\n /**\n * Random seed for reproducible target layouts. If null, uses random seed.\n */\n seed: {\n type: ParameterType.INT,\n default: null,\n },\n },\n data: {\n /** The type of trail making test (\"A\" or \"B\"). */\n test_type: {\n type: ParameterType.STRING,\n },\n /** Array of target objects with x, y, and label properties. */\n targets: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Array of click events with target_index, label, time, x, y, and correct properties. */\n clicks: {\n type: ParameterType.COMPLEX,\n array: true,\n },\n /** Total time in milliseconds from first click to last correct click. */\n completion_time: {\n type: ParameterType.INT,\n },\n /** Number of errors (incorrect clicks). */\n num_errors: {\n type: ParameterType.INT,\n },\n /** Total path distance in pixels (sum of distances between consecutive correct targets). */\n total_path_distance: {\n type: ParameterType.FLOAT,\n },\n /** Array of response times between consecutive correct clicks. */\n inter_click_times: {\n type: ParameterType.INT,\n array: true,\n },\n },\n};\n\ntype Info = typeof info;\n\ninterface Target {\n x: number;\n y: number;\n label: string;\n}\n\ninterface ClickEvent {\n target_index: number | null;\n label: string | null;\n time: number;\n x: number;\n y: number;\n correct: boolean;\n}\n\n/**\n * **trail-making**\n *\n * A jsPsych plugin for the Trail Making Test (TMT), a neuropsychological test of\n * visual attention and task switching. Participants connect circles in sequence\n * as quickly as possible.\n *\n * Part A: Connect numbers in order (1-2-3-4...)\n * Part B: Alternate between numbers and letters (1-A-2-B-3-C...)\n *\n * @author Josh de Leeuw\n * @see {@link https://github.com/jspsych/jspsych-contrib/tree/main/packages/plugin-trail-making}\n */\nclass TrailMakingPlugin implements JsPsychPlugin<Info> {\n static info = info;\n\n constructor(private jsPsych: JsPsych) {}\n\n trial(display_element: HTMLElement, trial: TrialType<Info>) {\n // Generate or use provided targets\n const targets = trial.targets ? (trial.targets as Target[]) : this.generateTargets(trial);\n\n // Determine the correct sequence\n const sequence = this.getCorrectSequence(trial.test_type, targets);\n\n // State variables\n let currentTargetIndex = 0;\n let startTime: number | null = null;\n let lastClickTime: number | null = null;\n const clicks: ClickEvent[] = [];\n const interClickTimes: number[] = [];\n let numErrors = 0;\n let isShowingError = false;\n\n // Create display\n const container = document.createElement(\"div\");\n container.style.cssText = \"display: flex; flex-direction: column; align-items: center;\";\n\n if (trial.prompt) {\n const promptDiv = document.createElement(\"div\");\n promptDiv.innerHTML = trial.prompt;\n promptDiv.style.marginBottom = \"10px\";\n container.appendChild(promptDiv);\n }\n\n const canvas = document.createElement(\"canvas\");\n canvas.width = trial.canvas_width;\n canvas.height = trial.canvas_height;\n canvas.style.cssText = \"border: 1px solid #ccc; cursor: pointer; touch-action: none;\";\n container.appendChild(canvas);\n\n display_element.appendChild(container);\n\n const ctx = canvas.getContext(\"2d\")!;\n\n // Draw initial state\n this.drawCanvas(ctx, targets, [], trial);\n\n // Handle clicks/taps\n const handleClick = (e: MouseEvent | TouchEvent) => {\n if (isShowingError) return;\n\n // Prevent the synthetic click event that browsers fire after touchend\n if (e instanceof TouchEvent) {\n e.preventDefault();\n }\n\n const rect = canvas.getBoundingClientRect();\n let clientX: number, clientY: number;\n\n if (e instanceof TouchEvent) {\n clientX = e.changedTouches[0].clientX;\n clientY = e.changedTouches[0].clientY;\n } else {\n clientX = e.clientX;\n clientY = e.clientY;\n }\n\n const x = clientX - rect.left;\n const y = clientY - rect.top;\n const now = performance.now();\n\n // Start timing on first click\n if (startTime === null) {\n startTime = now;\n }\n\n // Find clicked target\n const clickedTargetIndex = this.findClickedTarget(x, y, targets, trial.target_radius);\n const expectedLabel = sequence[currentTargetIndex];\n\n const clickEvent: ClickEvent = {\n target_index: clickedTargetIndex,\n label: clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null,\n time: Math.round(now - startTime),\n x: Math.round(x),\n y: Math.round(y),\n correct: false,\n };\n\n if (clickedTargetIndex !== null && targets[clickedTargetIndex].label === expectedLabel) {\n // Correct click\n clickEvent.correct = true;\n\n if (lastClickTime !== null) {\n interClickTimes.push(Math.round(now - lastClickTime));\n }\n lastClickTime = now;\n\n currentTargetIndex++;\n clicks.push(clickEvent);\n\n // Redraw with updated visited targets\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n\n // Check if complete\n if (currentTargetIndex >= sequence.length) {\n this.jsPsych.pluginAPI.setTimeout(() => {\n this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);\n }, trial.end_delay);\n }\n } else {\n // Error\n clickEvent.correct = false;\n clicks.push(clickEvent);\n numErrors++;\n\n // Show error feedback\n isShowingError = true;\n const visitedLabels = sequence.slice(0, currentTargetIndex);\n const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;\n this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);\n\n this.jsPsych.pluginAPI.setTimeout(() => {\n isShowingError = false;\n this.drawCanvas(ctx, targets, visitedLabels, trial);\n }, trial.error_duration);\n }\n };\n\n canvas.addEventListener(\"click\", handleClick);\n canvas.addEventListener(\"touchend\", handleClick);\n }\n\n private generateTargets(trial: TrialType<Info>): Target[] {\n const labels = this.generateLabels(trial.test_type, trial.num_targets);\n const targets: Target[] = [];\n const padding = trial.target_radius + 10;\n const maxAttempts = 1000;\n\n // Simple seeded random for reproducibility\n let seed = trial.seed ?? Math.floor(Math.random() * 1000000);\n const random = () => {\n seed = (seed * 1103515245 + 12345) & 0x7fffffff;\n return seed / 0x7fffffff;\n };\n\n for (const label of labels) {\n let placed = false;\n let attempts = 0;\n\n while (!placed && attempts < maxAttempts) {\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n\n // Check distance from all existing targets\n let validPosition = true;\n for (const target of targets) {\n const dist = Math.sqrt((x - target.x) ** 2 + (y - target.y) ** 2);\n if (dist < trial.min_separation) {\n validPosition = false;\n break;\n }\n }\n\n if (validPosition) {\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n placed = true;\n }\n\n attempts++;\n }\n\n if (!placed) {\n // Fallback: place with reduced separation requirement\n const x = padding + random() * (trial.canvas_width - 2 * padding);\n const y = padding + random() * (trial.canvas_height - 2 * padding);\n targets.push({ x: Math.round(x), y: Math.round(y), label });\n }\n }\n\n return targets;\n }\n\n private generateLabels(type: string, numTargets: number): string[] {\n const labels: string[] = [];\n\n if (type === \"A\") {\n for (let i = 1; i <= numTargets; i++) {\n labels.push(i.toString());\n }\n } else if (type === \"B\") {\n const numbers = [];\n const letters = [];\n const numPairs = Math.floor(numTargets / 2);\n\n for (let i = 1; i <= numPairs; i++) {\n numbers.push(i.toString());\n }\n for (let i = 0; i < numPairs; i++) {\n letters.push(String.fromCharCode(65 + i)); // A, B, C, ...\n }\n\n // Interleave: 1, A, 2, B, 3, C, ...\n for (let i = 0; i < numPairs; i++) {\n labels.push(numbers[i]);\n labels.push(letters[i]);\n }\n\n // If odd number, add one more number\n if (numTargets % 2 === 1) {\n labels.push((numPairs + 1).toString());\n }\n }\n\n return labels;\n }\n\n private getCorrectSequence(type: string, targets: Target[]): string[] {\n // The correct sequence is the labels in order\n if (type === \"A\") {\n // Sort by numeric value\n return targets.map((t) => t.label).sort((a, b) => parseInt(a) - parseInt(b));\n } else {\n // For type B, sequence is 1, A, 2, B, 3, C, ...\n const numbers = targets\n .filter((t) => /^\\d+$/.test(t.label))\n .map((t) => t.label)\n .sort((a, b) => parseInt(a) - parseInt(b));\n const letters = targets\n .filter((t) => /^[A-Z]$/.test(t.label))\n .map((t) => t.label)\n .sort();\n\n const sequence: string[] = [];\n const maxLen = Math.max(numbers.length, letters.length);\n for (let i = 0; i < maxLen; i++) {\n if (i < numbers.length) sequence.push(numbers[i]);\n if (i < letters.length) sequence.push(letters[i]);\n }\n return sequence;\n }\n }\n\n private findClickedTarget(\n x: number,\n y: number,\n targets: Target[],\n radius: number\n ): number | null {\n // Add a small cushion (5px) around each target to make clicking easier\n const hitRadius = radius + 5;\n for (let i = 0; i < targets.length; i++) {\n const dist = Math.sqrt((x - targets[i].x) ** 2 + (y - targets[i].y) ** 2);\n if (dist <= hitRadius) {\n return i;\n }\n }\n return null;\n }\n\n private drawCanvas(\n ctx: CanvasRenderingContext2D,\n targets: Target[],\n visitedLabels: string[],\n trial: TrialType<Info>,\n errorLabel: string | null = null\n ) {\n const width = trial.canvas_width;\n const height = trial.canvas_height;\n\n // Clear canvas\n ctx.fillStyle = \"#f5f5f5\";\n ctx.fillRect(0, 0, width, height);\n\n // Draw lines between visited targets in sequence order\n if (visitedLabels.length > 1) {\n ctx.strokeStyle = trial.line_color;\n ctx.lineWidth = trial.line_width;\n ctx.beginPath();\n\n for (let i = 0; i < visitedLabels.length; i++) {\n const target = targets.find((t) => t.label === visitedLabels[i]);\n if (target) {\n if (i === 0) {\n ctx.moveTo(target.x, target.y);\n } else {\n ctx.lineTo(target.x, target.y);\n }\n }\n }\n ctx.stroke();\n }\n\n // Draw targets\n for (const target of targets) {\n const isVisited = visitedLabels.includes(target.label);\n\n // Set fill color before drawing\n if (errorLabel !== null && target.label === errorLabel) {\n ctx.fillStyle = trial.error_color;\n } else if (isVisited) {\n ctx.fillStyle = trial.visited_color;\n } else {\n ctx.fillStyle = trial.target_color;\n }\n\n // Circle\n ctx.beginPath();\n ctx.arc(target.x, target.y, trial.target_radius, 0, Math.PI * 2);\n ctx.fill();\n ctx.strokeStyle = trial.target_border_color;\n ctx.lineWidth = 2;\n ctx.stroke();\n\n // Label\n ctx.fillStyle = \"#000000\";\n ctx.font = `bold ${Math.round(trial.target_radius * 0.8)}px Arial`;\n ctx.textAlign = \"center\";\n ctx.textBaseline = \"middle\";\n ctx.fillText(target.label, target.x, target.y);\n }\n }\n\n private endTrial(\n targets: Target[],\n clicks: ClickEvent[],\n startTime: number,\n endTime: number,\n numErrors: number,\n interClickTimes: number[],\n trial: TrialType<Info>\n ) {\n // Calculate total path distance\n const correctClicks = clicks.filter((c) => c.correct);\n let totalPathDistance = 0;\n\n for (let i = 1; i < correctClicks.length; i++) {\n const prev = targets.find((t) => t.label === correctClicks[i - 1].label);\n const curr = targets.find((t) => t.label === correctClicks[i].label);\n if (prev && curr) {\n totalPathDistance += Math.sqrt((curr.x - prev.x) ** 2 + (curr.y - prev.y) ** 2);\n }\n }\n\n const trial_data = {\n test_type: trial.test_type,\n targets: targets,\n clicks: clicks,\n completion_time: Math.round(endTime - startTime),\n num_errors: numErrors,\n total_path_distance: Math.round(totalPathDistance * 100) / 100,\n inter_click_times: interClickTimes,\n };\n\n this.jsPsych.finishTrial(trial_data);\n }\n}\n\nexport default TrailMakingPlugin;\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jspsych-contrib/plugin-trail-making",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "A jsPsych plugin for the Trail Making Test (TMT), where participants connect circles in sequence. Supports Part A (numbers only) and Part B (alternating numbers and letters).",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
package/src/index.spec.ts CHANGED
@@ -208,6 +208,7 @@ describe("trail-making plugin", () => {
208
208
  canvas.dispatchEvent(clickEvent);
209
209
  }
210
210
 
211
+ jest.advanceTimersByTime(500);
211
212
  await expectFinished();
212
213
 
213
214
  const data = getData().values()[0];
@@ -280,6 +281,7 @@ describe("trail-making plugin", () => {
280
281
  })
281
282
  );
282
283
 
284
+ jest.advanceTimersByTime(500);
283
285
  await expectFinished();
284
286
 
285
287
  const data = getData().values()[0];
@@ -336,6 +338,7 @@ describe("trail-making plugin", () => {
336
338
  jest.advanceTimersByTime(300);
337
339
  canvas.dispatchEvent(new MouseEvent("click", { clientX: 300, clientY: 300, bubbles: true }));
338
340
 
341
+ jest.advanceTimersByTime(500);
339
342
  await expectFinished();
340
343
 
341
344
  const data = getData().values()[0];
@@ -378,6 +381,7 @@ describe("trail-making plugin", () => {
378
381
  canvas.dispatchEvent(new MouseEvent("click", { clientX: 100, clientY: 0, bubbles: true }));
379
382
  canvas.dispatchEvent(new MouseEvent("click", { clientX: 100, clientY: 100, bubbles: true }));
380
383
 
384
+ jest.advanceTimersByTime(500);
381
385
  await expectFinished();
382
386
 
383
387
  const data = getData().values()[0];
@@ -414,6 +418,7 @@ describe("trail-making plugin", () => {
414
418
  canvas.dispatchEvent(new MouseEvent("click", { clientX: 100, clientY: 100, bubbles: true }));
415
419
  canvas.dispatchEvent(new MouseEvent("click", { clientX: 200, clientY: 200, bubbles: true }));
416
420
 
421
+ jest.advanceTimersByTime(500);
417
422
  await expectFinished();
418
423
 
419
424
  const data = getData().values()[0];
@@ -453,6 +458,7 @@ describe("trail-making plugin", () => {
453
458
  canvas.dispatchEvent(new MouseEvent("click", { clientX: 100, clientY: 100, bubbles: true }));
454
459
  canvas.dispatchEvent(new MouseEvent("click", { clientX: 200, clientY: 200, bubbles: true }));
455
460
 
461
+ jest.advanceTimersByTime(500);
456
462
  await expectFinished();
457
463
 
458
464
  const data = getData().values()[0];
@@ -516,6 +522,7 @@ describe("trail-making plugin", () => {
516
522
  canvas.dispatchEvent(createTouchEvent(100, 100));
517
523
  canvas.dispatchEvent(createTouchEvent(200, 200));
518
524
 
525
+ jest.advanceTimersByTime(500);
519
526
  await expectFinished();
520
527
  });
521
528
  });
package/src/index.ts CHANGED
@@ -116,6 +116,13 @@ const info = <const>{
116
116
  type: ParameterType.HTML_STRING,
117
117
  default: null,
118
118
  },
119
+ /**
120
+ * Duration in milliseconds to wait after the last circle is clicked before ending the trial.
121
+ */
122
+ end_delay: {
123
+ type: ParameterType.INT,
124
+ default: 500,
125
+ },
119
126
  /**
120
127
  * Random seed for reproducible target layouts. If null, uses random seed.
121
128
  */
@@ -238,6 +245,11 @@ class TrailMakingPlugin implements JsPsychPlugin<Info> {
238
245
  const handleClick = (e: MouseEvent | TouchEvent) => {
239
246
  if (isShowingError) return;
240
247
 
248
+ // Prevent the synthetic click event that browsers fire after touchend
249
+ if (e instanceof TouchEvent) {
250
+ e.preventDefault();
251
+ }
252
+
241
253
  const rect = canvas.getBoundingClientRect();
242
254
  let clientX: number, clientY: number;
243
255
 
@@ -289,7 +301,9 @@ class TrailMakingPlugin implements JsPsychPlugin<Info> {
289
301
 
290
302
  // Check if complete
291
303
  if (currentTargetIndex >= sequence.length) {
292
- this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);
304
+ this.jsPsych.pluginAPI.setTimeout(() => {
305
+ this.endTrial(targets, clicks, startTime, now, numErrors, interClickTimes, trial);
306
+ }, trial.end_delay);
293
307
  }
294
308
  } else {
295
309
  // Error
@@ -300,7 +314,8 @@ class TrailMakingPlugin implements JsPsychPlugin<Info> {
300
314
  // Show error feedback
301
315
  isShowingError = true;
302
316
  const visitedLabels = sequence.slice(0, currentTargetIndex);
303
- this.drawCanvas(ctx, targets, visitedLabels, trial, true);
317
+ const errorLabel = clickedTargetIndex !== null ? targets[clickedTargetIndex].label : null;
318
+ this.drawCanvas(ctx, targets, visitedLabels, trial, errorLabel);
304
319
 
305
320
  this.jsPsych.pluginAPI.setTimeout(() => {
306
321
  isShowingError = false;
@@ -445,7 +460,7 @@ class TrailMakingPlugin implements JsPsychPlugin<Info> {
445
460
  targets: Target[],
446
461
  visitedLabels: string[],
447
462
  trial: TrialType<Info>,
448
- showError: boolean = false
463
+ errorLabel: string | null = null
449
464
  ) {
450
465
  const width = trial.canvas_width;
451
466
  const height = trial.canvas_height;
@@ -478,7 +493,7 @@ class TrailMakingPlugin implements JsPsychPlugin<Info> {
478
493
  const isVisited = visitedLabels.includes(target.label);
479
494
 
480
495
  // Set fill color before drawing
481
- if (showError && !isVisited) {
496
+ if (errorLabel !== null && target.label === errorLabel) {
482
497
  ctx.fillStyle = trial.error_color;
483
498
  } else if (isVisited) {
484
499
  ctx.fillStyle = trial.visited_color;