@tinyweb_dev/oe-exam-sdk 0.2.1 → 0.2.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.
Files changed (39) hide show
  1. package/dist/components/CueCardEditor.d.ts +1 -7
  2. package/dist/components/CueCardEditor.js +1 -58
  3. package/dist/components/exams/take/components/QuestionRenderer.js +10 -2
  4. package/dist/components/exams/take/components/question-renderers/FindWordsInMatrixRenderer.d.ts +4 -0
  5. package/dist/components/exams/take/components/question-renderers/FindWordsInMatrixRenderer.js +389 -0
  6. package/dist/components/exams/take/components/question-renderers/index.d.ts +1 -0
  7. package/dist/components/exams/take/components/question-renderers/index.js +1 -0
  8. package/dist/components/exams/take/types.d.ts +50 -0
  9. package/dist/components/exams/take/utils/question-transformers.d.ts +41 -1
  10. package/dist/components/exams/take/utils/question-transformers.js +24 -0
  11. package/dist/components/questions/_shared/config/question-types.config.js +6 -0
  12. package/dist/components/questions/_shared/types/find-words-in-matrix.type.d.ts +18 -0
  13. package/dist/components/questions/_shared/types/find-words-in-matrix.type.js +1 -0
  14. package/dist/components/questions/creator/question-type-registry.js +2 -0
  15. package/dist/components/questions/question-bank/QuestionEditorRenderer.d.ts +2 -1
  16. package/dist/components/questions/question-bank/QuestionEditorRenderer.js +16 -0
  17. package/dist/components/questions/types/crossword-puzzle/CrosswordPuzzleClient.js +3 -3
  18. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.d.ts +9 -0
  19. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixClient.js +69 -0
  20. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.d.ts +8 -0
  21. package/dist/components/questions/types/find-words-in-matrix/FindWordsInMatrixCreator.js +302 -0
  22. package/dist/components/questions/types/find-words-in-matrix/index.d.ts +4 -0
  23. package/dist/components/questions/types/find-words-in-matrix/index.js +4 -0
  24. package/dist/components/questions/types/find-words-in-matrix/register.d.ts +2 -0
  25. package/dist/components/questions/types/find-words-in-matrix/register.js +29 -0
  26. package/dist/components/questions/types/find-words-in-matrix/transform.d.ts +2 -0
  27. package/dist/components/questions/types/find-words-in-matrix/transform.js +89 -0
  28. package/dist/components/questions/types/speaking-cue-card/SpeakingCueCardCreator.js +1 -1
  29. package/dist/components/shared/CueCardEditor.d.ts +7 -0
  30. package/dist/components/shared/CueCardEditor.js +44 -0
  31. package/dist/shared/constants/question-skills.js +2 -0
  32. package/dist/shared/lib/utils/question-reverse-transform.js +63 -0
  33. package/dist/shared/lib/utils/question-transform.js +2 -0
  34. package/dist/shared/types/common.types.d.ts +1 -1
  35. package/dist/shared/types/questions/find-words-in-matrix.d.ts +71 -0
  36. package/dist/shared/types/questions/find-words-in-matrix.js +8 -0
  37. package/dist/shared/types/questions/index.d.ts +1 -0
  38. package/dist/shared/types/questions/index.js +2 -0
  39. package/package.json +1 -1
@@ -1204,3 +1204,27 @@ export function transformCrosswordPuzzle(questions) {
1204
1204
  explanation: question?.explanation,
1205
1205
  };
1206
1206
  }
1207
+ export function transformFindWordsInMatrix(questions) {
1208
+ const question = questions[0];
1209
+ const content = (question?.content || {});
1210
+ const correctAnswer = (question?.correct_answer || question?.correctAnswer || {});
1211
+ const type = content?.type || (Array.isArray(content?.categories) && content.categories.length > 0 ? 'CATEGORIZE' : 'WORD_BANK');
1212
+ const rows = Number(content?.gridSize?.rows) || (Array.isArray(content?.grid) ? content.grid.length : 14);
1213
+ const cols = Number(content?.gridSize?.cols) || (Array.isArray(content?.grid?.[0]) ? content.grid[0].length : 14);
1214
+ const grid = Array.isArray(content?.grid) ? content.grid : [];
1215
+ const categories = Array.isArray(content?.categories) ? content.categories : undefined;
1216
+ const wordBank = Array.isArray(content?.wordBank) ? content.wordBank : [];
1217
+ const correctWords = Array.isArray(correctAnswer?.words) ? correctAnswer.words : [];
1218
+ return {
1219
+ type,
1220
+ title: content?.title || '',
1221
+ instruction: question?.instruction || content?.instruction || 'Find these words in the wordsearch box.',
1222
+ gridSize: { rows, cols },
1223
+ grid,
1224
+ ...(categories ? { categories } : {}),
1225
+ wordBank,
1226
+ ...(content?.exampleSelection ? { exampleSelection: content.exampleSelection } : {}),
1227
+ correctWords,
1228
+ explanation: question?.explanation,
1229
+ };
1230
+ }
@@ -322,6 +322,11 @@ export const QUESTION_TYPE_CONFIG = {
322
322
  requiresContent: true,
323
323
  hasDedicatedComponent: true,
324
324
  },
325
+ FIND_WORDS_IN_MATRIX: {
326
+ label: 'Tìm từ trong ma trận (Word Search)',
327
+ requiresContent: true,
328
+ hasDedicatedComponent: true,
329
+ },
325
330
  };
326
331
  export const QUESTION_TYPES_WITH_DEDICATED_COMPONENTS = [
327
332
  'FILL_IN_BLANK',
@@ -368,6 +373,7 @@ export const QUESTION_TYPES_WITH_DEDICATED_COMPONENTS = [
368
373
  'SPEAKING_CUE_CARD',
369
374
  'WORD_FILL_STRUCTURED_FORM',
370
375
  'CROSSWORD_PUZZLE',
376
+ 'FIND_WORDS_IN_MATRIX',
371
377
  ];
372
378
  export function getQuestionTypeLabel(type) {
373
379
  return QUESTION_TYPE_CONFIG[type]?.label || type;
@@ -0,0 +1,18 @@
1
+ import type { FindWordsInMatrixType, MatrixCellCoord, CategoryHeadingItem, WordBankItem, ExampleSelection, CorrectWordItem, FindWordsInMatrixContent, FindWordsInMatrixCorrectAnswer, StudentWordSelectionItem } from '../../../../shared/types/questions/find-words-in-matrix';
2
+ export interface WordBankFormItem extends WordBankItem {
3
+ path?: MatrixCellCoord[];
4
+ }
5
+ export interface FindWordsInMatrixFormState {
6
+ type: FindWordsInMatrixType;
7
+ title?: string;
8
+ instruction?: string;
9
+ rows: number;
10
+ cols: number;
11
+ grid: string[][];
12
+ categories: CategoryHeadingItem[];
13
+ words: WordBankFormItem[];
14
+ caseSensitive?: boolean;
15
+ points: number;
16
+ explanation?: string;
17
+ }
18
+ export type { FindWordsInMatrixType, MatrixCellCoord, CategoryHeadingItem, WordBankItem, ExampleSelection, CorrectWordItem, FindWordsInMatrixContent, FindWordsInMatrixCorrectAnswer, StudentWordSelectionItem, };
@@ -44,6 +44,7 @@ import { speakingCueCardRegistration } from '../types/speaking-cue-card/register
44
44
  import { wordFillStructuredFormRegistration } from '../types/word-fill-structured-form/register';
45
45
  import { wordOrderAndMatchGroupRegistration } from '../types/word-order-and-match-group/register';
46
46
  import { crosswordPuzzleRegistration } from '../types/crossword-puzzle/register';
47
+ import { findWordsInMatrixRegistration } from '../types/find-words-in-matrix/register';
47
48
  // ─── Registry map ───────────────────────────────────────────────────────────
48
49
  export const questionTypeRegistry = {
49
50
  CHOOSE_THE_CORRECT_ANSWER: chooseTheCorrectAnswerRegistration,
@@ -91,4 +92,5 @@ export const questionTypeRegistry = {
91
92
  SPEAKING_CUE_CARD: speakingCueCardRegistration,
92
93
  WORD_FILL_STRUCTURED_FORM: wordFillStructuredFormRegistration,
93
94
  CROSSWORD_PUZZLE: crosswordPuzzleRegistration,
95
+ FIND_WORDS_IN_MATRIX: findWordsInMatrixRegistration,
94
96
  };
@@ -15,7 +15,8 @@ export declare enum QuestionBankEditorType {
15
15
  WRITE_CORRECT_VERB_FORM = "WRITE_CORRECT_VERB_FORM",
16
16
  FILL_IN_BLANK = "FILL_IN_BLANK",
17
17
  CHOOSE_CORRECT_ADJECTIVE = "CHOOSE_CORRECT_ADJECTIVE",
18
- CROSSWORD_PUZZLE = "CROSSWORD_PUZZLE"
18
+ CROSSWORD_PUZZLE = "CROSSWORD_PUZZLE",
19
+ FIND_WORDS_IN_MATRIX = "FIND_WORDS_IN_MATRIX"
19
20
  }
20
21
  /**
21
22
  * Renders the appropriate Creator for a question-bank item.
@@ -4,6 +4,7 @@ import { useEffect, useRef, useState } from 'react';
4
4
  import { t } from '../../../shared/lib/i18n';
5
5
  import { renderChooseCorrectAdjectiveEditor, renderChooseTheCorrectAnswerEditor, renderFillInBlankEditor, renderFillMissingWordsInGridEditor, renderImageObjectMatchingEditor, renderLabelThePictureEditor, renderLookPictureChooseCorrectAnswerEditor, renderLookPictureFillBlankChooseAnswerEditor, renderLookPictureFillWordHintEditor, renderReadAndColorObjectsEditor, renderReadPassageAndAnswerQuestionsEditor, renderWriteAShortLetterEditor, renderWriteCorrectVerbFormEditor, UnsupportedEditorFallback, } from './editor-adapters';
6
6
  import { CrosswordPuzzleCreator } from '../types/crossword-puzzle/CrosswordPuzzleCreator';
7
+ import { FindWordsInMatrixCreator } from '../types/find-words-in-matrix/FindWordsInMatrixCreator';
7
8
  /** Marketing question types supported by the question-bank editor. */
8
9
  export var QuestionBankEditorType;
9
10
  (function (QuestionBankEditorType) {
@@ -21,6 +22,7 @@ export var QuestionBankEditorType;
21
22
  QuestionBankEditorType["FILL_IN_BLANK"] = "FILL_IN_BLANK";
22
23
  QuestionBankEditorType["CHOOSE_CORRECT_ADJECTIVE"] = "CHOOSE_CORRECT_ADJECTIVE";
23
24
  QuestionBankEditorType["CROSSWORD_PUZZLE"] = "CROSSWORD_PUZZLE";
25
+ QuestionBankEditorType["FIND_WORDS_IN_MATRIX"] = "FIND_WORDS_IN_MATRIX";
24
26
  })(QuestionBankEditorType || (QuestionBankEditorType = {}));
25
27
  /**
26
28
  * Renders the appropriate Creator for a question-bank item.
@@ -85,6 +87,20 @@ export function QuestionEditorRenderer({ questionType, content, correctAnswer, e
85
87
  points: data.points ?? points,
86
88
  });
87
89
  } }));
90
+ case QuestionBankEditorType.FIND_WORDS_IN_MATRIX:
91
+ return (_jsx(FindWordsInMatrixCreator, { initialData: {
92
+ content,
93
+ answer: correctAnswer,
94
+ explanation,
95
+ points,
96
+ }, onChange: (data) => {
97
+ onChange?.({
98
+ content: data.content,
99
+ answer: data.answer,
100
+ explanation: data.explanation || (explanation ?? undefined),
101
+ points: data.points ?? points,
102
+ });
103
+ } }));
88
104
  default:
89
105
  return (_jsx(UnsupportedEditorFallback, { questionType: questionType, content: content, correctAnswer: correctAnswer, explanation: explanation }));
90
106
  }
@@ -4,8 +4,8 @@ import { useMemo } from 'react';
4
4
  export function CrosswordPuzzleClient({ content, correctAnswer, isReviewMode = false, userAnswers = {}, }) {
5
5
  const rows = content?.gridSize?.rows || 12;
6
6
  const cols = content?.gridSize?.cols || 12;
7
- const acrossClues = content?.clues?.across || [];
8
- const downClues = content?.clues?.down || [];
7
+ const acrossClues = useMemo(() => content?.clues?.across || [], [content?.clues?.across]);
8
+ const downClues = useMemo(() => content?.clues?.down || [], [content?.clues?.down]);
9
9
  // Build grid cell mapping
10
10
  const { cellMap, clueNumberMap } = useMemo(() => {
11
11
  const cMap = new Map();
@@ -69,7 +69,7 @@ export function CrosswordPuzzleClient({ content, correctAnswer, isReviewMode = f
69
69
  else if (userAnswers?.[key]) {
70
70
  previewLetter = userAnswers[key];
71
71
  }
72
- let cellBg = isMarked ? 'bg-gray-400 border-gray-400 shadow-sm ring-1 ring-gray-400/50' : 'bg-gray-100 border-gray-300';
72
+ const cellBg = isMarked ? 'bg-gray-400 border-gray-400 shadow-sm ring-1 ring-gray-400/50' : 'bg-gray-100 border-gray-300';
73
73
  let textColor = isExample
74
74
  ? 'text-orange-600 font-bold italic'
75
75
  : isMarked
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { FindWordsInMatrixContent, FindWordsInMatrixCorrectAnswer } from '../../../../shared/types/questions/find-words-in-matrix';
3
+ interface FindWordsInMatrixClientProps {
4
+ content: FindWordsInMatrixContent;
5
+ correctAnswer?: FindWordsInMatrixCorrectAnswer;
6
+ showSolution?: boolean;
7
+ }
8
+ export declare function FindWordsInMatrixClient({ content, correctAnswer, showSolution, }: FindWordsInMatrixClientProps): React.JSX.Element;
9
+ export {};
@@ -0,0 +1,69 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { Card } from '../../../../components/ui/card';
4
+ import { Badge } from '../../../../components/ui/badge';
5
+ // 10 Distinct pastel highlighter colors for word selections
6
+ const WORD_COLORS = [
7
+ { bg: 'bg-amber-100 text-amber-900 border-amber-400', badge: 'bg-amber-500 text-white', stroke: '#f59e0b' },
8
+ { bg: 'bg-emerald-100 text-emerald-900 border-emerald-400', badge: 'bg-emerald-500 text-white', stroke: '#10b981' },
9
+ { bg: 'bg-sky-100 text-sky-900 border-sky-400', badge: 'bg-sky-500 text-white', stroke: '#0ea5e9' },
10
+ { bg: 'bg-purple-100 text-purple-900 border-purple-400', badge: 'bg-purple-500 text-white', stroke: '#a855f7' },
11
+ { bg: 'bg-rose-100 text-rose-900 border-rose-400', badge: 'bg-rose-500 text-white', stroke: '#f43f5e' },
12
+ { bg: 'bg-indigo-100 text-indigo-900 border-indigo-400', badge: 'bg-indigo-500 text-white', stroke: '#6366f1' },
13
+ { bg: 'bg-teal-100 text-teal-900 border-teal-400', badge: 'bg-teal-500 text-white', stroke: '#14b8a6' },
14
+ { bg: 'bg-yellow-100 text-yellow-900 border-yellow-400', badge: 'bg-yellow-500 text-white', stroke: '#eab308' },
15
+ { bg: 'bg-pink-100 text-pink-900 border-pink-400', badge: 'bg-pink-500 text-white', stroke: '#ec4899' },
16
+ { bg: 'bg-cyan-100 text-cyan-900 border-cyan-400', badge: 'bg-cyan-500 text-white', stroke: '#06b6d4' },
17
+ ];
18
+ export function FindWordsInMatrixClient({ content, correctAnswer, showSolution = false, }) {
19
+ const rows = content.gridSize?.rows || content.grid?.length || 14;
20
+ const cols = content.gridSize?.cols || content.grid?.[0]?.length || 14;
21
+ const grid = content.grid || [];
22
+ const wordBank = content.wordBank || [];
23
+ const isCategorize = content.type === 'CATEGORIZE' && Array.isArray(content.categories) && content.categories.length > 0;
24
+ const categories = content.categories || [];
25
+ // Build cell highlight map
26
+ const cellColorMap = {};
27
+ // Example selection
28
+ if (content.exampleSelection?.path) {
29
+ for (const c of content.exampleSelection.path) {
30
+ cellColorMap[`${c.row}-${c.col}`] = { colorIdx: 0, word: 'Example' };
31
+ }
32
+ }
33
+ // Solution paths if visible
34
+ if (showSolution && correctAnswer?.words) {
35
+ correctAnswer.words.forEach((item, idx) => {
36
+ const colorIdx = (idx + 1) % WORD_COLORS.length;
37
+ item.path?.forEach((c) => {
38
+ cellColorMap[`${c.row}-${c.col}`] = { colorIdx, word: item.word || item.wordId };
39
+ });
40
+ });
41
+ }
42
+ return (_jsxs("div", { className: "space-y-6", children: [content.title && (_jsx("h3", { className: "text-lg font-bold text-gray-800 dark:text-gray-100", children: content.title })), isCategorize ? (_jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4", children: categories.map((cat) => {
43
+ const wordsInCat = wordBank.filter((w) => w.categoryId === cat.id);
44
+ return (_jsxs(Card, { className: "p-3.5 bg-gray-50/70 dark:bg-gray-800/40 border", children: [_jsxs("div", { className: "font-bold text-sm text-gray-800 dark:text-gray-200 border-b pb-1.5 mb-2.5 flex items-center justify-between", children: [_jsx("span", { children: cat.name }), _jsx(Badge, { variant: "outline", className: "text-xs", children: wordsInCat.length })] }), _jsx("div", { className: "flex flex-wrap gap-1.5", children: wordsInCat.map((w, idx) => {
45
+ const isExample = Boolean(w.isExample);
46
+ return (_jsxs(Badge, { variant: "outline", className: `px-2.5 py-1 text-xs font-medium ${isExample
47
+ ? 'line-through bg-amber-50 text-amber-800 border-amber-300'
48
+ : 'bg-white dark:bg-gray-800'}`, children: [w.word, isExample && _jsx("span", { className: "ml-1 text-amber-600 font-bold", children: "(Ex)" })] }, w.id || idx));
49
+ }) })] }, cat.id));
50
+ }) })) : (_jsxs(Card, { className: "p-4 bg-gray-50 dark:bg-gray-800/50 border-dashed border-2", children: [_jsx("div", { className: "text-xs font-semibold uppercase tracking-wider text-gray-500 dark:text-gray-400 mb-3 flex items-center gap-2", children: _jsxs("span", { children: ["Word Bank (", wordBank.length, " words)"] }) }), _jsx("div", { className: "flex flex-wrap gap-2", children: wordBank.map((w, idx) => {
51
+ const isExample = Boolean(w.isExample);
52
+ return (_jsxs(Badge, { variant: "outline", className: `px-3 py-1.5 text-sm font-medium transition-all ${isExample
53
+ ? 'line-through bg-amber-50 text-amber-800 border-amber-300'
54
+ : 'bg-white dark:bg-gray-800 hover:shadow-sm'}`, children: [w.word, isExample && (_jsx("span", { className: "ml-1.5 text-xs text-amber-600 font-bold no-underline", children: "(Example)" }))] }, w.id || idx));
55
+ }) })] })), _jsx("div", { className: "overflow-x-auto pb-2 flex justify-center", children: _jsx("div", { className: "inline-grid gap-1 p-3 bg-white dark:bg-gray-900 rounded-xl shadow-sm border border-gray-200 dark:border-gray-800", style: {
56
+ gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))`,
57
+ }, children: Array.from({ length: rows }).map((_, rIdx) => {
58
+ const r = rIdx + 1;
59
+ return Array.from({ length: cols }).map((_, cIdx) => {
60
+ const c = cIdx + 1;
61
+ const char = grid[rIdx]?.[cIdx] || '';
62
+ const highlight = cellColorMap[`${r}-${c}`];
63
+ const colorConfig = highlight ? WORD_COLORS[highlight.colorIdx] : null;
64
+ return (_jsx("div", { className: `h-9 w-9 sm:h-11 sm:w-11 flex items-center justify-center font-bold text-sm sm:text-base select-none rounded-md transition-colors border ${colorConfig
65
+ ? `${colorConfig.bg} font-extrabold shadow-sm scale-95`
66
+ : 'border-gray-100 dark:border-gray-800 bg-gray-50/50 dark:bg-gray-800/30 text-gray-700 dark:text-gray-300'}`, title: highlight ? `${highlight.word} (${r},${c})` : `(${r},${c})`, children: char || '·' }, `${r}-${c}`));
67
+ });
68
+ }) }) })] }));
69
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ interface FindWordsInMatrixCreatorProps {
3
+ initialData?: any;
4
+ onChange?: (data: any) => void;
5
+ onUnsavedChangesChange?: (hasChanges: boolean) => void;
6
+ }
7
+ export declare function FindWordsInMatrixCreator({ initialData, onChange, onUnsavedChangesChange, }: FindWordsInMatrixCreatorProps): React.JSX.Element;
8
+ export {};
@@ -0,0 +1,302 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useState, useCallback } from 'react';
4
+ import { Button } from '../../../../components/ui/button';
5
+ import { Input } from '../../../../components/ui/input';
6
+ import { Label } from '../../../../components/ui/label';
7
+ import { Card } from '../../../../components/ui/card';
8
+ import { Badge } from '../../../../components/ui/badge';
9
+ import { Checkbox } from '../../../../components/ui/checkbox';
10
+ import { Textarea } from '../../../../components/ui/textarea';
11
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '../../../../components/ui/select';
12
+ import { Plus, Trash2, Sparkles, Clipboard, MousePointer, RotateCcw, Layers, ListFilter, FolderPlus, } from 'lucide-react';
13
+ const PALETTE = [
14
+ '#f59e0b', '#10b981', '#0ea5e9', '#8b5cf6', '#f43f5e',
15
+ '#6366f1', '#14b8a6', '#eab308', '#ec4899', '#06b6d4',
16
+ ];
17
+ export function FindWordsInMatrixCreator({ initialData, onChange, onUnsavedChangesChange, }) {
18
+ // Parse initial state
19
+ const parseInitial = () => {
20
+ const content = initialData?.content || {};
21
+ const answer = initialData?.answer || {};
22
+ const type = content.type || (Array.isArray(content.categories) && content.categories.length > 0 ? 'CATEGORIZE' : 'WORD_BANK');
23
+ const rows = Number(content.gridSize?.rows) || (Array.isArray(content.grid) ? content.grid.length : 14);
24
+ const cols = Number(content.gridSize?.cols) || (Array.isArray(content.grid?.[0]) ? content.grid[0].length : 14);
25
+ let grid = [];
26
+ if (Array.isArray(content.grid) && content.grid.length > 0) {
27
+ grid = content.grid.map((row) => Array.isArray(row) ? [...row].map((c) => String(c || '').toUpperCase()) : Array(cols).fill(''));
28
+ }
29
+ else {
30
+ grid = Array.from({ length: rows }, () => Array(cols).fill(''));
31
+ }
32
+ const categories = Array.isArray(content.categories) && content.categories.length > 0
33
+ ? content.categories
34
+ : [
35
+ { id: 'cat_1', name: 'Plants' },
36
+ { id: 'cat_2', name: 'Animals' },
37
+ ];
38
+ const words = [];
39
+ if (Array.isArray(content.wordBank)) {
40
+ const correctMap = new Map();
41
+ if (Array.isArray(answer.words)) {
42
+ answer.words.forEach((cw) => {
43
+ if (cw.wordId && Array.isArray(cw.path)) {
44
+ correctMap.set(cw.wordId, cw);
45
+ }
46
+ });
47
+ }
48
+ content.wordBank.forEach((wb) => {
49
+ const isEx = Boolean(wb.isExample);
50
+ const correctInfo = correctMap.get(wb.id);
51
+ let path = correctInfo?.path;
52
+ let categoryId = wb.categoryId || correctInfo?.categoryId;
53
+ if (isEx && content.exampleSelection?.wordId === wb.id) {
54
+ path = content.exampleSelection.path;
55
+ if (content.exampleSelection.categoryId) {
56
+ categoryId = content.exampleSelection.categoryId;
57
+ }
58
+ }
59
+ words.push({
60
+ id: wb.id || `w_${Math.random().toString(36).substring(2, 9)}`,
61
+ word: wb.word || '',
62
+ isExample: isEx,
63
+ ...(categoryId ? { categoryId } : {}),
64
+ path: path || [],
65
+ });
66
+ });
67
+ }
68
+ return {
69
+ type,
70
+ title: content.title || '',
71
+ instruction: initialData?.instruction || 'Find these words in the wordsearch box.',
72
+ rows,
73
+ cols,
74
+ grid,
75
+ categories,
76
+ words,
77
+ points: Number(initialData?.points) || 1,
78
+ explanation: initialData?.explanation || '',
79
+ };
80
+ };
81
+ const [state, setState] = useState(parseInitial);
82
+ const [selectedWordIdx, setSelectedWordIdx] = useState(null);
83
+ const [isPickingPath, setIsPickingPath] = useState(false);
84
+ const [pasteModalOpen, setPasteModalOpen] = useState(false);
85
+ const [pasteText, setPasteText] = useState('');
86
+ const updateState = useCallback((updater) => {
87
+ setState((prev) => {
88
+ const next = updater(prev);
89
+ if (onChange) {
90
+ onChange({
91
+ content: {
92
+ type: next.type,
93
+ title: next.title,
94
+ gridSize: { rows: next.rows, cols: next.cols },
95
+ grid: next.grid,
96
+ ...(next.type === 'CATEGORIZE' ? { categories: next.categories } : {}),
97
+ words: next.words,
98
+ },
99
+ answer: {
100
+ words: next.words
101
+ .filter((w) => !w.isExample && w.path && w.path.length > 0)
102
+ .map((w) => ({
103
+ wordId: w.id,
104
+ word: w.word,
105
+ ...(w.categoryId ? { categoryId: w.categoryId } : {}),
106
+ path: w.path,
107
+ })),
108
+ },
109
+ points: next.points,
110
+ explanation: next.explanation,
111
+ });
112
+ }
113
+ onUnsavedChangesChange?.(true);
114
+ return next;
115
+ });
116
+ }, [onChange, onUnsavedChangesChange]);
117
+ // Resize Grid helper
118
+ const handleResize = (newRows, newCols) => {
119
+ const r = Math.max(4, Math.min(25, newRows));
120
+ const c = Math.max(4, Math.min(25, newCols));
121
+ updateState((prev) => {
122
+ const newGrid = Array.from({ length: r }, (_, rIdx) => Array.from({ length: c }, (_, cIdx) => prev.grid[rIdx]?.[cIdx] || ''));
123
+ return { ...prev, rows: r, cols: c, grid: newGrid };
124
+ });
125
+ };
126
+ // Cell character change
127
+ const handleCellChange = (rIdx, cIdx, val) => {
128
+ const char = val.slice(-1).toUpperCase();
129
+ updateState((prev) => {
130
+ const newGrid = prev.grid.map((row) => [...row]);
131
+ newGrid[rIdx][cIdx] = char;
132
+ return { ...prev, grid: newGrid };
133
+ });
134
+ };
135
+ // Fill empty with random letters
136
+ const handleRandomFill = () => {
137
+ const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
138
+ updateState((prev) => {
139
+ const newGrid = prev.grid.map((row) => row.map((cell) => (cell.trim() ? cell : letters[Math.floor(Math.random() * letters.length)])));
140
+ return { ...prev, grid: newGrid };
141
+ });
142
+ };
143
+ // Paste matrix string
144
+ const handleApplyPaste = () => {
145
+ const lines = pasteText
146
+ .split('\n')
147
+ .map((l) => l.trim().replace(/\s+/g, '').toUpperCase())
148
+ .filter(Boolean);
149
+ if (lines.length === 0)
150
+ return;
151
+ const r = lines.length;
152
+ const c = Math.max(...lines.map((l) => l.length));
153
+ const newGrid = Array.from({ length: r }, (_, rIdx) => Array.from({ length: c }, (_, cIdx) => lines[rIdx]?.[cIdx] || ''));
154
+ updateState((prev) => ({
155
+ ...prev,
156
+ rows: r,
157
+ cols: c,
158
+ grid: newGrid,
159
+ }));
160
+ setPasteModalOpen(false);
161
+ setPasteText('');
162
+ };
163
+ // Category management
164
+ const handleAddCategory = () => {
165
+ const newCat = {
166
+ id: `cat_${Date.now()}_${Math.random().toString(36).substring(2, 5)}`,
167
+ name: `Nhóm #${state.categories.length + 1}`,
168
+ };
169
+ updateState((prev) => ({
170
+ ...prev,
171
+ categories: [...prev.categories, newCat],
172
+ }));
173
+ };
174
+ const handleRemoveCategory = (catId) => {
175
+ updateState((prev) => ({
176
+ ...prev,
177
+ categories: prev.categories.filter((c) => c.id !== catId),
178
+ words: prev.words.map((w) => (w.categoryId === catId ? { ...w, categoryId: undefined } : w)),
179
+ }));
180
+ };
181
+ // Add word to word bank
182
+ const handleAddWord = () => {
183
+ const newWord = {
184
+ id: `w_${Date.now()}_${Math.random().toString(36).substring(2, 6)}`,
185
+ word: '',
186
+ isExample: false,
187
+ ...(state.type === 'CATEGORIZE' && state.categories[0] ? { categoryId: state.categories[0].id } : {}),
188
+ path: [],
189
+ };
190
+ updateState((prev) => ({ ...prev, words: [...prev.words, newWord] }));
191
+ setSelectedWordIdx(state.words.length);
192
+ };
193
+ // Remove word
194
+ const handleRemoveWord = (idx) => {
195
+ updateState((prev) => ({
196
+ ...prev,
197
+ words: prev.words.filter((_, i) => i !== idx),
198
+ }));
199
+ if (selectedWordIdx === idx) {
200
+ setSelectedWordIdx(null);
201
+ setIsPickingPath(false);
202
+ }
203
+ };
204
+ // Handle cell click during Path Picking Mode
205
+ const handleCellClickForPath = (r, c) => {
206
+ if (selectedWordIdx === null || !isPickingPath)
207
+ return;
208
+ const activeWord = state.words[selectedWordIdx];
209
+ if (!activeWord)
210
+ return;
211
+ const currentPath = activeWord.path || [];
212
+ const cellIdx = currentPath.findIndex((item) => item.row === r && item.col === c);
213
+ let nextPath;
214
+ if (cellIdx >= 0) {
215
+ nextPath = currentPath.filter((_, i) => i !== cellIdx);
216
+ }
217
+ else {
218
+ nextPath = [...currentPath, { row: r, col: c }];
219
+ }
220
+ updateState((prev) => {
221
+ const nextWords = [...prev.words];
222
+ nextWords[selectedWordIdx] = { ...activeWord, path: nextPath };
223
+ return { ...prev, words: nextWords };
224
+ });
225
+ };
226
+ // Path highlight mapping
227
+ const cellColorMap = {};
228
+ state.words.forEach((w, idx) => {
229
+ const color = PALETTE[idx % PALETTE.length];
230
+ w.path?.forEach((coord) => {
231
+ cellColorMap[`${coord.row}-${coord.col}`] = { color, word: w.word };
232
+ });
233
+ });
234
+ return (_jsxs("div", { className: "space-y-6", children: [_jsxs(Card, { className: "p-4 space-y-4", children: [_jsxs("div", { children: [_jsx(Label, { className: "font-semibold text-sm mb-2 block", children: "D\u1EA1ng b\u00E0i t\u1EADp (Type / Mode)" }), _jsxs("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-3", children: [_jsxs("button", { type: "button", onClick: () => updateState((p) => ({ ...p, type: 'WORD_BANK' })), className: `flex items-center gap-3 p-3 rounded-xl border text-left transition-all ${state.type === 'WORD_BANK'
235
+ ? 'border-blue-500 bg-blue-50/60 dark:bg-blue-950/30 text-blue-900 dark:text-blue-200 ring-2 ring-blue-400'
236
+ : 'border-gray-200 hover:bg-gray-50 dark:border-gray-700 text-gray-700 dark:text-gray-300'}`, children: [_jsx("div", { className: "p-2 rounded-lg bg-blue-100 dark:bg-blue-900 text-blue-600", children: _jsx(ListFilter, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("div", { className: "font-bold text-sm", children: "1. Danh s\u00E1ch t\u1EEB (Word Bank)" }), _jsx("div", { className: "text-xs text-gray-500", children: "T\u00ECm c\u00E1c t\u1EEB c\u00F3 s\u1EB5n trong b\u1EA3ng ch\u1EEF c\u00E1i (VD: Gadgets)." })] })] }), _jsxs("button", { type: "button", onClick: () => updateState((p) => ({ ...p, type: 'CATEGORIZE' })), className: `flex items-center gap-3 p-3 rounded-xl border text-left transition-all ${state.type === 'CATEGORIZE'
237
+ ? 'border-emerald-500 bg-emerald-50/60 dark:bg-emerald-950/30 text-emerald-900 dark:text-emerald-200 ring-2 ring-emerald-400'
238
+ : 'border-gray-200 hover:bg-gray-50 dark:border-gray-700 text-gray-700 dark:text-gray-300'}`, children: [_jsx("div", { className: "p-2 rounded-lg bg-emerald-100 dark:bg-emerald-900 text-emerald-600", children: _jsx(Layers, { className: "h-5 w-5" }) }), _jsxs("div", { children: [_jsx("div", { className: "font-bold text-sm", children: "2. T\u00ECm t\u1EEB & Ph\u00E2n nh\u00F3m (Categorize)" }), _jsx("div", { className: "text-xs text-gray-500", children: "T\u00ECm t\u1EEB \u1EA9n v\u00E0 x\u1EBFp v\u00E0o c\u00E1c nh\u00F3m ti\u00EAu \u0111\u1EC1 (VD: Plants / Animals)." })] })] })] })] }), _jsxs("div", { className: "grid grid-cols-1 md:grid-cols-3 gap-4 pt-2 border-t", children: [_jsxs("div", { className: "md:col-span-2", children: [_jsx(Label, { className: "font-semibold", children: "Ti\u00EAu \u0111\u1EC1 b\u00E0i t\u1EADp" }), _jsx(Input, { value: state.title || '', onChange: (e) => updateState((p) => ({ ...p, title: e.target.value })), placeholder: state.type === 'CATEGORIZE' ? 'VD: Plants and Animals Word Search' : 'VD: Technology Gadgets Word Search', className: "mt-1" })] }), _jsxs("div", { children: [_jsx(Label, { className: "font-semibold", children: "K\u00EDch th\u01B0\u1EDBc ma tr\u1EADn (H\u00E0ng x C\u1ED9t)" }), _jsxs("div", { className: "flex items-center gap-2 mt-1", children: [_jsx(Input, { type: "number", min: 4, max: 25, value: state.rows, onChange: (e) => handleResize(Number(e.target.value), state.cols), className: "w-20 text-center font-bold" }), _jsx("span", { className: "font-bold text-gray-400", children: "\u00D7" }), _jsx(Input, { type: "number", min: 4, max: 25, value: state.cols, onChange: (e) => handleResize(state.rows, Number(e.target.value)), className: "w-20 text-center font-bold" })] })] })] })] }), state.type === 'CATEGORIZE' && (_jsxs(Card, { className: "p-4 space-y-3 bg-emerald-50/30 dark:bg-emerald-950/10 border-emerald-200 dark:border-emerald-800", children: [_jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsxs("h4", { className: "font-bold text-sm text-emerald-900 dark:text-emerald-300 flex items-center gap-1.5", children: [_jsx(Layers, { className: "h-4 w-4 text-emerald-600" }), "C\u00E1c nh\u00F3m danh m\u1EE5c (Categories / Headings)"] }), _jsx("p", { className: "text-xs text-gray-500", children: "T\u1EA1o c\u00E1c c\u1ED9t ti\u00EAu \u0111\u1EC1 \u0111\u1EC3 h\u1ECDc sinh ph\u00E2n lo\u1EA1i t\u1EEB v\u00E0o (t\u1ED1i thi\u1EC3u 2 nh\u00F3m)." })] }), _jsxs(Button, { type: "button", size: "sm", variant: "outline", onClick: handleAddCategory, className: "gap-1 text-xs", children: [_jsx(FolderPlus, { className: "h-3.5 w-3.5" }), " Th\u00EAm nh\u00F3m"] })] }), _jsx("div", { className: "grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-3", children: state.categories.map((cat, idx) => (_jsxs("div", { className: "flex items-center gap-2 p-2.5 bg-white dark:bg-gray-900 rounded-lg border shadow-xs", children: [_jsx(Badge, { variant: "secondary", className: "font-mono text-xs", children: idx + 1 }), _jsx(Input, { value: cat.name, onChange: (e) => {
239
+ const val = e.target.value;
240
+ updateState((p) => ({
241
+ ...p,
242
+ categories: p.categories.map((c, i) => (i === idx ? { ...c, name: val } : c)),
243
+ }));
244
+ }, placeholder: "T\u00EAn nh\u00F3m (VD: Plants)", className: "h-8 text-sm font-semibold" }), state.categories.length > 2 && (_jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => handleRemoveCategory(cat.id), className: "h-8 w-8 p-0 text-red-500 hover:text-red-700", children: _jsx(Trash2, { className: "h-3.5 w-3.5" }) }))] }, cat.id || idx))) })] })), _jsxs(Card, { className: "p-4 space-y-4", children: [_jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2 border-b pb-3", children: [_jsxs("div", { children: [_jsxs("h4", { className: "font-bold text-base text-gray-800 dark:text-gray-200", children: ["Ma tr\u1EADn ch\u1EEF c\u00E1i (", state.rows, " h\u00E0ng \u00D7 ", state.cols, " c\u1ED9t)"] }), _jsx("p", { className: "text-xs text-gray-500", children: isPickingPath
245
+ ? `👉 Đang gán vị trí cho từ: "${state.words[selectedWordIdx]?.word}". Hãy click các ô trên lưới theo thứ tự.`
246
+ : 'Nhập trực tiếp ký tự vào ô, hoặc dùng các nút công cụ bên phải.' })] }), _jsxs("div", { className: "flex items-center gap-2", children: [_jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => setPasteModalOpen(!pasteModalOpen), className: "gap-1 text-xs", children: [_jsx(Clipboard, { className: "h-3.5 w-3.5" }), "Paste ma tr\u1EADn"] }), _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: handleRandomFill, className: "gap-1 text-xs text-emerald-600 hover:text-emerald-700", children: [_jsx(Sparkles, { className: "h-3.5 w-3.5" }), "L\u1EA5p ng\u1EABu nhi\u00EAn (A-Z)"] })] })] }), pasteModalOpen && (_jsxs("div", { className: "p-3 bg-blue-50 dark:bg-blue-950/30 rounded-lg border border-blue-200 space-y-2", children: [_jsx(Label, { className: "text-xs font-bold text-blue-900 dark:text-blue-200", children: "D\u00E1n v\u0103n b\u1EA3n ma tr\u1EADn (m\u1ED7i d\u00F2ng 1 h\u00E0ng):" }), _jsx(Textarea, { value: pasteText, onChange: (e) => setPasteText(e.target.value), placeholder: `SEMONOFFP\nCROCODILE\n...`, rows: 4, className: "font-mono text-xs" }), _jsxs("div", { className: "flex justify-end gap-2", children: [_jsx(Button, { size: "sm", variant: "ghost", onClick: () => setPasteModalOpen(false), children: "H\u1EE7y" }), _jsx(Button, { size: "sm", onClick: handleApplyPaste, children: "\u00C1p d\u1EE5ng v\u00E0o ma tr\u1EADn" })] })] })), _jsx("div", { className: "overflow-x-auto pb-2 flex justify-center", children: _jsx("div", { className: "inline-grid gap-1 p-2 bg-gray-100 dark:bg-gray-900 rounded-lg border border-gray-300", style: {
247
+ gridTemplateColumns: `repeat(${state.cols}, minmax(0, 1fr))`,
248
+ }, children: Array.from({ length: state.rows }).map((_, rIdx) => {
249
+ const r = rIdx + 1;
250
+ return Array.from({ length: state.cols }).map((_, cIdx) => {
251
+ const c = cIdx + 1;
252
+ const char = state.grid[rIdx]?.[cIdx] || '';
253
+ const highlight = cellColorMap[`${r}-${c}`];
254
+ return (_jsx("div", { className: "relative", children: _jsx("input", { type: "text", maxLength: 1, value: char, onChange: (e) => handleCellChange(rIdx, cIdx, e.target.value), onClick: () => isPickingPath && handleCellClickForPath(r, c), className: `h-8 w-8 sm:h-10 sm:w-10 text-center font-bold text-sm sm:text-base uppercase rounded border transition-all ${isPickingPath ? 'cursor-pointer hover:ring-2 hover:ring-blue-400' : ''} ${highlight
255
+ ? 'border-2 text-white font-extrabold shadow-sm'
256
+ : 'bg-white dark:bg-gray-800 text-gray-800 dark:text-gray-100 border-gray-300'}`, style: highlight ? { backgroundColor: highlight.color, borderColor: highlight.color } : {}, title: highlight ? `${highlight.word} (${r},${c})` : `(${r},${c})` }) }, `${r}-${c}`));
257
+ });
258
+ }) }) })] }), _jsxs(Card, { className: "p-4 space-y-4", children: [_jsxs("div", { className: "flex items-center justify-between border-b pb-2", children: [_jsxs("div", { children: [_jsx("h4", { className: "font-bold text-base", children: state.type === 'CATEGORIZE' ? 'Danh sách từ & Nhóm phân loại' : 'Danh sách từ cần tìm (Word Bank)' }), _jsxs("p", { className: "text-xs text-gray-500", children: ["Nh\u1EADp t\u1EEB v\u1EF1ng, ", state.type === 'CATEGORIZE' ? 'chọn nhóm thuộc tính, ' : '', "nh\u1EA5n \"G\u00E1n v\u1ECB tr\u00ED\" v\u00E0 click c\u00E1c \u00F4 t\u01B0\u01A1ng \u1EE9ng tr\u00EAn ma tr\u1EADn."] })] }), _jsxs(Button, { type: "button", size: "sm", onClick: handleAddWord, className: "gap-1", children: [_jsx(Plus, { className: "h-4 w-4" }), " Th\u00EAm t\u1EEB"] })] }), _jsx("div", { className: "space-y-3", children: state.words.map((w, idx) => {
259
+ const isSelected = selectedWordIdx === idx;
260
+ const color = PALETTE[idx % PALETTE.length];
261
+ const pathLength = w.path?.length || 0;
262
+ const formedText = (w.path || [])
263
+ .map((coord) => state.grid[coord.row - 1]?.[coord.col - 1] || '')
264
+ .join('');
265
+ return (_jsxs("div", { className: `p-3 rounded-lg border transition-all ${isSelected
266
+ ? 'border-blue-500 bg-blue-50/50 dark:bg-blue-950/20 ring-1 ring-blue-400'
267
+ : 'border-gray-200 bg-white dark:bg-gray-900'}`, children: [_jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [_jsx("div", { className: "w-4 h-4 rounded-full flex-shrink-0", style: { backgroundColor: color }, title: `Màu đại diện: ${color}` }), _jsx("div", { className: "flex-1 min-w-[180px]", children: _jsx(Input, { value: w.word, onChange: (e) => {
268
+ const val = e.target.value;
269
+ updateState((prev) => {
270
+ const nextWords = [...prev.words];
271
+ nextWords[idx] = { ...w, word: val };
272
+ return { ...prev, words: nextWords };
273
+ });
274
+ }, placeholder: `Từ #${idx + 1} (VD: ${state.type === 'CATEGORIZE' ? 'monkey' : 'digital camera'})`, className: "font-medium" }) }), state.type === 'CATEGORIZE' && (_jsx("div", { className: "w-40", children: _jsxs(Select, { value: w.categoryId || state.categories[0]?.id || '', onValueChange: (catVal) => {
275
+ updateState((prev) => {
276
+ const nextWords = [...prev.words];
277
+ nextWords[idx] = { ...w, categoryId: catVal };
278
+ return { ...prev, words: nextWords };
279
+ });
280
+ }, children: [_jsx(SelectTrigger, { className: "h-9 text-xs font-semibold", children: _jsx(SelectValue, { placeholder: "Ch\u1ECDn nh\u00F3m" }) }), _jsx(SelectContent, { children: state.categories.map((c) => (_jsx(SelectItem, { value: c.id, className: "text-xs", children: c.name }, c.id))) })] }) })), _jsx("div", { className: "flex items-center gap-2", children: _jsxs("label", { className: "flex items-center gap-1.5 text-xs text-amber-700 dark:text-amber-300 font-medium cursor-pointer", children: [_jsx(Checkbox, { checked: Boolean(w.isExample), onCheckedChange: (checked) => {
281
+ updateState((prev) => {
282
+ const nextWords = [...prev.words];
283
+ nextWords[idx] = { ...w, isExample: Boolean(checked) };
284
+ return { ...prev, words: nextWords };
285
+ });
286
+ } }), "C\u00E2u v\u00ED d\u1EE5"] }) }), _jsxs(Button, { type: "button", size: "sm", variant: isSelected && isPickingPath ? 'default' : 'outline', onClick: () => {
287
+ if (isSelected && isPickingPath) {
288
+ setIsPickingPath(false);
289
+ }
290
+ else {
291
+ setSelectedWordIdx(idx);
292
+ setIsPickingPath(true);
293
+ }
294
+ }, className: "gap-1 text-xs", children: [_jsx(MousePointer, { className: "h-3.5 w-3.5" }), isSelected && isPickingPath ? 'Đang chọn ô...' : `Gán vị trí (${pathLength} ô)`] }), pathLength > 0 && (_jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => {
295
+ updateState((prev) => {
296
+ const nextWords = [...prev.words];
297
+ nextWords[idx] = { ...w, path: [] };
298
+ return { ...prev, words: nextWords };
299
+ });
300
+ }, className: "text-xs text-gray-500 hover:text-red-500", title: "X\u00F3a \u0111\u01B0\u1EDDng \u0111i \u0111\u00E3 ch\u1ECDn", children: _jsx(RotateCcw, { className: "h-3.5 w-3.5" }) })), _jsx(Button, { type: "button", size: "sm", variant: "ghost", onClick: () => handleRemoveWord(idx), className: "text-red-500 hover:text-red-700 hover:bg-red-50", children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), pathLength > 0 && (_jsxs("div", { className: "mt-2 text-xs flex items-center gap-2 text-gray-600 dark:text-gray-400 pl-7", children: [_jsx("span", { className: "font-semibold", children: "Chu\u1ED7i qu\u00E9t \u0111\u01B0\u1EE3c:" }), _jsx(Badge, { variant: "secondary", className: "font-mono uppercase tracking-widest", children: formedText || '(chưa có chữ)' }), _jsxs("span", { className: "text-gray-400", children: ["(T\u1ECDa \u0111\u1ED9: ", w.path?.map((c) => `[${c.row},${c.col}]`).join(' → '), ")"] })] }))] }, w.id || idx));
301
+ }) })] })] }));
302
+ }
@@ -0,0 +1,4 @@
1
+ export * from './FindWordsInMatrixCreator';
2
+ export * from './FindWordsInMatrixClient';
3
+ export * from './register';
4
+ export * from './transform';
@@ -0,0 +1,4 @@
1
+ export * from './FindWordsInMatrixCreator';
2
+ export * from './FindWordsInMatrixClient';
3
+ export * from './register';
4
+ export * from './transform';
@@ -0,0 +1,2 @@
1
+ import type { QuestionTypeRegistration } from '../../creator/question-type-registry';
2
+ export declare const findWordsInMatrixRegistration: QuestionTypeRegistration;
@@ -0,0 +1,29 @@
1
+ import { FindWordsInMatrixCreator } from './FindWordsInMatrixCreator';
2
+ export const findWordsInMatrixRegistration = {
3
+ component: FindWordsInMatrixCreator,
4
+ useRawInitialData: true,
5
+ getExtraProps: (ctx) => ({
6
+ onUnsavedChangesChange: ctx.onUnsavedChangesChange,
7
+ }),
8
+ wrapOnSave: (data, ctx) => ({
9
+ type: ctx.questionType,
10
+ content: ctx.state.content,
11
+ points: data.points ?? ctx.state.points ?? 1,
12
+ level: ctx.state.level,
13
+ difficulty: ctx.state.difficulty,
14
+ skill: ctx.state.skill,
15
+ ...(data.content !== undefined ? { content: data.content } : {}),
16
+ answer: data.answer ?? data,
17
+ explanation: data.explanation || '',
18
+ }),
19
+ wrapOnChange: (data, ctx) => ({
20
+ type: ctx.questionType,
21
+ points: data.points ?? ctx.state.points ?? 1,
22
+ level: ctx.state.level,
23
+ difficulty: ctx.state.difficulty,
24
+ skill: ctx.state.skill,
25
+ content: data.content,
26
+ answer: data.answer,
27
+ explanation: data.explanation || '',
28
+ }),
29
+ };
@@ -0,0 +1,2 @@
1
+ import type { TransformHandler } from '../../../../shared/lib/utils/question-transform-types';
2
+ export declare const transformFindWordsInMatrix: TransformHandler;