catchup-library-web 1.20.36 → 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.
@@ -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,24 +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 [touchPosition, setTouchPosition] = useState<{ x: number; y: number }>({
28
- x: 0,
29
- y: 0,
30
- });
31
- 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
32
32
 
33
33
  useEffect(() => {
34
- const shuffleArray = (array: any) => {
34
+ const shuffleArray = (array) => {
35
35
  if (!isShuffled) {
36
36
  const copyArray = JSON.parse(JSON.stringify(array));
37
37
  for (let i = copyArray.length - 1; i > 0; i--) {
@@ -43,7 +43,7 @@ const MatchingActivityMaterialContent = ({
43
43
  }
44
44
  return array;
45
45
  };
46
- const materialList: any = [];
46
+ const materialList = [];
47
47
  Object.keys(materialMap).forEach((materialKey) => {
48
48
  materialList.push(materialMap[materialKey]);
49
49
  });
@@ -52,32 +52,84 @@ const MatchingActivityMaterialContent = ({
52
52
 
53
53
  useEffect(() => {
54
54
  if (!showCorrectAnswer) return;
55
- answer.data.find(
56
- (answerData: any) => answerData.type === "MATCHING"
57
- ).answerMap = materialMap;
55
+ answer.data.find((answerData) => answerData.type === "MATCHING").answerMap =
56
+ materialMap;
58
57
  }, [showCorrectAnswer, answer, materialMap]);
59
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
+
60
113
  const retrieveAnswerMap = () => {
61
114
  const foundIndex = answer.data.findIndex(
62
- (answerData: any) => answerData.type === "MATCHING"
115
+ (answerData) => answerData.type === "MATCHING"
63
116
  );
64
117
  return answer.data[foundIndex].answerMap;
65
118
  };
66
119
 
67
- const retrieveFilteredMaterialList = (answerMap: any) => {
68
- const selectedValueList: any = [];
120
+ const retrieveFilteredMaterialList = (answerMap) => {
121
+ const selectedValueList = [];
69
122
  Object.keys(answerMap).forEach((key) => {
70
123
  selectedValueList.push(answerMap[key]);
71
124
  });
72
125
 
73
126
  return shuffledMaterialList.filter(
74
127
  (material) =>
75
- selectedValueList.findIndex((value: string) => material === value) ===
76
- -1
128
+ selectedValueList.findIndex((value) => material === value) === -1
77
129
  );
78
130
  };
79
131
 
80
- const checkAnswerState = (correctAnswer: string, learnerAnswer: string) => {
132
+ const checkAnswerState = (correctAnswer, learnerAnswer) => {
81
133
  if (!isPreview) return null;
82
134
  if (!learnerAnswer) return "EMPTY";
83
135
  if (correctAnswer === learnerAnswer) {
@@ -87,30 +139,42 @@ const MatchingActivityMaterialContent = ({
87
139
  };
88
140
 
89
141
  // Mouse drag handlers
90
- const handleMouseDown = (
91
- e: React.MouseEvent,
92
- materialValue: string
93
- ): void => {
142
+ const handleMouseDown = (e, materialValue) => {
94
143
  if (!checkCanAnswerQuestion()) return;
95
144
  e.preventDefault();
96
145
  setDraggedValue(materialValue);
97
146
  setSelectedValue(null);
147
+ setMousePosition({ x: e.clientX, y: e.clientY });
98
148
  };
99
149
 
100
- const handleMouseUp = (): void => {
150
+ const handleMouseMove = (e) => {
151
+ if (!draggedValue) return;
152
+
153
+ setMousePosition({ x: e.clientX, y: e.clientY });
154
+
155
+ // Find the element under the mouse point
156
+ const elementUnder = document.elementFromPoint(e.clientX, e.clientY);
157
+ const dropZone = elementUnder?.closest("[data-matching-drop-zone]");
158
+
159
+ if (dropZone) {
160
+ const dropKey = dropZone.getAttribute("data-matching-drop-zone");
161
+ setDropTargetKey(dropKey);
162
+ } else {
163
+ setDropTargetKey(null);
164
+ }
165
+ };
166
+
167
+ const handleMouseUp = () => {
101
168
  if (dropTargetKey !== null && draggedValue !== null) {
102
169
  onChange(answer, dropTargetKey, draggedValue);
103
170
  }
104
171
  setDraggedValue(null);
105
172
  setDropTargetKey(null);
173
+ setMousePosition({ x: 0, y: 0 });
106
174
  };
107
175
 
108
176
  // Touch drag handlers
109
- const handleTouchStart = (
110
- e: React.TouchEvent,
111
- materialValue: string,
112
- element: HTMLElement
113
- ): void => {
177
+ const handleTouchStart = (e, materialValue, element) => {
114
178
  if (!checkCanAnswerQuestion()) return;
115
179
  const touch = e.touches[0];
116
180
  setDraggedValue(materialValue);
@@ -119,7 +183,7 @@ const MatchingActivityMaterialContent = ({
119
183
  setSelectedValue(null);
120
184
  };
121
185
 
122
- const handleTouchMove = (e: React.TouchEvent): void => {
186
+ const handleTouchMove = (e) => {
123
187
  if (!draggedValue) return;
124
188
 
125
189
  const touch = e.touches[0];
@@ -140,23 +204,24 @@ const MatchingActivityMaterialContent = ({
140
204
  }
141
205
  };
142
206
 
143
- const handleTouchEnd = (): void => {
207
+ const handleTouchEnd = () => {
144
208
  if (dropTargetKey !== null && draggedValue !== null) {
145
209
  onChange(answer, dropTargetKey, draggedValue);
146
210
  }
147
211
  setDraggedValue(null);
148
212
  setDropTargetKey(null);
149
213
  setDraggedElement(null);
214
+ setTouchPosition({ x: 0, y: 0 });
150
215
  };
151
216
 
152
217
  // Click/tap to select (for easier mobile interaction)
153
- const handleSelectItem = (materialValue: string): void => {
218
+ const handleSelectItem = (materialValue) => {
154
219
  if (!checkCanAnswerQuestion()) return;
155
220
  setSelectedValue(materialValue);
156
221
  setDraggedValue(null);
157
222
  };
158
223
 
159
- const handleDropZoneClick = (answerMapKey: string): void => {
224
+ const handleDropZoneClick = (answerMapKey) => {
160
225
  if (selectedValue !== null) {
161
226
  onChange(answer, answerMapKey, selectedValue);
162
227
  setSelectedValue(null);
@@ -167,7 +232,55 @@ const MatchingActivityMaterialContent = ({
167
232
  const filteredMaterialList = retrieveFilteredMaterialList(answerMap);
168
233
 
169
234
  return (
170
- <div onMouseUp={handleMouseUp}>
235
+ <div onMouseMove={handleMouseMove} onMouseUp={handleMouseUp}>
236
+ {/* Floating drag preview for mouse */}
237
+ {draggedValue && mousePosition.x > 0 && (
238
+ <div
239
+ className="fixed pointer-events-none z-50 opacity-80"
240
+ style={{
241
+ left: `${mousePosition.x}px`,
242
+ top: `${mousePosition.y}px`,
243
+ transform: "translate(-50%, -50%)",
244
+ }}
245
+ >
246
+ {contentMap.type === "TEXT" ? (
247
+ <div className="border-catchup-blue border-2 rounded-catchup-xlarge bg-white shadow-lg">
248
+ <div className="flex flex-col items-center justify-center m-2 min-w-[200px] px-4">
249
+ <p className="text-lg whitespace-pre-wrap">
250
+ {constructInputWithSpecialExpressionList(draggedValue).map(
251
+ (inputPart, index) => (
252
+ <span
253
+ key={index}
254
+ className={`${inputPart.isBold ? "font-bold" : ""} ${
255
+ inputPart.isUnderline ? "underline" : ""
256
+ }`}
257
+ >
258
+ {inputPart.isEquation ? (
259
+ <span className="text-xl">
260
+ <InlineMath math={inputPart.value} />
261
+ </span>
262
+ ) : (
263
+ inputPart.value
264
+ )}
265
+ </span>
266
+ )
267
+ )}
268
+ </p>
269
+ </div>
270
+ </div>
271
+ ) : (
272
+ <div className="border-catchup-blue border-2 rounded-catchup-xlarge bg-white shadow-lg">
273
+ <ShowMaterialMediaByContentType
274
+ key={`${uniqueValue}-drag-mouse`}
275
+ contentType={contentMap.type}
276
+ src={draggedValue}
277
+ canFullScreen={false}
278
+ />
279
+ </div>
280
+ )}
281
+ </div>
282
+ )}
283
+
171
284
  {/* Floating drag preview for touch */}
172
285
  {draggedValue && touchPosition.x > 0 && (
173
286
  <div
@@ -206,7 +319,7 @@ const MatchingActivityMaterialContent = ({
206
319
  ) : (
207
320
  <div className="border-catchup-blue border-2 rounded-catchup-xlarge bg-white shadow-lg">
208
321
  <ShowMaterialMediaByContentType
209
- key={`${uniqueValue}-drag`}
322
+ key={`${uniqueValue}-drag-touch`}
210
323
  contentType={contentMap.type}
211
324
  src={draggedValue}
212
325
  canFullScreen={false}
@@ -293,36 +406,34 @@ const MatchingActivityMaterialContent = ({
293
406
  </>
294
407
  ) : null}
295
408
 
296
- <div className="overflow-y-auto max-h-[500px]">
297
- {Object.keys(answerMap).map((answerMapKey, index) => {
298
- const learnerAnswerState = checkAnswerState(
299
- materialMap[answerMapKey],
300
- answerMap[answerMapKey]
301
- );
409
+ {Object.keys(answerMap).map((answerMapKey, index) => {
410
+ const learnerAnswerState = checkAnswerState(
411
+ materialMap[answerMapKey],
412
+ answerMap[answerMapKey]
413
+ );
302
414
 
303
- return (
304
- <div key={index} className="flex flex-row w-full">
305
- <div className="w-1/3">
306
- <div
307
- className={`${
308
- contentMap.type === "TEXT"
309
- ? "h-catchup-activity-text-box-item"
310
- : "h-catchup-activity-media-box-item"
311
- } flex flex-col items-center justify-center border-2 rounded-catchup-xlarge transition-all duration-300 my-3 ${
312
- learnerAnswerState === "EMPTY"
313
- ? "border-catchup-blue"
314
- : learnerAnswerState === "CORRECT"
315
- ? "border-catchup-green"
316
- : learnerAnswerState === "INCORRECT"
317
- ? "border-catchup-red"
318
- : "border-catchup-blue"
319
- }`}
320
- >
321
- <div className="flex flex-col items-center justify-center transition-all duration-300 px-4 text-center">
322
- <p className="text-lg whitespace-pre-wrap">
323
- {constructInputWithSpecialExpressionList(
324
- answerMapKey
325
- ).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) => (
326
437
  <span
327
438
  key={index}
328
439
  className={`${inputPart.isBold ? "font-bold" : ""} ${
@@ -337,86 +448,86 @@ const MatchingActivityMaterialContent = ({
337
448
  inputPart.value
338
449
  )}
339
450
  </span>
340
- ))}
341
- </p>
342
- </div>
451
+ )
452
+ )}
453
+ </p>
343
454
  </div>
344
455
  </div>
345
- <div className="mx-4 w-[2px] bg-catchup-lighter-gray"></div>
346
- <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
+ >
347
484
  <div
348
- data-matching-drop-zone={answerMapKey}
349
- onMouseEnter={() =>
350
- draggedValue && setDropTargetKey(answerMapKey)
351
- }
352
- onMouseLeave={() => setDropTargetKey(null)}
353
- onClick={() => handleDropZoneClick(answerMapKey)}
354
- className={`${
355
- dropTargetKey === answerMapKey
356
- ? "bg-catchup-light-blue ring-2 ring-blue-400"
357
- : ""
358
- } ${
359
- contentMap.type === "TEXT"
360
- ? "h-catchup-activity-text-box-item"
361
- : "h-catchup-activity-media-box-item"
362
- } flex flex-col items-center justify-center border-2 rounded-catchup-xlarge cursor-pointer transition-all duration-300 my-3 ${
363
- learnerAnswerState === "EMPTY"
364
- ? "border-catchup-blue"
365
- : learnerAnswerState === "CORRECT"
366
- ? "border-catchup-green"
367
- : learnerAnswerState === "INCORRECT"
368
- ? "border-catchup-red"
369
- : "border-catchup-blue"
370
- }`}
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
+ }}
371
493
  >
372
- <div
373
- className="h-full flex-1 flex flex-row items-center justify-center px-4"
374
- onClick={(e) => {
375
- e.stopPropagation();
376
- if (checkCanAnswerQuestion() && answerMap[answerMapKey]) {
377
- onChange(answer, answerMapKey, null);
378
- setSelectedValue(null);
379
- }
380
- }}
381
- >
382
- {answerMap[answerMapKey] ? (
383
- contentMap.type === "TEXT" ? (
384
- <p className="text-lg whitespace-pre-wrap">
385
- {constructInputWithSpecialExpressionList(
386
- answerMap[answerMapKey]
387
- ).map((inputPart, index) => (
388
- <span
389
- key={index}
390
- className={`${
391
- inputPart.isBold ? "font-bold" : ""
392
- } ${inputPart.isUnderline ? "underline" : ""}`}
393
- >
394
- {inputPart.isEquation ? (
395
- <span className="text-xl">
396
- <InlineMath math={inputPart.value} />
397
- </span>
398
- ) : (
399
- inputPart.value
400
- )}
401
- </span>
402
- ))}
403
- </p>
404
- ) : (
405
- <ShowMaterialMediaByContentType
406
- key={`${uniqueValue}-${index}`}
407
- contentType={contentMap.type}
408
- src={answerMap[answerMapKey]}
409
- canFullScreen={false}
410
- />
411
- )
412
- ) : null}
413
- </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}
414
525
  </div>
415
526
  </div>
416
527
  </div>
417
- );
418
- })}
419
- </div>
528
+ </div>
529
+ );
530
+ })}
420
531
  </div>
421
532
  );
422
533
  };
@@ -22,6 +22,10 @@ const OrderingActivityMaterialContent = ({
22
22
  null
23
23
  );
24
24
  const dragElementRef = useRef<HTMLDivElement>(null);
25
+ const [mousePosition, setMousePosition] = useState<{ x: number; y: number }>({
26
+ x: 0,
27
+ y: 0,
28
+ });
25
29
  const [touchPosition, setTouchPosition] = useState<{ x: number; y: number }>({
26
30
  x: 0,
27
31
  y: 0,
@@ -90,6 +94,24 @@ const OrderingActivityMaterialContent = ({
90
94
  e.preventDefault();
91
95
  setDraggedKey(materialKey);
92
96
  setSelectedKey(null);
97
+ setMousePosition({ x: e.clientX, y: e.clientY });
98
+ };
99
+
100
+ const handleMouseMove = (e: React.MouseEvent): void => {
101
+ if (!draggedKey) return;
102
+
103
+ setMousePosition({ x: e.clientX, y: e.clientY });
104
+
105
+ // Find the element under the mouse point
106
+ const elementUnder = document.elementFromPoint(e.clientX, e.clientY);
107
+ const dropZone = elementUnder?.closest("[data-ordering-drop-zone]");
108
+
109
+ if (dropZone) {
110
+ const dropKey = dropZone.getAttribute("data-ordering-drop-zone");
111
+ setDropTargetKey(dropKey);
112
+ } else {
113
+ setDropTargetKey(null);
114
+ }
93
115
  };
94
116
 
95
117
  const handleMouseUp = (): void => {
@@ -102,6 +124,7 @@ const OrderingActivityMaterialContent = ({
102
124
  }
103
125
  setDraggedKey(null);
104
126
  setDropTargetKey(null);
127
+ setMousePosition({ x: 0, y: 0 });
105
128
  };
106
129
 
107
130
  // Touch drag handlers
@@ -150,6 +173,7 @@ const OrderingActivityMaterialContent = ({
150
173
  setDraggedKey(null);
151
174
  setDropTargetKey(null);
152
175
  setDraggedElement(null);
176
+ setTouchPosition({ x: 0, y: 0 });
153
177
  };
154
178
 
155
179
  // Click/tap to select (for easier mobile interaction)
@@ -170,7 +194,57 @@ const OrderingActivityMaterialContent = ({
170
194
  const answerMap = retrieveAnswerMap();
171
195
 
172
196
  return (
173
- <div className="flex flex-row flex-wrap" onMouseUp={handleMouseUp}>
197
+ <div
198
+ className="flex flex-row flex-wrap"
199
+ onMouseMove={handleMouseMove}
200
+ onMouseUp={handleMouseUp}
201
+ >
202
+ {/* Floating drag preview for mouse */}
203
+ {draggedKey && mousePosition.x > 0 && (
204
+ <div
205
+ className="fixed pointer-events-none z-50 opacity-80"
206
+ style={{
207
+ left: `${mousePosition.x}px`,
208
+ top: `${mousePosition.y}px`,
209
+ transform: "translate(-50%, -50%)",
210
+ }}
211
+ >
212
+ {contentMap.type === "TEXT" ? (
213
+ <div className="border-catchup-blue border-2 px-3 py-2 rounded-catchup-xlarge bg-white shadow-lg">
214
+ <p className="text-xl whitespace-pre-wrap">
215
+ {constructInputWithSpecialExpressionList(
216
+ materialMap[answerMap[draggedKey]]
217
+ ).map((inputPart, index) => (
218
+ <span
219
+ key={index}
220
+ className={`${inputPart.isBold ? "font-bold" : ""} ${
221
+ inputPart.isUnderline ? "underline" : ""
222
+ }`}
223
+ >
224
+ {inputPart.isEquation ? (
225
+ <span className="text-xl">
226
+ <InlineMath math={inputPart.value} />
227
+ </span>
228
+ ) : (
229
+ inputPart.value
230
+ )}
231
+ </span>
232
+ ))}
233
+ </p>
234
+ </div>
235
+ ) : (
236
+ <div className="border-catchup-blue border-2 px-2 py-1 rounded-catchup-xlarge bg-white shadow-lg">
237
+ <ShowMaterialMediaByContentType
238
+ key={`${uniqueValue}-drag-mouse`}
239
+ contentType={contentMap.type}
240
+ src={materialMap[answerMap[draggedKey]]}
241
+ canFullScreen={false}
242
+ />
243
+ </div>
244
+ )}
245
+ </div>
246
+ )}
247
+
174
248
  {/* Floating drag preview for touch */}
175
249
  {draggedKey && touchPosition.x > 0 && (
176
250
  <div
@@ -207,7 +281,7 @@ const OrderingActivityMaterialContent = ({
207
281
  ) : (
208
282
  <div className="border-catchup-blue border-2 px-2 py-1 rounded-catchup-xlarge bg-white shadow-lg">
209
283
  <ShowMaterialMediaByContentType
210
- key={`${uniqueValue}-drag`}
284
+ key={`${uniqueValue}-drag-touch`}
211
285
  contentType={contentMap.type}
212
286
  src={materialMap[answerMap[draggedKey]]}
213
287
  canFullScreen={false}