catchup-library-web 1.21.0 → 1.21.1

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
@@ -5080,7 +5080,7 @@ var FillInTheBlanksActivityMaterialContent = ({
5080
5080
  }
5081
5081
  ),
5082
5082
  /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "w-full flex flex-row flex-wrap gap-x-2 gap-y-2 my-2", children: shuffleOptionList.map(
5083
- (option, index) => checkAnswerProvided(answerMap, option) ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)("div", { className: "opacity-30", children: /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5083
+ (option, index) => checkAnswerProvided(answerMap, option) ? /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5084
5084
  ShowMaterialMediaByContentType_default,
5085
5085
  {
5086
5086
  contentType: contentMap.type,
@@ -5088,7 +5088,7 @@ var FillInTheBlanksActivityMaterialContent = ({
5088
5088
  canFullScreen: true
5089
5089
  },
5090
5090
  `${uniqueValue}-${index}`
5091
- ) }, index) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5091
+ ) : /* @__PURE__ */ (0, import_jsx_runtime28.jsx)(
5092
5092
  "div",
5093
5093
  {
5094
5094
  ref: draggedOption === option ? dragElementRef : null,
@@ -5747,21 +5747,16 @@ var MatchingActivityMaterialContent = ({
5747
5747
  const [selectedValue, setSelectedValue] = (0, import_react21.useState)(null);
5748
5748
  const [draggedValue, setDraggedValue] = (0, import_react21.useState)(null);
5749
5749
  const [dropTargetKey, setDropTargetKey] = (0, import_react21.useState)(null);
5750
- const [draggedElement, setDraggedElement] = (0, import_react21.useState)(
5751
- null
5752
- );
5750
+ const [draggedElement, setDraggedElement] = (0, import_react21.useState)(null);
5753
5751
  const [isShuffled, setIsShuffled] = (0, import_react21.useState)(false);
5754
5752
  const [shuffledMaterialList, setShuffledMaterialList] = (0, import_react21.useState)([]);
5755
5753
  const dragElementRef = (0, import_react21.useRef)(null);
5756
- const [mousePosition, setMousePosition] = (0, import_react21.useState)({
5757
- x: 0,
5758
- y: 0
5759
- });
5760
- const [touchPosition, setTouchPosition] = (0, import_react21.useState)({
5761
- x: 0,
5762
- y: 0
5763
- });
5754
+ const [mousePosition, setMousePosition] = (0, import_react21.useState)({ x: 0, y: 0 });
5755
+ const [touchPosition, setTouchPosition] = (0, import_react21.useState)({ x: 0, y: 0 });
5764
5756
  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;
5765
5760
  (0, import_react21.useEffect)(() => {
5766
5761
  const shuffleArray2 = (array) => {
5767
5762
  if (!isShuffled) {
@@ -5783,10 +5778,44 @@ var MatchingActivityMaterialContent = ({
5783
5778
  }, [materialMap, isShuffled]);
5784
5779
  (0, import_react21.useEffect)(() => {
5785
5780
  if (!showCorrectAnswer) return;
5786
- answer.data.find(
5787
- (answerData) => answerData.type === "MATCHING"
5788
- ).answerMap = materialMap;
5781
+ answer.data.find((answerData) => answerData.type === "MATCHING").answerMap = materialMap;
5789
5782
  }, [showCorrectAnswer, answer, materialMap]);
5783
+ (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);
5816
+ }
5817
+ };
5818
+ }, [draggedValue, touchPosition.y]);
5790
5819
  const retrieveAnswerMap = () => {
5791
5820
  const foundIndex = answer.data.findIndex(
5792
5821
  (answerData) => answerData.type === "MATCHING"
@@ -5989,7 +6018,7 @@ var MatchingActivityMaterialContent = ({
5989
6018
  ),
5990
6019
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex-shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(DividerLine_default, {}) })
5991
6020
  ] }) : null,
5992
- /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "overflow-y-auto max-h-[500px]", children: Object.keys(answerMap).map((answerMapKey, index) => {
6021
+ Object.keys(answerMap).map((answerMapKey, index) => {
5993
6022
  const learnerAnswerState = checkAnswerState(
5994
6023
  materialMap[answerMapKey],
5995
6024
  answerMap[answerMapKey]
@@ -5999,16 +6028,16 @@ var MatchingActivityMaterialContent = ({
5999
6028
  "div",
6000
6029
  {
6001
6030
  className: `${contentMap.type === "TEXT" ? "h-catchup-activity-text-box-item" : "h-catchup-activity-media-box-item"} flex flex-col items-center justify-center border-2 rounded-catchup-xlarge transition-all duration-300 my-3 ${learnerAnswerState === "EMPTY" ? "border-catchup-blue" : learnerAnswerState === "CORRECT" ? "border-catchup-green" : learnerAnswerState === "INCORRECT" ? "border-catchup-red" : "border-catchup-blue"}`,
6002
- children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex flex-col items-center justify-center transition-all duration-300 px-4 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-lg whitespace-pre-wrap", children: constructInputWithSpecialExpressionList(
6003
- answerMapKey
6004
- ).map((inputPart, index2) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6005
- "span",
6006
- {
6007
- className: `${inputPart.isBold ? "font-bold" : ""} ${inputPart.isUnderline ? "underline" : ""}`,
6008
- children: inputPart.isEquation ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-xl", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_katex6.InlineMath, { math: inputPart.value }) }) : inputPart.value
6009
- },
6010
- index2
6011
- )) }) })
6031
+ children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "flex flex-col items-center justify-center transition-all duration-300 px-4 text-center", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("p", { className: "text-lg whitespace-pre-wrap", children: constructInputWithSpecialExpressionList(answerMapKey).map(
6032
+ (inputPart, index2) => /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(
6033
+ "span",
6034
+ {
6035
+ className: `${inputPart.isBold ? "font-bold" : ""} ${inputPart.isUnderline ? "underline" : ""}`,
6036
+ children: inputPart.isEquation ? /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("span", { className: "text-xl", children: /* @__PURE__ */ (0, import_jsx_runtime32.jsx)(import_react_katex6.InlineMath, { math: inputPart.value }) }) : inputPart.value
6037
+ },
6038
+ index2
6039
+ )
6040
+ ) }) })
6012
6041
  }
6013
6042
  ) }),
6014
6043
  /* @__PURE__ */ (0, import_jsx_runtime32.jsx)("div", { className: "mx-4 w-[2px] bg-catchup-lighter-gray" }),
@@ -6054,7 +6083,7 @@ var MatchingActivityMaterialContent = ({
6054
6083
  }
6055
6084
  ) })
6056
6085
  ] }, index);
6057
- }) })
6086
+ })
6058
6087
  ] });
6059
6088
  };
6060
6089
  var MatchingActivityMaterialContent_default = MatchingActivityMaterialContent;
package/dist/index.mjs CHANGED
@@ -4864,7 +4864,7 @@ var FillInTheBlanksActivityMaterialContent = ({
4864
4864
  }
4865
4865
  ),
4866
4866
  /* @__PURE__ */ jsx28("div", { className: "w-full flex flex-row flex-wrap gap-x-2 gap-y-2 my-2", children: shuffleOptionList.map(
4867
- (option, index) => checkAnswerProvided(answerMap, option) ? /* @__PURE__ */ jsx28("div", { className: "opacity-30", children: /* @__PURE__ */ jsx28(
4867
+ (option, index) => checkAnswerProvided(answerMap, option) ? /* @__PURE__ */ jsx28(
4868
4868
  ShowMaterialMediaByContentType_default,
4869
4869
  {
4870
4870
  contentType: contentMap.type,
@@ -4872,7 +4872,7 @@ var FillInTheBlanksActivityMaterialContent = ({
4872
4872
  canFullScreen: true
4873
4873
  },
4874
4874
  `${uniqueValue}-${index}`
4875
- ) }, index) : /* @__PURE__ */ jsx28(
4875
+ ) : /* @__PURE__ */ jsx28(
4876
4876
  "div",
4877
4877
  {
4878
4878
  ref: draggedOption === option ? dragElementRef : null,
@@ -5531,21 +5531,16 @@ var MatchingActivityMaterialContent = ({
5531
5531
  const [selectedValue, setSelectedValue] = useState20(null);
5532
5532
  const [draggedValue, setDraggedValue] = useState20(null);
5533
5533
  const [dropTargetKey, setDropTargetKey] = useState20(null);
5534
- const [draggedElement, setDraggedElement] = useState20(
5535
- null
5536
- );
5534
+ const [draggedElement, setDraggedElement] = useState20(null);
5537
5535
  const [isShuffled, setIsShuffled] = useState20(false);
5538
5536
  const [shuffledMaterialList, setShuffledMaterialList] = useState20([]);
5539
5537
  const dragElementRef = useRef6(null);
5540
- const [mousePosition, setMousePosition] = useState20({
5541
- x: 0,
5542
- y: 0
5543
- });
5544
- const [touchPosition, setTouchPosition] = useState20({
5545
- x: 0,
5546
- y: 0
5547
- });
5538
+ const [mousePosition, setMousePosition] = useState20({ x: 0, y: 0 });
5539
+ const [touchPosition, setTouchPosition] = useState20({ x: 0, y: 0 });
5548
5540
  const itemsRef = useRef6(null);
5541
+ const scrollIntervalRef = useRef6(null);
5542
+ const SCROLL_THRESHOLD = 100;
5543
+ const SCROLL_SPEED = 10;
5549
5544
  useEffect11(() => {
5550
5545
  const shuffleArray2 = (array) => {
5551
5546
  if (!isShuffled) {
@@ -5567,10 +5562,44 @@ var MatchingActivityMaterialContent = ({
5567
5562
  }, [materialMap, isShuffled]);
5568
5563
  useEffect11(() => {
5569
5564
  if (!showCorrectAnswer) return;
5570
- answer.data.find(
5571
- (answerData) => answerData.type === "MATCHING"
5572
- ).answerMap = materialMap;
5565
+ answer.data.find((answerData) => answerData.type === "MATCHING").answerMap = materialMap;
5573
5566
  }, [showCorrectAnswer, answer, materialMap]);
5567
+ 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);
5600
+ }
5601
+ };
5602
+ }, [draggedValue, touchPosition.y]);
5574
5603
  const retrieveAnswerMap = () => {
5575
5604
  const foundIndex = answer.data.findIndex(
5576
5605
  (answerData) => answerData.type === "MATCHING"
@@ -5773,7 +5802,7 @@ var MatchingActivityMaterialContent = ({
5773
5802
  ),
5774
5803
  /* @__PURE__ */ jsx32("div", { className: "flex-shrink-0", children: /* @__PURE__ */ jsx32(DividerLine_default, {}) })
5775
5804
  ] }) : null,
5776
- /* @__PURE__ */ jsx32("div", { className: "overflow-y-auto max-h-[500px]", children: Object.keys(answerMap).map((answerMapKey, index) => {
5805
+ Object.keys(answerMap).map((answerMapKey, index) => {
5777
5806
  const learnerAnswerState = checkAnswerState(
5778
5807
  materialMap[answerMapKey],
5779
5808
  answerMap[answerMapKey]
@@ -5783,16 +5812,16 @@ var MatchingActivityMaterialContent = ({
5783
5812
  "div",
5784
5813
  {
5785
5814
  className: `${contentMap.type === "TEXT" ? "h-catchup-activity-text-box-item" : "h-catchup-activity-media-box-item"} flex flex-col items-center justify-center border-2 rounded-catchup-xlarge transition-all duration-300 my-3 ${learnerAnswerState === "EMPTY" ? "border-catchup-blue" : learnerAnswerState === "CORRECT" ? "border-catchup-green" : learnerAnswerState === "INCORRECT" ? "border-catchup-red" : "border-catchup-blue"}`,
5786
- children: /* @__PURE__ */ jsx32("div", { className: "flex flex-col items-center justify-center transition-all duration-300 px-4 text-center", children: /* @__PURE__ */ jsx32("p", { className: "text-lg whitespace-pre-wrap", children: constructInputWithSpecialExpressionList(
5787
- answerMapKey
5788
- ).map((inputPart, index2) => /* @__PURE__ */ jsx32(
5789
- "span",
5790
- {
5791
- className: `${inputPart.isBold ? "font-bold" : ""} ${inputPart.isUnderline ? "underline" : ""}`,
5792
- children: inputPart.isEquation ? /* @__PURE__ */ jsx32("span", { className: "text-xl", children: /* @__PURE__ */ jsx32(InlineMath6, { math: inputPart.value }) }) : inputPart.value
5793
- },
5794
- index2
5795
- )) }) })
5815
+ children: /* @__PURE__ */ jsx32("div", { className: "flex flex-col items-center justify-center transition-all duration-300 px-4 text-center", children: /* @__PURE__ */ jsx32("p", { className: "text-lg whitespace-pre-wrap", children: constructInputWithSpecialExpressionList(answerMapKey).map(
5816
+ (inputPart, index2) => /* @__PURE__ */ jsx32(
5817
+ "span",
5818
+ {
5819
+ className: `${inputPart.isBold ? "font-bold" : ""} ${inputPart.isUnderline ? "underline" : ""}`,
5820
+ children: inputPart.isEquation ? /* @__PURE__ */ jsx32("span", { className: "text-xl", children: /* @__PURE__ */ jsx32(InlineMath6, { math: inputPart.value }) }) : inputPart.value
5821
+ },
5822
+ index2
5823
+ )
5824
+ ) }) })
5796
5825
  }
5797
5826
  ) }),
5798
5827
  /* @__PURE__ */ jsx32("div", { className: "mx-4 w-[2px] bg-catchup-lighter-gray" }),
@@ -5838,7 +5867,7 @@ var MatchingActivityMaterialContent = ({
5838
5867
  }
5839
5868
  ) })
5840
5869
  ] }, index);
5841
- }) })
5870
+ })
5842
5871
  ] });
5843
5872
  };
5844
5873
  var MatchingActivityMaterialContent_default = MatchingActivityMaterialContent;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "catchup-library-web",
3
- "version": "1.21.00",
3
+ "version": "1.21.01",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "scripts": {
@@ -261,14 +261,12 @@ const FillInTheBlanksActivityMaterialContent = ({
261
261
  <div className="w-full flex flex-row flex-wrap gap-x-2 gap-y-2 my-2">
262
262
  {shuffleOptionList.map((option, index) =>
263
263
  checkAnswerProvided(answerMap, option) ? (
264
- <div className="opacity-30" key={index}>
265
- <ShowMaterialMediaByContentType
266
- key={`${uniqueValue}-${index}`}
267
- contentType={contentMap.type}
268
- src={option}
269
- canFullScreen={true}
270
- />
271
- </div>
264
+ <ShowMaterialMediaByContentType
265
+ key={`${uniqueValue}-${index}`}
266
+ contentType={contentMap.type}
267
+ src={option}
268
+ canFullScreen={true}
269
+ />
272
270
  ) : (
273
271
  <div
274
272
  key={index}
@@ -2,7 +2,6 @@ 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";
6
5
  import DividerLine from "../../dividers/DividerLine";
7
6
 
8
7
  const MatchingActivityMaterialContent = ({
@@ -14,28 +13,25 @@ const MatchingActivityMaterialContent = ({
14
13
  onChange,
15
14
  isPreview,
16
15
  showCorrectAnswer,
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
- );
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);
24
21
  const [isShuffled, setIsShuffled] = useState(false);
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);
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
36
32
 
37
33
  useEffect(() => {
38
- const shuffleArray = (array: any) => {
34
+ const shuffleArray = (array) => {
39
35
  if (!isShuffled) {
40
36
  const copyArray = JSON.parse(JSON.stringify(array));
41
37
  for (let i = copyArray.length - 1; i > 0; i--) {
@@ -47,7 +43,7 @@ const MatchingActivityMaterialContent = ({
47
43
  }
48
44
  return array;
49
45
  };
50
- const materialList: any = [];
46
+ const materialList = [];
51
47
  Object.keys(materialMap).forEach((materialKey) => {
52
48
  materialList.push(materialMap[materialKey]);
53
49
  });
@@ -56,32 +52,84 @@ const MatchingActivityMaterialContent = ({
56
52
 
57
53
  useEffect(() => {
58
54
  if (!showCorrectAnswer) return;
59
- answer.data.find(
60
- (answerData: any) => answerData.type === "MATCHING"
61
- ).answerMap = materialMap;
55
+ answer.data.find((answerData) => answerData.type === "MATCHING").answerMap =
56
+ materialMap;
62
57
  }, [showCorrectAnswer, answer, materialMap]);
63
58
 
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
87
+ 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);
101
+ }
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]);
112
+
64
113
  const retrieveAnswerMap = () => {
65
114
  const foundIndex = answer.data.findIndex(
66
- (answerData: any) => answerData.type === "MATCHING"
115
+ (answerData) => answerData.type === "MATCHING"
67
116
  );
68
117
  return answer.data[foundIndex].answerMap;
69
118
  };
70
119
 
71
- const retrieveFilteredMaterialList = (answerMap: any) => {
72
- const selectedValueList: any = [];
120
+ const retrieveFilteredMaterialList = (answerMap) => {
121
+ const selectedValueList = [];
73
122
  Object.keys(answerMap).forEach((key) => {
74
123
  selectedValueList.push(answerMap[key]);
75
124
  });
76
125
 
77
126
  return shuffledMaterialList.filter(
78
127
  (material) =>
79
- selectedValueList.findIndex((value: string) => material === value) ===
80
- -1
128
+ selectedValueList.findIndex((value) => material === value) === -1
81
129
  );
82
130
  };
83
131
 
84
- const checkAnswerState = (correctAnswer: string, learnerAnswer: string) => {
132
+ const checkAnswerState = (correctAnswer, learnerAnswer) => {
85
133
  if (!isPreview) return null;
86
134
  if (!learnerAnswer) return "EMPTY";
87
135
  if (correctAnswer === learnerAnswer) {
@@ -91,10 +139,7 @@ const MatchingActivityMaterialContent = ({
91
139
  };
92
140
 
93
141
  // Mouse drag handlers
94
- const handleMouseDown = (
95
- e: React.MouseEvent,
96
- materialValue: string
97
- ): void => {
142
+ const handleMouseDown = (e, materialValue) => {
98
143
  if (!checkCanAnswerQuestion()) return;
99
144
  e.preventDefault();
100
145
  setDraggedValue(materialValue);
@@ -102,7 +147,7 @@ const MatchingActivityMaterialContent = ({
102
147
  setMousePosition({ x: e.clientX, y: e.clientY });
103
148
  };
104
149
 
105
- const handleMouseMove = (e: React.MouseEvent): void => {
150
+ const handleMouseMove = (e) => {
106
151
  if (!draggedValue) return;
107
152
 
108
153
  setMousePosition({ x: e.clientX, y: e.clientY });
@@ -119,7 +164,7 @@ const MatchingActivityMaterialContent = ({
119
164
  }
120
165
  };
121
166
 
122
- const handleMouseUp = (): void => {
167
+ const handleMouseUp = () => {
123
168
  if (dropTargetKey !== null && draggedValue !== null) {
124
169
  onChange(answer, dropTargetKey, draggedValue);
125
170
  }
@@ -129,11 +174,7 @@ const MatchingActivityMaterialContent = ({
129
174
  };
130
175
 
131
176
  // Touch drag handlers
132
- const handleTouchStart = (
133
- e: React.TouchEvent,
134
- materialValue: string,
135
- element: HTMLElement
136
- ): void => {
177
+ const handleTouchStart = (e, materialValue, element) => {
137
178
  if (!checkCanAnswerQuestion()) return;
138
179
  const touch = e.touches[0];
139
180
  setDraggedValue(materialValue);
@@ -142,7 +183,7 @@ const MatchingActivityMaterialContent = ({
142
183
  setSelectedValue(null);
143
184
  };
144
185
 
145
- const handleTouchMove = (e: React.TouchEvent): void => {
186
+ const handleTouchMove = (e) => {
146
187
  if (!draggedValue) return;
147
188
 
148
189
  const touch = e.touches[0];
@@ -163,7 +204,7 @@ const MatchingActivityMaterialContent = ({
163
204
  }
164
205
  };
165
206
 
166
- const handleTouchEnd = (): void => {
207
+ const handleTouchEnd = () => {
167
208
  if (dropTargetKey !== null && draggedValue !== null) {
168
209
  onChange(answer, dropTargetKey, draggedValue);
169
210
  }
@@ -174,13 +215,13 @@ const MatchingActivityMaterialContent = ({
174
215
  };
175
216
 
176
217
  // Click/tap to select (for easier mobile interaction)
177
- const handleSelectItem = (materialValue: string): void => {
218
+ const handleSelectItem = (materialValue) => {
178
219
  if (!checkCanAnswerQuestion()) return;
179
220
  setSelectedValue(materialValue);
180
221
  setDraggedValue(null);
181
222
  };
182
223
 
183
- const handleDropZoneClick = (answerMapKey: string): void => {
224
+ const handleDropZoneClick = (answerMapKey) => {
184
225
  if (selectedValue !== null) {
185
226
  onChange(answer, answerMapKey, selectedValue);
186
227
  setSelectedValue(null);
@@ -365,36 +406,34 @@ const MatchingActivityMaterialContent = ({
365
406
  </>
366
407
  ) : null}
367
408
 
368
- <div className="overflow-y-auto max-h-[500px]">
369
- {Object.keys(answerMap).map((answerMapKey, index) => {
370
- const learnerAnswerState = checkAnswerState(
371
- materialMap[answerMapKey],
372
- answerMap[answerMapKey]
373
- );
409
+ {Object.keys(answerMap).map((answerMapKey, index) => {
410
+ const learnerAnswerState = checkAnswerState(
411
+ materialMap[answerMapKey],
412
+ answerMap[answerMapKey]
413
+ );
374
414
 
375
- return (
376
- <div key={index} className="flex flex-row w-full">
377
- <div className="w-1/3">
378
- <div
379
- className={`${
380
- contentMap.type === "TEXT"
381
- ? "h-catchup-activity-text-box-item"
382
- : "h-catchup-activity-media-box-item"
383
- } flex flex-col items-center justify-center border-2 rounded-catchup-xlarge transition-all duration-300 my-3 ${
384
- learnerAnswerState === "EMPTY"
385
- ? "border-catchup-blue"
386
- : learnerAnswerState === "CORRECT"
387
- ? "border-catchup-green"
388
- : learnerAnswerState === "INCORRECT"
389
- ? "border-catchup-red"
390
- : "border-catchup-blue"
391
- }`}
392
- >
393
- <div className="flex flex-col items-center justify-center transition-all duration-300 px-4 text-center">
394
- <p className="text-lg whitespace-pre-wrap">
395
- {constructInputWithSpecialExpressionList(
396
- answerMapKey
397
- ).map((inputPart, index) => (
415
+ return (
416
+ <div key={index} className="flex flex-row w-full">
417
+ <div className="w-1/3">
418
+ <div
419
+ className={`${
420
+ contentMap.type === "TEXT"
421
+ ? "h-catchup-activity-text-box-item"
422
+ : "h-catchup-activity-media-box-item"
423
+ } flex flex-col items-center justify-center border-2 rounded-catchup-xlarge transition-all duration-300 my-3 ${
424
+ learnerAnswerState === "EMPTY"
425
+ ? "border-catchup-blue"
426
+ : learnerAnswerState === "CORRECT"
427
+ ? "border-catchup-green"
428
+ : learnerAnswerState === "INCORRECT"
429
+ ? "border-catchup-red"
430
+ : "border-catchup-blue"
431
+ }`}
432
+ >
433
+ <div className="flex flex-col items-center justify-center transition-all duration-300 px-4 text-center">
434
+ <p className="text-lg whitespace-pre-wrap">
435
+ {constructInputWithSpecialExpressionList(answerMapKey).map(
436
+ (inputPart, index) => (
398
437
  <span
399
438
  key={index}
400
439
  className={`${inputPart.isBold ? "font-bold" : ""} ${
@@ -409,86 +448,86 @@ const MatchingActivityMaterialContent = ({
409
448
  inputPart.value
410
449
  )}
411
450
  </span>
412
- ))}
413
- </p>
414
- </div>
451
+ )
452
+ )}
453
+ </p>
415
454
  </div>
416
455
  </div>
417
- <div className="mx-4 w-[2px] bg-catchup-lighter-gray"></div>
418
- <div className="flex-1">
456
+ </div>
457
+ <div className="mx-4 w-[2px] bg-catchup-lighter-gray"></div>
458
+ <div className="flex-1">
459
+ <div
460
+ data-matching-drop-zone={answerMapKey}
461
+ onMouseEnter={() =>
462
+ draggedValue && setDropTargetKey(answerMapKey)
463
+ }
464
+ onMouseLeave={() => setDropTargetKey(null)}
465
+ onClick={() => handleDropZoneClick(answerMapKey)}
466
+ className={`${
467
+ dropTargetKey === answerMapKey
468
+ ? "bg-catchup-light-blue ring-2 ring-blue-400"
469
+ : ""
470
+ } ${
471
+ contentMap.type === "TEXT"
472
+ ? "h-catchup-activity-text-box-item"
473
+ : "h-catchup-activity-media-box-item"
474
+ } flex flex-col items-center justify-center border-2 rounded-catchup-xlarge cursor-pointer transition-all duration-300 my-3 ${
475
+ learnerAnswerState === "EMPTY"
476
+ ? "border-catchup-blue"
477
+ : learnerAnswerState === "CORRECT"
478
+ ? "border-catchup-green"
479
+ : learnerAnswerState === "INCORRECT"
480
+ ? "border-catchup-red"
481
+ : "border-catchup-blue"
482
+ }`}
483
+ >
419
484
  <div
420
- data-matching-drop-zone={answerMapKey}
421
- onMouseEnter={() =>
422
- draggedValue && setDropTargetKey(answerMapKey)
423
- }
424
- onMouseLeave={() => setDropTargetKey(null)}
425
- onClick={() => handleDropZoneClick(answerMapKey)}
426
- className={`${
427
- dropTargetKey === answerMapKey
428
- ? "bg-catchup-light-blue ring-2 ring-blue-400"
429
- : ""
430
- } ${
431
- contentMap.type === "TEXT"
432
- ? "h-catchup-activity-text-box-item"
433
- : "h-catchup-activity-media-box-item"
434
- } flex flex-col items-center justify-center border-2 rounded-catchup-xlarge cursor-pointer transition-all duration-300 my-3 ${
435
- learnerAnswerState === "EMPTY"
436
- ? "border-catchup-blue"
437
- : learnerAnswerState === "CORRECT"
438
- ? "border-catchup-green"
439
- : learnerAnswerState === "INCORRECT"
440
- ? "border-catchup-red"
441
- : "border-catchup-blue"
442
- }`}
485
+ className="h-full flex-1 flex flex-row items-center justify-center px-4"
486
+ onClick={(e) => {
487
+ e.stopPropagation();
488
+ if (checkCanAnswerQuestion() && answerMap[answerMapKey]) {
489
+ onChange(answer, answerMapKey, null);
490
+ setSelectedValue(null);
491
+ }
492
+ }}
443
493
  >
444
- <div
445
- className="h-full flex-1 flex flex-row items-center justify-center px-4"
446
- onClick={(e) => {
447
- e.stopPropagation();
448
- if (checkCanAnswerQuestion() && answerMap[answerMapKey]) {
449
- onChange(answer, answerMapKey, null);
450
- setSelectedValue(null);
451
- }
452
- }}
453
- >
454
- {answerMap[answerMapKey] ? (
455
- contentMap.type === "TEXT" ? (
456
- <p className="text-lg whitespace-pre-wrap">
457
- {constructInputWithSpecialExpressionList(
458
- answerMap[answerMapKey]
459
- ).map((inputPart, index) => (
460
- <span
461
- key={index}
462
- className={`${
463
- inputPart.isBold ? "font-bold" : ""
464
- } ${inputPart.isUnderline ? "underline" : ""}`}
465
- >
466
- {inputPart.isEquation ? (
467
- <span className="text-xl">
468
- <InlineMath math={inputPart.value} />
469
- </span>
470
- ) : (
471
- inputPart.value
472
- )}
473
- </span>
474
- ))}
475
- </p>
476
- ) : (
477
- <ShowMaterialMediaByContentType
478
- key={`${uniqueValue}-${index}`}
479
- contentType={contentMap.type}
480
- src={answerMap[answerMapKey]}
481
- canFullScreen={false}
482
- />
483
- )
484
- ) : null}
485
- </div>
494
+ {answerMap[answerMapKey] ? (
495
+ contentMap.type === "TEXT" ? (
496
+ <p className="text-lg whitespace-pre-wrap">
497
+ {constructInputWithSpecialExpressionList(
498
+ answerMap[answerMapKey]
499
+ ).map((inputPart, index) => (
500
+ <span
501
+ key={index}
502
+ className={`${
503
+ inputPart.isBold ? "font-bold" : ""
504
+ } ${inputPart.isUnderline ? "underline" : ""}`}
505
+ >
506
+ {inputPart.isEquation ? (
507
+ <span className="text-xl">
508
+ <InlineMath math={inputPart.value} />
509
+ </span>
510
+ ) : (
511
+ inputPart.value
512
+ )}
513
+ </span>
514
+ ))}
515
+ </p>
516
+ ) : (
517
+ <ShowMaterialMediaByContentType
518
+ key={`${uniqueValue}-${index}`}
519
+ contentType={contentMap.type}
520
+ src={answerMap[answerMapKey]}
521
+ canFullScreen={false}
522
+ />
523
+ )
524
+ ) : null}
486
525
  </div>
487
526
  </div>
488
527
  </div>
489
- );
490
- })}
491
- </div>
528
+ </div>
529
+ );
530
+ })}
492
531
  </div>
493
532
  );
494
533
  };