catchup-library-web 1.21.1 → 1.21.3

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.
package/dist/index.js CHANGED
@@ -5359,6 +5359,7 @@ var GroupingActivityMaterialContent = ({
5359
5359
  y: 0
5360
5360
  });
5361
5361
  const ref = (0, import_react20.useRef)(null);
5362
+ const dropZoneRefs = (0, import_react20.useRef)({});
5362
5363
  (0, import_react20.useEffect)(() => {
5363
5364
  const shuffleArray2 = (array) => {
5364
5365
  if (!isShuffled) {
@@ -5386,6 +5387,17 @@ var GroupingActivityMaterialContent = ({
5386
5387
  (answerData) => answerData.type === "GROUPING"
5387
5388
  ).answerMap = materialMap;
5388
5389
  }, [showCorrectAnswer, answer, materialMap]);
5390
+ (0, import_react20.useEffect)(() => {
5391
+ if (dropTargetKey && dropZoneRefs.current[dropTargetKey]) {
5392
+ const dropZoneElement = dropZoneRefs.current[dropTargetKey];
5393
+ if (dropZoneElement) {
5394
+ dropZoneElement.scrollIntoView({
5395
+ behavior: "smooth",
5396
+ block: "center"
5397
+ });
5398
+ }
5399
+ }
5400
+ }, [dropTargetKey]);
5389
5401
  const retrieveAnswerMap = () => {
5390
5402
  const foundIndex = answer.data.findIndex(
5391
5403
  (answerData) => answerData.type === "GROUPING"
@@ -5590,7 +5602,7 @@ var GroupingActivityMaterialContent = ({
5590
5602
  }) }),
5591
5603
  /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(DividerLine_default, {})
5592
5604
  ] }) : null,
5593
- /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "overflow-y-auto max-h-[500px]", children: Object.keys(answerMap).map((answerMapKey, index) => /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-row w-full", children: [
5605
+ Object.keys(answerMap).map((answerMapKey, index) => /* @__PURE__ */ (0, import_jsx_runtime30.jsxs)("div", { className: "flex flex-row w-full", children: [
5594
5606
  /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "w-1/3", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
5595
5607
  "div",
5596
5608
  {
@@ -5611,6 +5623,7 @@ var GroupingActivityMaterialContent = ({
5611
5623
  /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "flex-1 min-w-0", ref, children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)("div", { className: "h-full py-3", children: /* @__PURE__ */ (0, import_jsx_runtime30.jsx)(
5612
5624
  "div",
5613
5625
  {
5626
+ ref: (el) => dropZoneRefs.current[answerMapKey] = el,
5614
5627
  "data-grouping-drop-zone": answerMapKey,
5615
5628
  onMouseEnter: () => draggedValue && setDropTargetKey(answerMapKey),
5616
5629
  onMouseLeave: () => setDropTargetKey(null),
@@ -5673,7 +5686,7 @@ var GroupingActivityMaterialContent = ({
5673
5686
  ) }) })
5674
5687
  }
5675
5688
  ) }) })
5676
- ] }, index)) })
5689
+ ] }, index))
5677
5690
  ] });
5678
5691
  };
5679
5692
  var GroupingActivityMaterialContent_default = GroupingActivityMaterialContent;
@@ -5747,16 +5760,22 @@ var MatchingActivityMaterialContent = ({
5747
5760
  const [selectedValue, setSelectedValue] = (0, import_react21.useState)(null);
5748
5761
  const [draggedValue, setDraggedValue] = (0, import_react21.useState)(null);
5749
5762
  const [dropTargetKey, setDropTargetKey] = (0, import_react21.useState)(null);
5750
- const [draggedElement, setDraggedElement] = (0, import_react21.useState)(null);
5763
+ const [draggedElement, setDraggedElement] = (0, import_react21.useState)(
5764
+ null
5765
+ );
5751
5766
  const [isShuffled, setIsShuffled] = (0, import_react21.useState)(false);
5752
5767
  const [shuffledMaterialList, setShuffledMaterialList] = (0, import_react21.useState)([]);
5753
5768
  const dragElementRef = (0, import_react21.useRef)(null);
5754
- const [mousePosition, setMousePosition] = (0, import_react21.useState)({ x: 0, y: 0 });
5755
- const [touchPosition, setTouchPosition] = (0, import_react21.useState)({ x: 0, y: 0 });
5769
+ const [mousePosition, setMousePosition] = (0, import_react21.useState)({
5770
+ x: 0,
5771
+ y: 0
5772
+ });
5773
+ const [touchPosition, setTouchPosition] = (0, import_react21.useState)({
5774
+ x: 0,
5775
+ y: 0
5776
+ });
5756
5777
  const itemsRef = (0, import_react21.useRef)(null);
5757
- const scrollIntervalRef = (0, import_react21.useRef)(null);
5758
- const SCROLL_THRESHOLD = 100;
5759
- const SCROLL_SPEED = 10;
5778
+ const dropZoneRefs = (0, import_react21.useRef)({});
5760
5779
  (0, import_react21.useEffect)(() => {
5761
5780
  const shuffleArray2 = (array) => {
5762
5781
  if (!isShuffled) {
@@ -5778,44 +5797,21 @@ var MatchingActivityMaterialContent = ({
5778
5797
  }, [materialMap, isShuffled]);
5779
5798
  (0, import_react21.useEffect)(() => {
5780
5799
  if (!showCorrectAnswer) return;
5781
- answer.data.find((answerData) => answerData.type === "MATCHING").answerMap = materialMap;
5800
+ answer.data.find(
5801
+ (answerData) => answerData.type === "MATCHING"
5802
+ ).answerMap = materialMap;
5782
5803
  }, [showCorrectAnswer, answer, materialMap]);
5783
5804
  (0, import_react21.useEffect)(() => {
5784
- if (!draggedValue || mousePosition.y === 0) return;
5785
- const handleAutoScroll = () => {
5786
- const viewportHeight = window.innerHeight;
5787
- const scrollY = window.scrollY;
5788
- if (mousePosition.y < SCROLL_THRESHOLD + scrollY) {
5789
- window.scrollBy(0, -SCROLL_SPEED);
5790
- } else if (mousePosition.y > viewportHeight + scrollY - SCROLL_THRESHOLD) {
5791
- window.scrollBy(0, SCROLL_SPEED);
5792
- }
5793
- };
5794
- scrollIntervalRef.current = setInterval(handleAutoScroll, 16);
5795
- return () => {
5796
- if (scrollIntervalRef.current) {
5797
- clearInterval(scrollIntervalRef.current);
5798
- }
5799
- };
5800
- }, [draggedValue, mousePosition.y]);
5801
- (0, import_react21.useEffect)(() => {
5802
- if (!draggedValue || touchPosition.y === 0) return;
5803
- const handleAutoScroll = () => {
5804
- const viewportHeight = window.innerHeight;
5805
- const scrollY = window.scrollY;
5806
- if (touchPosition.y < SCROLL_THRESHOLD + scrollY) {
5807
- window.scrollBy(0, -SCROLL_SPEED);
5808
- } else if (touchPosition.y > viewportHeight + scrollY - SCROLL_THRESHOLD) {
5809
- window.scrollBy(0, SCROLL_SPEED);
5810
- }
5811
- };
5812
- scrollIntervalRef.current = setInterval(handleAutoScroll, 16);
5813
- return () => {
5814
- if (scrollIntervalRef.current) {
5815
- clearInterval(scrollIntervalRef.current);
5805
+ if (dropTargetKey && dropZoneRefs.current[dropTargetKey]) {
5806
+ const dropZoneElement = dropZoneRefs.current[dropTargetKey];
5807
+ if (dropZoneElement) {
5808
+ dropZoneElement.scrollIntoView({
5809
+ behavior: "smooth",
5810
+ block: "center"
5811
+ });
5816
5812
  }
5817
- };
5818
- }, [draggedValue, touchPosition.y]);
5813
+ }
5814
+ }, [dropTargetKey]);
5819
5815
  const retrieveAnswerMap = () => {
5820
5816
  const foundIndex = answer.data.findIndex(
5821
5817
  (answerData) => answerData.type === "MATCHING"
@@ -6044,6 +6040,7 @@ var MatchingActivityMaterialContent = ({
6044
6040
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex-1", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6045
6041
  "div",
6046
6042
  {
6043
+ ref: (el) => dropZoneRefs.current[answerMapKey] = el,
6047
6044
  "data-matching-drop-zone": answerMapKey,
6048
6045
  onMouseEnter: () => draggedValue && setDropTargetKey(answerMapKey),
6049
6046
  onMouseLeave: () => setDropTargetKey(null),
package/dist/index.mjs CHANGED
@@ -5143,6 +5143,7 @@ var GroupingActivityMaterialContent = ({
5143
5143
  y: 0
5144
5144
  });
5145
5145
  const ref = useRef5(null);
5146
+ const dropZoneRefs = useRef5({});
5146
5147
  useEffect10(() => {
5147
5148
  const shuffleArray2 = (array) => {
5148
5149
  if (!isShuffled) {
@@ -5170,6 +5171,17 @@ var GroupingActivityMaterialContent = ({
5170
5171
  (answerData) => answerData.type === "GROUPING"
5171
5172
  ).answerMap = materialMap;
5172
5173
  }, [showCorrectAnswer, answer, materialMap]);
5174
+ useEffect10(() => {
5175
+ if (dropTargetKey && dropZoneRefs.current[dropTargetKey]) {
5176
+ const dropZoneElement = dropZoneRefs.current[dropTargetKey];
5177
+ if (dropZoneElement) {
5178
+ dropZoneElement.scrollIntoView({
5179
+ behavior: "smooth",
5180
+ block: "center"
5181
+ });
5182
+ }
5183
+ }
5184
+ }, [dropTargetKey]);
5173
5185
  const retrieveAnswerMap = () => {
5174
5186
  const foundIndex = answer.data.findIndex(
5175
5187
  (answerData) => answerData.type === "GROUPING"
@@ -5374,7 +5386,7 @@ var GroupingActivityMaterialContent = ({
5374
5386
  }) }),
5375
5387
  /* @__PURE__ */ jsx30(DividerLine_default, {})
5376
5388
  ] }) : null,
5377
- /* @__PURE__ */ jsx30("div", { className: "overflow-y-auto max-h-[500px]", children: Object.keys(answerMap).map((answerMapKey, index) => /* @__PURE__ */ jsxs19("div", { className: "flex flex-row w-full", children: [
5389
+ Object.keys(answerMap).map((answerMapKey, index) => /* @__PURE__ */ jsxs19("div", { className: "flex flex-row w-full", children: [
5378
5390
  /* @__PURE__ */ jsx30("div", { className: "w-1/3", children: /* @__PURE__ */ jsx30(
5379
5391
  "div",
5380
5392
  {
@@ -5395,6 +5407,7 @@ var GroupingActivityMaterialContent = ({
5395
5407
  /* @__PURE__ */ jsx30("div", { className: "flex-1 min-w-0", ref, children: /* @__PURE__ */ jsx30("div", { className: "h-full py-3", children: /* @__PURE__ */ jsx30(
5396
5408
  "div",
5397
5409
  {
5410
+ ref: (el) => dropZoneRefs.current[answerMapKey] = el,
5398
5411
  "data-grouping-drop-zone": answerMapKey,
5399
5412
  onMouseEnter: () => draggedValue && setDropTargetKey(answerMapKey),
5400
5413
  onMouseLeave: () => setDropTargetKey(null),
@@ -5457,7 +5470,7 @@ var GroupingActivityMaterialContent = ({
5457
5470
  ) }) })
5458
5471
  }
5459
5472
  ) }) })
5460
- ] }, index)) })
5473
+ ] }, index))
5461
5474
  ] });
5462
5475
  };
5463
5476
  var GroupingActivityMaterialContent_default = GroupingActivityMaterialContent;
@@ -5531,16 +5544,22 @@ var MatchingActivityMaterialContent = ({
5531
5544
  const [selectedValue, setSelectedValue] = useState20(null);
5532
5545
  const [draggedValue, setDraggedValue] = useState20(null);
5533
5546
  const [dropTargetKey, setDropTargetKey] = useState20(null);
5534
- const [draggedElement, setDraggedElement] = useState20(null);
5547
+ const [draggedElement, setDraggedElement] = useState20(
5548
+ null
5549
+ );
5535
5550
  const [isShuffled, setIsShuffled] = useState20(false);
5536
5551
  const [shuffledMaterialList, setShuffledMaterialList] = useState20([]);
5537
5552
  const dragElementRef = useRef6(null);
5538
- const [mousePosition, setMousePosition] = useState20({ x: 0, y: 0 });
5539
- const [touchPosition, setTouchPosition] = useState20({ x: 0, y: 0 });
5553
+ const [mousePosition, setMousePosition] = useState20({
5554
+ x: 0,
5555
+ y: 0
5556
+ });
5557
+ const [touchPosition, setTouchPosition] = useState20({
5558
+ x: 0,
5559
+ y: 0
5560
+ });
5540
5561
  const itemsRef = useRef6(null);
5541
- const scrollIntervalRef = useRef6(null);
5542
- const SCROLL_THRESHOLD = 100;
5543
- const SCROLL_SPEED = 10;
5562
+ const dropZoneRefs = useRef6({});
5544
5563
  useEffect11(() => {
5545
5564
  const shuffleArray2 = (array) => {
5546
5565
  if (!isShuffled) {
@@ -5562,44 +5581,21 @@ var MatchingActivityMaterialContent = ({
5562
5581
  }, [materialMap, isShuffled]);
5563
5582
  useEffect11(() => {
5564
5583
  if (!showCorrectAnswer) return;
5565
- answer.data.find((answerData) => answerData.type === "MATCHING").answerMap = materialMap;
5584
+ answer.data.find(
5585
+ (answerData) => answerData.type === "MATCHING"
5586
+ ).answerMap = materialMap;
5566
5587
  }, [showCorrectAnswer, answer, materialMap]);
5567
5588
  useEffect11(() => {
5568
- if (!draggedValue || mousePosition.y === 0) return;
5569
- const handleAutoScroll = () => {
5570
- const viewportHeight = window.innerHeight;
5571
- const scrollY = window.scrollY;
5572
- if (mousePosition.y < SCROLL_THRESHOLD + scrollY) {
5573
- window.scrollBy(0, -SCROLL_SPEED);
5574
- } else if (mousePosition.y > viewportHeight + scrollY - SCROLL_THRESHOLD) {
5575
- window.scrollBy(0, SCROLL_SPEED);
5576
- }
5577
- };
5578
- scrollIntervalRef.current = setInterval(handleAutoScroll, 16);
5579
- return () => {
5580
- if (scrollIntervalRef.current) {
5581
- clearInterval(scrollIntervalRef.current);
5582
- }
5583
- };
5584
- }, [draggedValue, mousePosition.y]);
5585
- useEffect11(() => {
5586
- if (!draggedValue || touchPosition.y === 0) return;
5587
- const handleAutoScroll = () => {
5588
- const viewportHeight = window.innerHeight;
5589
- const scrollY = window.scrollY;
5590
- if (touchPosition.y < SCROLL_THRESHOLD + scrollY) {
5591
- window.scrollBy(0, -SCROLL_SPEED);
5592
- } else if (touchPosition.y > viewportHeight + scrollY - SCROLL_THRESHOLD) {
5593
- window.scrollBy(0, SCROLL_SPEED);
5594
- }
5595
- };
5596
- scrollIntervalRef.current = setInterval(handleAutoScroll, 16);
5597
- return () => {
5598
- if (scrollIntervalRef.current) {
5599
- clearInterval(scrollIntervalRef.current);
5589
+ if (dropTargetKey && dropZoneRefs.current[dropTargetKey]) {
5590
+ const dropZoneElement = dropZoneRefs.current[dropTargetKey];
5591
+ if (dropZoneElement) {
5592
+ dropZoneElement.scrollIntoView({
5593
+ behavior: "smooth",
5594
+ block: "center"
5595
+ });
5600
5596
  }
5601
- };
5602
- }, [draggedValue, touchPosition.y]);
5597
+ }
5598
+ }, [dropTargetKey]);
5603
5599
  const retrieveAnswerMap = () => {
5604
5600
  const foundIndex = answer.data.findIndex(
5605
5601
  (answerData) => answerData.type === "MATCHING"
@@ -5828,6 +5824,7 @@ var MatchingActivityMaterialContent = ({
5828
5824
  /* @__PURE__ */ jsx32("div", { className: "flex-1", children: /* @__PURE__ */ jsx32(
5829
5825
  "div",
5830
5826
  {
5827
+ ref: (el) => dropZoneRefs.current[answerMapKey] = el,
5831
5828
  "data-matching-drop-zone": answerMapKey,
5832
5829
  onMouseEnter: () => draggedValue && setDropTargetKey(answerMapKey),
5833
5830
  onMouseLeave: () => setDropTargetKey(null),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "catchup-library-web",
3
- "version": "1.21.01",
3
+ "version": "1.21.03",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -33,6 +33,7 @@ const GroupingActivityMaterialContent = ({
33
33
  y: 0,
34
34
  });
35
35
  const ref = useRef<HTMLDivElement>(null);
36
+ const dropZoneRefs = useRef<{ [key: string]: HTMLDivElement | null }>({});
36
37
 
37
38
  useEffect(() => {
38
39
  const shuffleArray = (array: any) => {
@@ -63,6 +64,19 @@ const GroupingActivityMaterialContent = ({
63
64
  ).answerMap = materialMap;
64
65
  }, [showCorrectAnswer, answer, materialMap]);
65
66
 
67
+ // Auto-scroll to center the drop zone when hovering
68
+ useEffect(() => {
69
+ if (dropTargetKey && dropZoneRefs.current[dropTargetKey]) {
70
+ const dropZoneElement = dropZoneRefs.current[dropTargetKey];
71
+ if (dropZoneElement) {
72
+ dropZoneElement.scrollIntoView({
73
+ behavior: "smooth",
74
+ block: "center",
75
+ });
76
+ }
77
+ }
78
+ }, [dropTargetKey]);
79
+
66
80
  const retrieveAnswerMap = () => {
67
81
  const foundIndex = answer.data.findIndex(
68
82
  (answerData: any) => answerData.type === "GROUPING"
@@ -369,152 +383,151 @@ const GroupingActivityMaterialContent = ({
369
383
  <DividerLine />
370
384
  </>
371
385
  ) : null}
372
- <div className="overflow-y-auto max-h-[500px]">
373
- {Object.keys(answerMap).map((answerMapKey, index) => (
374
- <div key={index} className="flex flex-row w-full">
375
- <div className="w-1/3">
376
- <div
377
- className={`border-catchup-blue ${
378
- contentMap.type === "TEXT"
379
- ? "h-catchup-activity-text-outer-box-item"
380
- : "h-catchup-activity-media-outer-box-item"
381
- } flex flex-col items-center justify-center border-2 rounded-catchup-xlarge transition-all duration-300 my-3`}
382
- >
383
- <div className="flex flex-col items-center justify-center transition-all duration-300 px-4 text-center">
384
- <p className="text-lg whitespace-pre-wrap">
385
- {constructInputWithSpecialExpressionList(answerMapKey).map(
386
- (inputPart, index) => (
387
- <span
388
- key={index}
389
- className={`${inputPart.isBold ? "font-bold" : ""} ${
390
- inputPart.isUnderline ? "underline" : ""
391
- }`}
392
- >
393
- {inputPart.isEquation ? (
394
- <span className="text-2xl">
395
- <InlineMath math={inputPart.value} />
396
- </span>
397
- ) : (
398
- inputPart.value
399
- )}
400
- </span>
401
- )
402
- )}
403
- </p>
404
- </div>
386
+ {Object.keys(answerMap).map((answerMapKey, index) => (
387
+ <div key={index} className="flex flex-row w-full">
388
+ <div className="w-1/3">
389
+ <div
390
+ className={`border-catchup-blue ${
391
+ contentMap.type === "TEXT"
392
+ ? "h-catchup-activity-text-outer-box-item"
393
+ : "h-catchup-activity-media-outer-box-item"
394
+ } flex flex-col items-center justify-center border-2 rounded-catchup-xlarge transition-all duration-300 my-3`}
395
+ >
396
+ <div className="flex flex-col items-center justify-center transition-all duration-300 px-4 text-center">
397
+ <p className="text-lg whitespace-pre-wrap">
398
+ {constructInputWithSpecialExpressionList(answerMapKey).map(
399
+ (inputPart, index) => (
400
+ <span
401
+ key={index}
402
+ className={`${inputPart.isBold ? "font-bold" : ""} ${
403
+ inputPart.isUnderline ? "underline" : ""
404
+ }`}
405
+ >
406
+ {inputPart.isEquation ? (
407
+ <span className="text-2xl">
408
+ <InlineMath math={inputPart.value} />
409
+ </span>
410
+ ) : (
411
+ inputPart.value
412
+ )}
413
+ </span>
414
+ )
415
+ )}
416
+ </p>
405
417
  </div>
406
418
  </div>
407
- <div className="mx-4 w-[2px] bg-catchup-lighter-gray"></div>
408
- <div className="flex-1 min-w-0" ref={ref}>
409
- <div className="h-full py-3">
410
- <div
411
- data-grouping-drop-zone={answerMapKey}
412
- onMouseEnter={() =>
413
- draggedValue && setDropTargetKey(answerMapKey)
414
- }
415
- onMouseLeave={() => setDropTargetKey(null)}
416
- onClick={() => handleDropZoneClick(answerMapKey)}
417
- className={`${
418
- dropTargetKey === answerMapKey
419
- ? "bg-catchup-light-blue ring-2 ring-blue-400"
420
- : ""
421
- } flex-1 border-catchup-blue rounded-catchup-xlarge border-2 h-full p-1 transition-all duration-200`}
422
- >
423
- <div className="h-full w-full overflow-x-auto">
424
- <div className="flex flex-row items-center gap-2 w-max h-full">
425
- {answerMap[answerMapKey].map(
426
- (answerMapValue: string, answerMapIndex: number) => {
427
- const learnerAnswerState = checkAnswerState(
428
- materialMap[answerMapKey],
429
- answerMapValue
430
- );
431
- return (
432
- <div key={answerMapIndex} className="p-1">
419
+ </div>
420
+ <div className="mx-4 w-[2px] bg-catchup-lighter-gray"></div>
421
+ <div className="flex-1 min-w-0" ref={ref}>
422
+ <div className="h-full py-3">
423
+ <div
424
+ ref={(el) => (dropZoneRefs.current[answerMapKey] = el)}
425
+ data-grouping-drop-zone={answerMapKey}
426
+ onMouseEnter={() =>
427
+ draggedValue && setDropTargetKey(answerMapKey)
428
+ }
429
+ onMouseLeave={() => setDropTargetKey(null)}
430
+ onClick={() => handleDropZoneClick(answerMapKey)}
431
+ className={`${
432
+ dropTargetKey === answerMapKey
433
+ ? "bg-catchup-light-blue ring-2 ring-blue-400"
434
+ : ""
435
+ } flex-1 border-catchup-blue rounded-catchup-xlarge border-2 h-full p-1 transition-all duration-200`}
436
+ >
437
+ <div className="h-full w-full overflow-x-auto">
438
+ <div className="flex flex-row items-center gap-2 w-max h-full">
439
+ {answerMap[answerMapKey].map(
440
+ (answerMapValue: string, answerMapIndex: number) => {
441
+ const learnerAnswerState = checkAnswerState(
442
+ materialMap[answerMapKey],
443
+ answerMapValue
444
+ );
445
+ return (
446
+ <div key={answerMapIndex} className="p-1">
447
+ <div
448
+ className={`${
449
+ contentMap.type === "TEXT"
450
+ ? "h-catchup-activity-text-box-item"
451
+ : "h-catchup-activity-media-box-item"
452
+ }`}
453
+ >
433
454
  <div
434
455
  className={`${
435
- contentMap.type === "TEXT"
436
- ? "h-catchup-activity-text-box-item"
437
- : "h-catchup-activity-media-box-item"
438
- }`}
456
+ learnerAnswerState === "EMPTY"
457
+ ? "border-catchup-lighter-gray"
458
+ : learnerAnswerState === "CORRECT"
459
+ ? "border-catchup-green"
460
+ : learnerAnswerState === "INCORRECT"
461
+ ? "border-catchup-red"
462
+ : "border-catchup-blue"
463
+ } border-2 rounded-catchup-xlarge h-full flex flex-col items-center justify-center transition-all duration-300 cursor-pointer`}
464
+ onClick={(e) => {
465
+ e.stopPropagation();
466
+ if (checkCanAnswerQuestion()) {
467
+ onChange(
468
+ answer,
469
+ answerMapKey,
470
+ null,
471
+ answerMapIndex
472
+ );
473
+ setSelectedValue(null);
474
+ }
475
+ }}
439
476
  >
440
- <div
441
- className={`${
442
- learnerAnswerState === "EMPTY"
443
- ? "border-catchup-lighter-gray"
444
- : learnerAnswerState === "CORRECT"
445
- ? "border-catchup-green"
446
- : learnerAnswerState === "INCORRECT"
447
- ? "border-catchup-red"
448
- : "border-catchup-blue"
449
- } border-2 rounded-catchup-xlarge h-full flex flex-col items-center justify-center transition-all duration-300 cursor-pointer`}
450
- onClick={(e) => {
451
- e.stopPropagation();
452
- if (checkCanAnswerQuestion()) {
453
- onChange(
454
- answer,
455
- answerMapKey,
456
- null,
457
- answerMapIndex
458
- );
459
- setSelectedValue(null);
460
- }
461
- }}
462
- >
463
- {contentMap.type === "TEXT" ? (
464
- <div className="flex flex-col items-center justify-center transition-all duration-300 min-w-[200px] overflow-y-auto">
465
- <div className="m-2">
466
- <p className="text-xl text-center whitespace-pre-wrap">
467
- {constructInputWithSpecialExpressionList(
468
- answerMapValue
469
- ).map((inputPart, index) => (
470
- <span
471
- key={index}
472
- className={`${
473
- inputPart.isBold
474
- ? "font-bold"
475
- : ""
476
- } ${
477
- inputPart.isUnderline
478
- ? "underline"
479
- : ""
480
- }`}
481
- >
482
- {inputPart.isEquation ? (
483
- <span className="text-2xl">
484
- <InlineMath
485
- math={inputPart.value}
486
- />
487
- </span>
488
- ) : (
489
- inputPart.value
490
- )}
491
- </span>
492
- ))}
493
- </p>
494
- </div>
477
+ {contentMap.type === "TEXT" ? (
478
+ <div className="flex flex-col items-center justify-center transition-all duration-300 min-w-[200px] overflow-y-auto">
479
+ <div className="m-2">
480
+ <p className="text-xl text-center whitespace-pre-wrap">
481
+ {constructInputWithSpecialExpressionList(
482
+ answerMapValue
483
+ ).map((inputPart, index) => (
484
+ <span
485
+ key={index}
486
+ className={`${
487
+ inputPart.isBold
488
+ ? "font-bold"
489
+ : ""
490
+ } ${
491
+ inputPart.isUnderline
492
+ ? "underline"
493
+ : ""
494
+ }`}
495
+ >
496
+ {inputPart.isEquation ? (
497
+ <span className="text-2xl">
498
+ <InlineMath
499
+ math={inputPart.value}
500
+ />
501
+ </span>
502
+ ) : (
503
+ inputPart.value
504
+ )}
505
+ </span>
506
+ ))}
507
+ </p>
495
508
  </div>
496
- ) : (
497
- <ShowMaterialMediaByContentType
498
- key={`${uniqueValue}-${answerMapIndex}`}
499
- contentType={contentMap.type}
500
- src={answerMapValue}
501
- canFullScreen={false}
502
- />
503
- )}
504
- </div>
509
+ </div>
510
+ ) : (
511
+ <ShowMaterialMediaByContentType
512
+ key={`${uniqueValue}-${answerMapIndex}`}
513
+ contentType={contentMap.type}
514
+ src={answerMapValue}
515
+ canFullScreen={false}
516
+ />
517
+ )}
505
518
  </div>
506
519
  </div>
507
- );
508
- }
509
- )}
510
- </div>
520
+ </div>
521
+ );
522
+ }
523
+ )}
511
524
  </div>
512
525
  </div>
513
526
  </div>
514
527
  </div>
515
528
  </div>
516
- ))}
517
- </div>
529
+ </div>
530
+ ))}
518
531
  </div>
519
532
  );
520
533
  };
@@ -2,6 +2,7 @@ import { useEffect, useRef, useState } from "react";
2
2
  import ShowMaterialMediaByContentType from "./ShowMaterialMediaByContentType";
3
3
  import { InlineMath } from "react-katex";
4
4
  import { constructInputWithSpecialExpressionList } from "../../../utilization/CatchtivityUtilization";
5
+ import { IMatchingActivityMaterialProps } from "../../../properties/ActivityProperties";
5
6
  import DividerLine from "../../dividers/DividerLine";
6
7
 
7
8
  const MatchingActivityMaterialContent = ({
@@ -13,25 +14,29 @@ const MatchingActivityMaterialContent = ({
13
14
  onChange,
14
15
  isPreview,
15
16
  showCorrectAnswer,
16
- }) => {
17
- const [selectedValue, setSelectedValue] = useState(null);
18
- const [draggedValue, setDraggedValue] = useState(null);
19
- const [dropTargetKey, setDropTargetKey] = useState(null);
20
- const [draggedElement, setDraggedElement] = useState(null);
17
+ }: IMatchingActivityMaterialProps) => {
18
+ const [selectedValue, setSelectedValue] = useState<string | null>(null);
19
+ const [draggedValue, setDraggedValue] = useState<string | null>(null);
20
+ const [dropTargetKey, setDropTargetKey] = useState<string | null>(null);
21
+ const [draggedElement, setDraggedElement] = useState<HTMLElement | null>(
22
+ null
23
+ );
21
24
  const [isShuffled, setIsShuffled] = useState(false);
22
- const [shuffledMaterialList, setShuffledMaterialList] = useState([]);
23
- const dragElementRef = useRef(null);
24
- const [mousePosition, setMousePosition] = useState({ x: 0, y: 0 });
25
- const [touchPosition, setTouchPosition] = useState({ x: 0, y: 0 });
26
- const itemsRef = useRef(null);
27
- const scrollIntervalRef = useRef(null);
28
-
29
- // Auto-scroll configuration
30
- const SCROLL_THRESHOLD = 100; // Distance from edge to trigger scroll
31
- const SCROLL_SPEED = 10; // Pixels to scroll per interval
25
+ const [shuffledMaterialList, setShuffledMaterialList] = useState<any[]>([]);
26
+ const dragElementRef = useRef<HTMLDivElement>(null);
27
+ const [mousePosition, setMousePosition] = useState<{ x: number; y: number }>({
28
+ x: 0,
29
+ y: 0,
30
+ });
31
+ const [touchPosition, setTouchPosition] = useState<{ x: number; y: number }>({
32
+ x: 0,
33
+ y: 0,
34
+ });
35
+ const itemsRef = useRef<HTMLDivElement>(null);
36
+ const dropZoneRefs = useRef<{ [key: string]: HTMLDivElement | null }>({});
32
37
 
33
38
  useEffect(() => {
34
- const shuffleArray = (array) => {
39
+ const shuffleArray = (array: any) => {
35
40
  if (!isShuffled) {
36
41
  const copyArray = JSON.parse(JSON.stringify(array));
37
42
  for (let i = copyArray.length - 1; i > 0; i--) {
@@ -43,7 +48,7 @@ const MatchingActivityMaterialContent = ({
43
48
  }
44
49
  return array;
45
50
  };
46
- const materialList = [];
51
+ const materialList: any = [];
47
52
  Object.keys(materialMap).forEach((materialKey) => {
48
53
  materialList.push(materialMap[materialKey]);
49
54
  });
@@ -52,84 +57,45 @@ const MatchingActivityMaterialContent = ({
52
57
 
53
58
  useEffect(() => {
54
59
  if (!showCorrectAnswer) return;
55
- answer.data.find((answerData) => answerData.type === "MATCHING").answerMap =
56
- materialMap;
60
+ answer.data.find(
61
+ (answerData: any) => answerData.type === "MATCHING"
62
+ ).answerMap = materialMap;
57
63
  }, [showCorrectAnswer, answer, materialMap]);
58
64
 
59
- // Auto-scroll effect for mouse drag
60
- useEffect(() => {
61
- if (!draggedValue || mousePosition.y === 0) return;
62
-
63
- const handleAutoScroll = () => {
64
- const viewportHeight = window.innerHeight;
65
- const scrollY = window.scrollY;
66
-
67
- // Check if near top edge
68
- if (mousePosition.y < SCROLL_THRESHOLD + scrollY) {
69
- window.scrollBy(0, -SCROLL_SPEED);
70
- }
71
- // Check if near bottom edge
72
- else if (mousePosition.y > viewportHeight + scrollY - SCROLL_THRESHOLD) {
73
- window.scrollBy(0, SCROLL_SPEED);
74
- }
75
- };
76
-
77
- scrollIntervalRef.current = setInterval(handleAutoScroll, 16); // ~60fps
78
-
79
- return () => {
80
- if (scrollIntervalRef.current) {
81
- clearInterval(scrollIntervalRef.current);
82
- }
83
- };
84
- }, [draggedValue, mousePosition.y]);
85
-
86
- // Auto-scroll effect for touch drag
65
+ // Auto-scroll to center the drop zone when hovering
87
66
  useEffect(() => {
88
- if (!draggedValue || touchPosition.y === 0) return;
89
-
90
- const handleAutoScroll = () => {
91
- const viewportHeight = window.innerHeight;
92
- const scrollY = window.scrollY;
93
-
94
- // Check if near top edge
95
- if (touchPosition.y < SCROLL_THRESHOLD + scrollY) {
96
- window.scrollBy(0, -SCROLL_SPEED);
97
- }
98
- // Check if near bottom edge
99
- else if (touchPosition.y > viewportHeight + scrollY - SCROLL_THRESHOLD) {
100
- window.scrollBy(0, SCROLL_SPEED);
67
+ if (dropTargetKey && dropZoneRefs.current[dropTargetKey]) {
68
+ const dropZoneElement = dropZoneRefs.current[dropTargetKey];
69
+ if (dropZoneElement) {
70
+ dropZoneElement.scrollIntoView({
71
+ behavior: "smooth",
72
+ block: "center",
73
+ });
101
74
  }
102
- };
103
-
104
- scrollIntervalRef.current = setInterval(handleAutoScroll, 16); // ~60fps
105
-
106
- return () => {
107
- if (scrollIntervalRef.current) {
108
- clearInterval(scrollIntervalRef.current);
109
- }
110
- };
111
- }, [draggedValue, touchPosition.y]);
75
+ }
76
+ }, [dropTargetKey]);
112
77
 
113
78
  const retrieveAnswerMap = () => {
114
79
  const foundIndex = answer.data.findIndex(
115
- (answerData) => answerData.type === "MATCHING"
80
+ (answerData: any) => answerData.type === "MATCHING"
116
81
  );
117
82
  return answer.data[foundIndex].answerMap;
118
83
  };
119
84
 
120
- const retrieveFilteredMaterialList = (answerMap) => {
121
- const selectedValueList = [];
85
+ const retrieveFilteredMaterialList = (answerMap: any) => {
86
+ const selectedValueList: any = [];
122
87
  Object.keys(answerMap).forEach((key) => {
123
88
  selectedValueList.push(answerMap[key]);
124
89
  });
125
90
 
126
91
  return shuffledMaterialList.filter(
127
92
  (material) =>
128
- selectedValueList.findIndex((value) => material === value) === -1
93
+ selectedValueList.findIndex((value: string) => material === value) ===
94
+ -1
129
95
  );
130
96
  };
131
97
 
132
- const checkAnswerState = (correctAnswer, learnerAnswer) => {
98
+ const checkAnswerState = (correctAnswer: string, learnerAnswer: string) => {
133
99
  if (!isPreview) return null;
134
100
  if (!learnerAnswer) return "EMPTY";
135
101
  if (correctAnswer === learnerAnswer) {
@@ -139,7 +105,10 @@ const MatchingActivityMaterialContent = ({
139
105
  };
140
106
 
141
107
  // Mouse drag handlers
142
- const handleMouseDown = (e, materialValue) => {
108
+ const handleMouseDown = (
109
+ e: React.MouseEvent,
110
+ materialValue: string
111
+ ): void => {
143
112
  if (!checkCanAnswerQuestion()) return;
144
113
  e.preventDefault();
145
114
  setDraggedValue(materialValue);
@@ -147,7 +116,7 @@ const MatchingActivityMaterialContent = ({
147
116
  setMousePosition({ x: e.clientX, y: e.clientY });
148
117
  };
149
118
 
150
- const handleMouseMove = (e) => {
119
+ const handleMouseMove = (e: React.MouseEvent): void => {
151
120
  if (!draggedValue) return;
152
121
 
153
122
  setMousePosition({ x: e.clientX, y: e.clientY });
@@ -164,7 +133,7 @@ const MatchingActivityMaterialContent = ({
164
133
  }
165
134
  };
166
135
 
167
- const handleMouseUp = () => {
136
+ const handleMouseUp = (): void => {
168
137
  if (dropTargetKey !== null && draggedValue !== null) {
169
138
  onChange(answer, dropTargetKey, draggedValue);
170
139
  }
@@ -174,7 +143,11 @@ const MatchingActivityMaterialContent = ({
174
143
  };
175
144
 
176
145
  // Touch drag handlers
177
- const handleTouchStart = (e, materialValue, element) => {
146
+ const handleTouchStart = (
147
+ e: React.TouchEvent,
148
+ materialValue: string,
149
+ element: HTMLElement
150
+ ): void => {
178
151
  if (!checkCanAnswerQuestion()) return;
179
152
  const touch = e.touches[0];
180
153
  setDraggedValue(materialValue);
@@ -183,7 +156,7 @@ const MatchingActivityMaterialContent = ({
183
156
  setSelectedValue(null);
184
157
  };
185
158
 
186
- const handleTouchMove = (e) => {
159
+ const handleTouchMove = (e: React.TouchEvent): void => {
187
160
  if (!draggedValue) return;
188
161
 
189
162
  const touch = e.touches[0];
@@ -204,7 +177,7 @@ const MatchingActivityMaterialContent = ({
204
177
  }
205
178
  };
206
179
 
207
- const handleTouchEnd = () => {
180
+ const handleTouchEnd = (): void => {
208
181
  if (dropTargetKey !== null && draggedValue !== null) {
209
182
  onChange(answer, dropTargetKey, draggedValue);
210
183
  }
@@ -215,13 +188,13 @@ const MatchingActivityMaterialContent = ({
215
188
  };
216
189
 
217
190
  // Click/tap to select (for easier mobile interaction)
218
- const handleSelectItem = (materialValue) => {
191
+ const handleSelectItem = (materialValue: string): void => {
219
192
  if (!checkCanAnswerQuestion()) return;
220
193
  setSelectedValue(materialValue);
221
194
  setDraggedValue(null);
222
195
  };
223
196
 
224
- const handleDropZoneClick = (answerMapKey) => {
197
+ const handleDropZoneClick = (answerMapKey: string): void => {
225
198
  if (selectedValue !== null) {
226
199
  onChange(answer, answerMapKey, selectedValue);
227
200
  setSelectedValue(null);
@@ -457,6 +430,7 @@ const MatchingActivityMaterialContent = ({
457
430
  <div className="mx-4 w-[2px] bg-catchup-lighter-gray"></div>
458
431
  <div className="flex-1">
459
432
  <div
433
+ ref={(el) => (dropZoneRefs.current[answerMapKey] = el)}
460
434
  data-matching-drop-zone={answerMapKey}
461
435
  onMouseEnter={() =>
462
436
  draggedValue && setDropTargetKey(answerMapKey)