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