jspsych-tangram 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/dist/construct/index.browser.js +23 -5
  2. package/dist/construct/index.browser.js.map +1 -1
  3. package/dist/construct/index.browser.min.js +8 -8
  4. package/dist/construct/index.browser.min.js.map +1 -1
  5. package/dist/construct/index.cjs +23 -5
  6. package/dist/construct/index.cjs.map +1 -1
  7. package/dist/construct/index.js +23 -5
  8. package/dist/construct/index.js.map +1 -1
  9. package/dist/grid/index.browser.js +17855 -0
  10. package/dist/grid/index.browser.js.map +1 -0
  11. package/dist/grid/index.browser.min.js +47 -0
  12. package/dist/grid/index.browser.min.js.map +1 -0
  13. package/dist/grid/index.cjs +547 -0
  14. package/dist/grid/index.cjs.map +1 -0
  15. package/dist/grid/index.d.ts +174 -0
  16. package/dist/grid/index.js +545 -0
  17. package/dist/grid/index.js.map +1 -0
  18. package/dist/index.cjs +548 -13
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.ts +187 -16
  21. package/dist/index.js +549 -15
  22. package/dist/index.js.map +1 -1
  23. package/dist/prep/index.browser.js +23 -5
  24. package/dist/prep/index.browser.js.map +1 -1
  25. package/dist/prep/index.browser.min.js +6 -6
  26. package/dist/prep/index.browser.min.js.map +1 -1
  27. package/dist/prep/index.cjs +23 -5
  28. package/dist/prep/index.cjs.map +1 -1
  29. package/dist/prep/index.js +23 -5
  30. package/dist/prep/index.js.map +1 -1
  31. package/package.json +1 -1
  32. package/src/core/components/board/GameBoard.tsx +12 -0
  33. package/src/core/io/InteractionTracker.ts +19 -7
  34. package/src/core/io/data-tracking.ts +5 -0
  35. package/src/index.ts +2 -1
  36. package/src/plugins/tangram-grid/GridApp.tsx +522 -0
  37. package/src/plugins/tangram-grid/index.ts +154 -0
  38. package/tangram-construct.min.js +8 -8
  39. package/tangram-prep.min.js +6 -6
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ParameterType } from 'jspsych';
2
- import React, { useRef, useState, useEffect } from 'react';
2
+ import React, { useRef, useState, useEffect, useMemo } from 'react';
3
3
  import { createRoot } from 'react-dom/client';
4
4
  import { v4 } from 'uuid';
5
5
 
@@ -2655,7 +2655,9 @@ function useClickController(controller, layout, pieces, clickMode, draggingId, s
2655
2655
 
2656
2656
  class InteractionTracker {
2657
2657
  constructor(controller, callbacks, trialParams) {
2658
- this.gridStep = CONFIG.layout.grid.stepPx;
2658
+ this.completionTimes = [];
2659
+ // Sector centers (set by GameBoard after layout computation)
2660
+ this.sectorCenters = {};
2659
2661
  // Interaction state
2660
2662
  this.interactionIndex = 0;
2661
2663
  this.currentPickup = null;
@@ -2665,14 +2667,13 @@ class InteractionTracker {
2665
2667
  this.mouseTracking = [];
2666
2668
  // Interaction history (for TrialEndData)
2667
2669
  this.interactions = [];
2668
- // Construction-specific tracking
2669
- this.completionTimes = [];
2670
2670
  // Prep-specific tracking
2671
2671
  this.createdMacros = [];
2672
2672
  this.controller = controller;
2673
2673
  this.callbacks = callbacks;
2674
2674
  this.trialParams = trialParams;
2675
2675
  this.trialStartTime = Date.now();
2676
+ this.gridStep = CONFIG.layout.grid.stepPx;
2676
2677
  this.controller.setTrackingCallbacks({
2677
2678
  onSectorCompleted: (sectorId) => this.recordSectorCompletion(sectorId)
2678
2679
  });
@@ -2802,7 +2803,13 @@ class InteractionTracker {
2802
2803
  });
2803
2804
  }
2804
2805
  /**
2805
- * Record sector completion
2806
+ * Set sector centers (for anchor alignment)
2807
+ */
2808
+ setSectorCenters(centers) {
2809
+ this.sectorCenters = centers;
2810
+ }
2811
+ /**
2812
+ * Record a sector completion event
2806
2813
  */
2807
2814
  recordSectorCompletion(sectorId) {
2808
2815
  this.completionTimes.push({
@@ -2979,7 +2986,8 @@ class InteractionTracker {
2979
2986
  sectorId: sector.id,
2980
2987
  completed,
2981
2988
  pieceCount: pieces.length,
2982
- pieces
2989
+ pieces,
2990
+ center: this.sectorCenters[sector.id] ? this.toAnchorPoint(this.sectorCenters[sector.id]) : void 0
2983
2991
  };
2984
2992
  if (sectorState?.completedAt !== void 0) {
2985
2993
  snapshot.completedAt = sectorState.completedAt;
@@ -3207,6 +3215,16 @@ function GameBoard(props) {
3207
3215
  viewBox: { w: logicalBox.LOGICAL_W, h: logicalBox.LOGICAL_H }
3208
3216
  };
3209
3217
  }, [sectors, layoutMode, target, maxQuickstashSlots, primitives.length]);
3218
+ React.useEffect(() => {
3219
+ if (tracker && layout) {
3220
+ const centers = {};
3221
+ layout.sectors.forEach((s) => {
3222
+ const rect = rectForBand(layout, s, "silhouette", 1);
3223
+ centers[s.id] = { x: rect.cx, y: rect.cy };
3224
+ });
3225
+ tracker.setSectorCenters(centers);
3226
+ }
3227
+ }, [tracker, layout]);
3210
3228
  const [, force] = React.useReducer((x) => x + 1, 0);
3211
3229
  React.useEffect(() => {
3212
3230
  if (onControllerReady) {
@@ -3764,7 +3782,7 @@ function startConstructionTrial(display_element, params, _jsPsych) {
3764
3782
  return { root, display_element, jsPsych: _jsPsych };
3765
3783
  }
3766
3784
 
3767
- const info$3 = {
3785
+ const info$4 = {
3768
3786
  name: "tangram-construct",
3769
3787
  version: "1.0.0",
3770
3788
  parameters: {
@@ -3896,7 +3914,7 @@ class TangramConstructPlugin {
3896
3914
  this.jsPsych = jsPsych;
3897
3915
  }
3898
3916
  static {
3899
- this.info = info$3;
3917
+ this.info = info$4;
3900
3918
  }
3901
3919
  /**
3902
3920
  * Launches the trial by invoking startConstructionTrial
@@ -4047,7 +4065,7 @@ function startPrepTrial(display_element, params, jsPsych) {
4047
4065
  return { root, display_element, jsPsych };
4048
4066
  }
4049
4067
 
4050
- const info$2 = {
4068
+ const info$3 = {
4051
4069
  name: "tangram-prep",
4052
4070
  version: "1.0.0",
4053
4071
  parameters: {
@@ -4135,7 +4153,7 @@ class TangramPrepPlugin {
4135
4153
  this.jsPsych = jsPsych;
4136
4154
  }
4137
4155
  static {
4138
- this.info = info$2;
4156
+ this.info = info$3;
4139
4157
  }
4140
4158
  /**
4141
4159
  * Launches the trial by invoking startPrepTrial
@@ -4431,7 +4449,7 @@ function NBackView({ params }) {
4431
4449
  )));
4432
4450
  }
4433
4451
 
4434
- const info$1 = {
4452
+ const info$2 = {
4435
4453
  name: "tangram-nback",
4436
4454
  version: "1.0.0",
4437
4455
  parameters: {
@@ -4524,7 +4542,7 @@ class TangramNBackPlugin {
4524
4542
  this.jsPsych = jsPsych;
4525
4543
  }
4526
4544
  static {
4527
- this.info = info$1;
4545
+ this.info = info$2;
4528
4546
  }
4529
4547
  /**
4530
4548
  * Launches the trial by invoking startNBackTrial
@@ -4807,7 +4825,7 @@ function TangramOption({
4807
4825
  ));
4808
4826
  }
4809
4827
 
4810
- const info = {
4828
+ const info$1 = {
4811
4829
  name: "tangram-afc",
4812
4830
  version: "1.0.0",
4813
4831
  parameters: {
@@ -4885,7 +4903,7 @@ class TangramAFCPlugin {
4885
4903
  this.jsPsych = jsPsych;
4886
4904
  }
4887
4905
  static {
4888
- this.info = info;
4906
+ this.info = info$1;
4889
4907
  }
4890
4908
  /**
4891
4909
  * Launches the trial by invoking startAFCTrial
@@ -4919,5 +4937,521 @@ class TangramAFCPlugin {
4919
4937
  }
4920
4938
  }
4921
4939
 
4922
- export { TangramAFCPlugin, TangramConstructPlugin, TangramNBackPlugin, TangramPrepPlugin };
4940
+ function startGridTrial(display_element, params, _jsPsych) {
4941
+ const root = createRoot(display_element);
4942
+ root.render(React.createElement(GridView, { params }));
4943
+ return { root, display_element, jsPsych: _jsPsych };
4944
+ }
4945
+ function computeBounds(polys) {
4946
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
4947
+ for (const poly of polys) {
4948
+ for (const p of poly) {
4949
+ minX = Math.min(minX, p.x);
4950
+ minY = Math.min(minY, p.y);
4951
+ maxX = Math.max(maxX, p.x);
4952
+ maxY = Math.max(maxY, p.y);
4953
+ }
4954
+ }
4955
+ return { minX, minY, maxX, maxY, width: maxX - minX, height: maxY - minY };
4956
+ }
4957
+ function GridView({ params }) {
4958
+ const {
4959
+ tangrams,
4960
+ n_rows,
4961
+ n_cols,
4962
+ prompt_text,
4963
+ button_text,
4964
+ show_tangram_decomposition = false,
4965
+ usePrimitiveColors = false,
4966
+ primitiveColorIndices = [0, 1, 2, 3, 4],
4967
+ onTrialEnd
4968
+ } = params;
4969
+ const trialStartTime = useRef(Date.now());
4970
+ const [response, setResponse] = useState("");
4971
+ const [cellSize, setCellSize] = useState(100);
4972
+ const controlsRef = useRef(null);
4973
+ const GRID_GAP = 6;
4974
+ const CONTAINER_PADDING = 8;
4975
+ const CELL_MARGIN = 0.05;
4976
+ const PROGRESS_BAR_HEIGHT = 58;
4977
+ const CELL_BORDER = CONFIG.size.stroke.bandPx;
4978
+ const SAFETY_BUFFER = 8;
4979
+ useEffect(() => {
4980
+ const styleId = "tangram-grid-jspsych-override";
4981
+ if (!document.getElementById(styleId)) {
4982
+ const style = document.createElement("style");
4983
+ style.id = styleId;
4984
+ style.textContent = `
4985
+ .jspsych-content {
4986
+ max-width: 100% !important;
4987
+ width: 100% !important;
4988
+ }
4989
+ `;
4990
+ document.head.appendChild(style);
4991
+ }
4992
+ return () => {
4993
+ const style = document.getElementById(styleId);
4994
+ if (style) style.remove();
4995
+ };
4996
+ }, []);
4997
+ const CANON = /* @__PURE__ */ new Set([
4998
+ "square",
4999
+ "smalltriangle",
5000
+ "parallelogram",
5001
+ "medtriangle",
5002
+ "largetriangle"
5003
+ ]);
5004
+ const processedTangrams = useMemo(() => {
5005
+ return tangrams.map((tangramSpec) => {
5006
+ const filteredTans = tangramSpec.solutionTans.filter((tan) => {
5007
+ const tanName = tan.name ?? tan.kind;
5008
+ return CANON.has(tanName);
5009
+ });
5010
+ const mask = filteredTans.map((tan) => {
5011
+ return tan.vertices.map(([x, y]) => ({
5012
+ x: x ?? 0,
5013
+ y: -(y ?? 0)
5014
+ }));
5015
+ });
5016
+ const primitiveDecomposition = filteredTans.map((tan) => ({
5017
+ kind: tan.name ?? tan.kind,
5018
+ polygon: tan.vertices.map(([x, y]) => ({
5019
+ x: x ?? 0,
5020
+ y: -(y ?? 0)
5021
+ }))
5022
+ }));
5023
+ return {
5024
+ tangramId: tangramSpec.tangramID,
5025
+ mask,
5026
+ primitiveDecomposition
5027
+ };
5028
+ });
5029
+ }, [tangrams]);
5030
+ const maxTangramExtent = useMemo(() => {
5031
+ let maxExtent = 0;
5032
+ for (const t of processedTangrams) {
5033
+ const bounds = computeBounds(t.mask);
5034
+ maxExtent = Math.max(maxExtent, bounds.width, bounds.height);
5035
+ }
5036
+ return maxExtent || 1;
5037
+ }, [processedTangrams]);
5038
+ useEffect(() => {
5039
+ const calculateCellSize = () => {
5040
+ const viewportWidth = document.documentElement.clientWidth;
5041
+ const viewportHeight = document.documentElement.clientHeight;
5042
+ const controlsHeight = controlsRef.current?.offsetHeight ?? 70;
5043
+ const availableWidth = viewportWidth - CONTAINER_PADDING * 2 - SAFETY_BUFFER;
5044
+ const availableHeight = viewportHeight - PROGRESS_BAR_HEIGHT - controlsHeight - CONTAINER_PADDING * 2 - SAFETY_BUFFER;
5045
+ const totalHorizontalGaps = GRID_GAP * (n_cols - 1);
5046
+ const totalVerticalGaps = GRID_GAP * (n_rows - 1);
5047
+ const totalHorizontalBorders = CELL_BORDER * 2 * n_cols;
5048
+ const totalVerticalBorders = CELL_BORDER * 2 * n_rows;
5049
+ const maxCellWidth = (availableWidth - totalHorizontalGaps - totalHorizontalBorders) / n_cols;
5050
+ const maxCellHeight = (availableHeight - totalVerticalGaps - totalVerticalBorders) / n_rows;
5051
+ const newCellSize = Math.floor(Math.min(maxCellWidth, maxCellHeight));
5052
+ setCellSize(Math.max(newCellSize, 50));
5053
+ };
5054
+ calculateCellSize();
5055
+ window.addEventListener("resize", calculateCellSize);
5056
+ return () => window.removeEventListener("resize", calculateCellSize);
5057
+ }, [n_rows, n_cols]);
5058
+ const tangramScale = useMemo(() => {
5059
+ const usableSize = cellSize * (1 - CELL_MARGIN * 2);
5060
+ return usableSize / maxTangramExtent;
5061
+ }, [cellSize, maxTangramExtent]);
5062
+ const kindToIndex = {
5063
+ square: 0,
5064
+ smalltriangle: 1,
5065
+ parallelogram: 2,
5066
+ medtriangle: 3,
5067
+ largetriangle: 4
5068
+ };
5069
+ const pathD = (poly) => {
5070
+ if (!poly || poly.length === 0) return "";
5071
+ const moves = poly.map(
5072
+ (p, i) => `${i === 0 ? "M" : "L"} ${p.x} ${p.y}`
5073
+ );
5074
+ return moves.join(" ") + " Z";
5075
+ };
5076
+ const handleSubmit = () => {
5077
+ const rt = Date.now() - trialStartTime.current;
5078
+ const trialData = {
5079
+ response,
5080
+ rt,
5081
+ n_rows,
5082
+ n_cols,
5083
+ tangram_ids: processedTangrams.map((t) => t.tangramId),
5084
+ show_tangram_decomposition,
5085
+ use_primitive_colors: usePrimitiveColors,
5086
+ primitive_color_indices: primitiveColorIndices
5087
+ };
5088
+ if (onTrialEnd) {
5089
+ onTrialEnd(trialData);
5090
+ }
5091
+ };
5092
+ const renderTangram = (tangramData, index) => {
5093
+ const { mask, primitiveDecomposition } = tangramData;
5094
+ if (show_tangram_decomposition) {
5095
+ const scaledPrimitives = primitiveDecomposition.map(
5096
+ (prim) => {
5097
+ const scaledPoly = prim.polygon.map((p) => ({
5098
+ x: p.x * tangramScale,
5099
+ y: p.y * tangramScale
5100
+ }));
5101
+ return { kind: prim.kind, polygon: scaledPoly };
5102
+ }
5103
+ );
5104
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
5105
+ for (const prim of scaledPrimitives) {
5106
+ for (const p of prim.polygon) {
5107
+ minX = Math.min(minX, p.x);
5108
+ minY = Math.min(minY, p.y);
5109
+ maxX = Math.max(maxX, p.x);
5110
+ maxY = Math.max(maxY, p.y);
5111
+ }
5112
+ }
5113
+ const width = maxX - minX;
5114
+ const height = maxY - minY;
5115
+ const tx = cellSize / 2 - (minX + width / 2);
5116
+ const ty = cellSize / 2 - (minY + height / 2);
5117
+ const translatedPrimitives = scaledPrimitives.map(
5118
+ (prim) => ({
5119
+ kind: prim.kind,
5120
+ polygon: prim.polygon.map((p) => ({
5121
+ x: p.x + tx,
5122
+ y: p.y + ty
5123
+ }))
5124
+ })
5125
+ );
5126
+ return /* @__PURE__ */ React.createElement(
5127
+ "svg",
5128
+ {
5129
+ key: index,
5130
+ width: cellSize,
5131
+ height: cellSize,
5132
+ viewBox: `0 0 ${cellSize} ${cellSize}`,
5133
+ style: {
5134
+ display: "block",
5135
+ background: CONFIG.color.bands.silhouette.fillEven,
5136
+ border: `${CONFIG.size.stroke.bandPx}px solid ${CONFIG.color.bands.silhouette.stroke}`,
5137
+ borderRadius: "8px"
5138
+ }
5139
+ },
5140
+ translatedPrimitives.map(
5141
+ (prim, i) => {
5142
+ let fillColor;
5143
+ if (usePrimitiveColors) {
5144
+ const primitiveIndex = kindToIndex[prim.kind];
5145
+ if (primitiveIndex !== void 0 && primitiveColorIndices[primitiveIndex] !== void 0) {
5146
+ const colorIndex = primitiveColorIndices[primitiveIndex];
5147
+ const color = CONFIG.color.primitiveColors[colorIndex];
5148
+ fillColor = color || CONFIG.color.piece.validFill;
5149
+ } else {
5150
+ fillColor = CONFIG.color.piece.validFill;
5151
+ }
5152
+ } else {
5153
+ fillColor = CONFIG.color.piece.validFill;
5154
+ }
5155
+ return /* @__PURE__ */ React.createElement(
5156
+ "path",
5157
+ {
5158
+ key: `prim-${i}`,
5159
+ d: pathD(prim.polygon),
5160
+ fill: fillColor,
5161
+ opacity: CONFIG.opacity.piece.normal,
5162
+ stroke: CONFIG.color.tangramDecomposition.stroke,
5163
+ strokeWidth: CONFIG.size.stroke.tangramDecompositionPx
5164
+ }
5165
+ );
5166
+ }
5167
+ )
5168
+ );
5169
+ } else {
5170
+ const scaledMask = mask.map(
5171
+ (poly) => poly.map((p) => ({
5172
+ x: p.x * tangramScale,
5173
+ y: p.y * tangramScale
5174
+ }))
5175
+ );
5176
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
5177
+ for (const poly of scaledMask) {
5178
+ for (const p of poly) {
5179
+ minX = Math.min(minX, p.x);
5180
+ minY = Math.min(minY, p.y);
5181
+ maxX = Math.max(maxX, p.x);
5182
+ maxY = Math.max(maxY, p.y);
5183
+ }
5184
+ }
5185
+ const width = maxX - minX;
5186
+ const height = maxY - minY;
5187
+ const tx = cellSize / 2 - (minX + width / 2);
5188
+ const ty = cellSize / 2 - (minY + height / 2);
5189
+ const placedMask = scaledMask.map(
5190
+ (poly) => poly.map((p) => ({ x: p.x + tx, y: p.y + ty }))
5191
+ );
5192
+ return /* @__PURE__ */ React.createElement(
5193
+ "svg",
5194
+ {
5195
+ key: index,
5196
+ width: cellSize,
5197
+ height: cellSize,
5198
+ viewBox: `0 0 ${cellSize} ${cellSize}`,
5199
+ style: {
5200
+ display: "block",
5201
+ background: CONFIG.color.bands.silhouette.fillEven,
5202
+ border: `${CONFIG.size.stroke.bandPx}px solid ${CONFIG.color.bands.silhouette.stroke}`,
5203
+ borderRadius: "8px"
5204
+ }
5205
+ },
5206
+ placedMask.map((poly, i) => /* @__PURE__ */ React.createElement(
5207
+ "path",
5208
+ {
5209
+ key: `sil-${i}`,
5210
+ d: pathD(poly),
5211
+ fill: CONFIG.color.piece.validFill,
5212
+ opacity: CONFIG.opacity.piece.normal,
5213
+ stroke: "none"
5214
+ }
5215
+ ))
5216
+ );
5217
+ }
5218
+ };
5219
+ const isSubmitDisabled = response.trim().length === 0;
5220
+ return /* @__PURE__ */ React.createElement(
5221
+ "div",
5222
+ {
5223
+ style: {
5224
+ display: "flex",
5225
+ flexDirection: "column",
5226
+ alignItems: "center",
5227
+ justifyContent: "space-between",
5228
+ background: CONFIG.color.background,
5229
+ width: "100%",
5230
+ height: `calc(100vh - ${PROGRESS_BAR_HEIGHT}px)`,
5231
+ overflow: "hidden",
5232
+ fontFamily: "Roboto, sans-serif",
5233
+ boxSizing: "border-box",
5234
+ padding: `${CONTAINER_PADDING}px`
5235
+ }
5236
+ },
5237
+ /* @__PURE__ */ React.createElement(
5238
+ "div",
5239
+ {
5240
+ style: {
5241
+ flex: "1 1 auto",
5242
+ display: "flex",
5243
+ alignItems: "center",
5244
+ justifyContent: "center",
5245
+ minHeight: 0
5246
+ }
5247
+ },
5248
+ /* @__PURE__ */ React.createElement(
5249
+ "div",
5250
+ {
5251
+ style: {
5252
+ display: "grid",
5253
+ gridTemplateColumns: `repeat(${n_cols}, ${cellSize}px)`,
5254
+ gridTemplateRows: `repeat(${n_rows}, ${cellSize}px)`,
5255
+ gap: `${GRID_GAP}px`
5256
+ }
5257
+ },
5258
+ processedTangrams.slice(0, n_rows * n_cols).map(
5259
+ (t, i) => renderTangram(t, i)
5260
+ )
5261
+ )
5262
+ ),
5263
+ /* @__PURE__ */ React.createElement(
5264
+ "div",
5265
+ {
5266
+ ref: controlsRef,
5267
+ style: {
5268
+ flex: "0 0 auto",
5269
+ display: "flex",
5270
+ flexDirection: "column",
5271
+ alignItems: "center",
5272
+ gap: "4px",
5273
+ paddingTop: "4px"
5274
+ }
5275
+ },
5276
+ /* @__PURE__ */ React.createElement(
5277
+ "div",
5278
+ {
5279
+ style: {
5280
+ fontSize: "14px",
5281
+ textAlign: "center",
5282
+ maxWidth: "90vw"
5283
+ }
5284
+ },
5285
+ prompt_text
5286
+ ),
5287
+ /* @__PURE__ */ React.createElement(
5288
+ "div",
5289
+ {
5290
+ style: {
5291
+ display: "flex",
5292
+ alignItems: "center",
5293
+ gap: "12px"
5294
+ }
5295
+ },
5296
+ /* @__PURE__ */ React.createElement(
5297
+ "input",
5298
+ {
5299
+ type: "text",
5300
+ value: response,
5301
+ onChange: (e) => setResponse(e.target.value),
5302
+ style: {
5303
+ width: "min(400px, 50vw)",
5304
+ padding: "6px 10px",
5305
+ fontSize: "14px",
5306
+ borderRadius: "6px",
5307
+ border: "2px solid #ccc",
5308
+ fontFamily: "inherit",
5309
+ boxSizing: "border-box"
5310
+ },
5311
+ placeholder: "Type your response here..."
5312
+ }
5313
+ ),
5314
+ /* @__PURE__ */ React.createElement(
5315
+ "button",
5316
+ {
5317
+ className: "jspsych-btn",
5318
+ onClick: handleSubmit,
5319
+ disabled: isSubmitDisabled,
5320
+ style: {
5321
+ padding: "6px 16px",
5322
+ fontSize: "13px",
5323
+ cursor: isSubmitDisabled ? "not-allowed" : "pointer",
5324
+ opacity: isSubmitDisabled ? 0.5 : 1,
5325
+ flexShrink: 0
5326
+ }
5327
+ },
5328
+ button_text
5329
+ )
5330
+ )
5331
+ )
5332
+ );
5333
+ }
5334
+
5335
+ const info = {
5336
+ name: "tangram-grid",
5337
+ version: "1.0.0",
5338
+ parameters: {
5339
+ /** Array of tangram specifications to display in the grid */
5340
+ tangrams: {
5341
+ type: ParameterType.COMPLEX,
5342
+ default: void 0,
5343
+ description: "Array of TangramSpec objects to display in the grid"
5344
+ },
5345
+ /** Number of rows in the grid */
5346
+ n_rows: {
5347
+ type: ParameterType.INT,
5348
+ default: 1,
5349
+ description: "Number of rows in the tangram grid"
5350
+ },
5351
+ /** Number of columns in the grid */
5352
+ n_cols: {
5353
+ type: ParameterType.INT,
5354
+ default: 1,
5355
+ description: "Number of columns in the tangram grid"
5356
+ },
5357
+ /** Prompt text displayed above the text input */
5358
+ prompt_text: {
5359
+ type: ParameterType.STRING,
5360
+ default: "",
5361
+ description: "Text displayed above the text input field"
5362
+ },
5363
+ /** Label for the submit button */
5364
+ button_text: {
5365
+ type: ParameterType.STRING,
5366
+ default: "Submit",
5367
+ description: "Text displayed on the submit button"
5368
+ },
5369
+ /** Whether to show tangrams decomposed into primitives */
5370
+ show_tangram_decomposition: {
5371
+ type: ParameterType.BOOL,
5372
+ default: false,
5373
+ description: "Whether to show tangrams decomposed into individual primitives"
5374
+ },
5375
+ /** Whether to use distinct colors for each primitive type */
5376
+ use_primitive_colors: {
5377
+ type: ParameterType.BOOL,
5378
+ default: false,
5379
+ description: "Whether each primitive shape type should have its own distinct color"
5380
+ },
5381
+ /** Indices mapping primitives to colors */
5382
+ primitive_color_indices: {
5383
+ type: ParameterType.OBJECT,
5384
+ default: [0, 1, 2, 3, 4],
5385
+ description: "Array of 5 integers indexing into primitiveColors array, mapping [square, smalltriangle, parallelogram, medtriangle, largetriangle] to colors"
5386
+ },
5387
+ /** Callback fired when trial ends */
5388
+ onTrialEnd: {
5389
+ type: ParameterType.FUNCTION,
5390
+ default: void 0,
5391
+ description: "Callback when trial completes with full data"
5392
+ }
5393
+ },
5394
+ data: {
5395
+ /** The text response entered by the participant */
5396
+ response: {
5397
+ type: ParameterType.STRING,
5398
+ description: "The text response entered by the participant"
5399
+ },
5400
+ /** Reaction time from trial start to submit button click */
5401
+ rt: {
5402
+ type: ParameterType.INT,
5403
+ description: "Time in milliseconds from trial start to submit"
5404
+ }
5405
+ },
5406
+ citations: ""
5407
+ };
5408
+ class TangramGridPlugin {
5409
+ constructor(jsPsych) {
5410
+ this.jsPsych = jsPsych;
5411
+ }
5412
+ static {
5413
+ this.info = info;
5414
+ }
5415
+ /**
5416
+ * Launches the trial by invoking startGridTrial with the display element,
5417
+ * parameters, and jsPsych instance.
5418
+ *
5419
+ * REQUIRES: display_element is a valid HTMLElement, trial contains valid
5420
+ * parameters
5421
+ * MODIFIES: display_element (renders React component)
5422
+ * EFFECTS: Starts the grid trial and handles cleanup on completion
5423
+ */
5424
+ trial(display_element, trial) {
5425
+ const wrappedOnTrialEnd = (data) => {
5426
+ if (trial.onTrialEnd) {
5427
+ trial.onTrialEnd(data);
5428
+ }
5429
+ const reactContext = display_element.__reactContext;
5430
+ if (reactContext?.root) {
5431
+ reactContext.root.unmount();
5432
+ }
5433
+ display_element.innerHTML = "";
5434
+ this.jsPsych.finishTrial(data);
5435
+ };
5436
+ const params = {
5437
+ tangrams: trial.tangrams,
5438
+ n_rows: trial.n_rows,
5439
+ n_cols: trial.n_cols,
5440
+ prompt_text: trial.prompt_text,
5441
+ button_text: trial.button_text,
5442
+ show_tangram_decomposition: trial.show_tangram_decomposition,
5443
+ usePrimitiveColors: trial.use_primitive_colors,
5444
+ primitiveColorIndices: trial.primitive_color_indices,
5445
+ onTrialEnd: wrappedOnTrialEnd
5446
+ };
5447
+ const { root, display_element: element, jsPsych } = startGridTrial(
5448
+ display_element,
5449
+ params,
5450
+ this.jsPsych
5451
+ );
5452
+ element.__reactContext = { root, jsPsych };
5453
+ }
5454
+ }
5455
+
5456
+ export { TangramAFCPlugin, TangramConstructPlugin, TangramGridPlugin, TangramNBackPlugin, TangramPrepPlugin };
4923
5457
  //# sourceMappingURL=index.js.map