@pie-element/match-list 8.0.4-next.20261011174919 → 8.0.4

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../../../shared/feedback/dist/index.js","../../../../../shared/controller-utils/dist/index.js","../../../src/controller/defaults.ts","../../../src/controller/index.ts"],"sourcesContent":["//#region src/defaults.ts\nvar e = {\n\tcorrect: {\n\t\ttype: \"default\",\n\t\tdefault: \"Correct\",\n\t\tcustom: \"Correct\"\n\t},\n\tincorrect: {\n\t\ttype: \"default\",\n\t\tdefault: \"Incorrect\",\n\t\tcustom: \"Incorrect\"\n\t},\n\tpartial: {\n\t\ttype: \"default\",\n\t\tdefault: \"Nearly\",\n\t\tcustom: \"Nearly\"\n\t},\n\tunanswered: {\n\t\ttype: \"default\",\n\t\tdefault: \"You have not entered a response\",\n\t\tcustom: \"You have not entered a response\"\n\t}\n};\n//#endregion\n//#region src/utils.ts\nfunction t(e) {\n\treturn e === \"partially-correct\" ? \"partial\" : e;\n}\n//#endregion\n//#region src/index.ts\nfunction n(n, r) {\n\tlet i = {\n\t\t...e,\n\t\t...r\n\t}, a = t(n), o = e[a] || {}, s = i[a] || o;\n\treturn {\n\t\tconfig: s,\n\t\tfallback: o[s.type || \"default\"]\n\t};\n}\nfunction r(e, t = {}) {\n\treturn new Promise((r) => {\n\t\tlet { config: a, fallback: o } = n(e, t);\n\t\ti(a, o).then(r);\n\t});\n}\nfunction i(e, t) {\n\treturn new Promise((n) => {\n\t\tif (!e || e.type === \"none\") {\n\t\t\tn(void 0);\n\t\t\treturn;\n\t\t}\n\t\tn(e[e.type] || t);\n\t});\n}\nfunction a(e, t = {}) {\n\tlet { config: r, fallback: i } = n(e, t);\n\treturn o(r, i);\n}\nfunction o(e, t) {\n\tif (!(!e || e.type === \"none\")) return e[e.type] || t;\n}\n//#endregion\nexport { e as defaultFeedback, o as getActualFeedback, a as getActualFeedbackForCorrectness, i as getFeedback, r as getFeedbackForCorrectness, t as normalizeCorrectness };\n","//#region \\0rolldown/runtime.js\nvar e = Object.defineProperty, t = (t, n) => {\n\tlet r = {};\n\tfor (var i in t) e(r, i, {\n\t\tget: t[i],\n\t\tenumerable: !0\n\t});\n\treturn n || e(r, Symbol.toStringTag, { value: \"Module\" }), r;\n};\n//#endregion\n//#region src/persistence.ts\nfunction n(e) {\n\treturn Array.isArray(e) ? e.filter((e) => e != null) : [];\n}\nfunction r(e) {\n\tlet t = [...e];\n\tfor (let e = t.length - 1; e > 0; e--) {\n\t\tlet n = Math.floor(Math.random() * (e + 1));\n\t\t[t[e], t[n]] = [t[n], t[e]];\n\t}\n\treturn t;\n}\nfunction i(e) {\n\treturn e == null ? !0 : Array.isArray(e) ? e.length === 0 : typeof e == \"object\" && Object.keys(e).length === 0;\n}\nasync function a(e, t, a, o = \"value\") {\n\tlet s = n(t?.data?.shuffledValues ?? t?.shuffledValues ?? []);\n\tif (!t) {\n\t\tconsole.warn(\"Unable to save shuffled choices because there's no session.\");\n\t\treturn;\n\t}\n\tif (!i(s)) return n(s.map((t) => e.find((e) => e[o] === t)));\n\tlet c = r(e);\n\tif (a && typeof a == \"function\") try {\n\t\tlet e = n(c.map((e) => e[o]));\n\t\ti(e) ? console.error(`shuffledValues is an empty array - refusing to call updateSession. shuffledChoices: ${JSON.stringify(c)}, key: ${o}`) : await a(t.id, t.element, { shuffledValues: e });\n\t} catch (e) {\n\t\tconsole.warn(\"Unable to save shuffled order for choices\"), console.error(e);\n\t}\n\telse console.warn(\"Unable to save shuffled choices, shuffle will happen every time.\");\n\treturn c;\n}\nfunction o(e, t, n) {\n\treturn !!(e.lockChoiceOrder || n?.[\"@pie-element\"]?.lockChoiceOrder);\n}\n//#endregion\n//#region src/partial-scoring.ts\nvar s = /* @__PURE__ */ t({ enabled: () => c });\nfunction c(e, t, n) {\n\treturn e?.partialScoring === !1 || t?.partialScoring === !1 ? !1 : typeof n != \"boolean\" || n;\n}\n//#endregion\nexport { a as getShuffledChoices, o as lockChoices, s as partialScoring };\n","// @ts-nocheck\n\nexport default {\n config: {\n answers: [],\n prompt: '',\n prompts: [],\n shuffled: false,\n },\n};\n","// @ts-nocheck\n\nimport { isEmpty } from '@pie-element/shared-lodash';\nimport { getFeedbackForCorrectness } from '@pie-element/shared-feedback';\nimport { lockChoices, getShuffledChoices, partialScoring } from '@pie-element/shared-controller-utils';\nimport debug from 'debug';\n\nconst log = debug('@pie-element:match-list:controller');\n\nimport defaults from './defaults.js';\n\nconst getResponseCorrectness = (model, answers, env) => {\n const isPartialScoring = partialScoring.enabled(model, env);\n const prompts = model.prompts;\n\n if (!answers || Object.keys(answers).length === 0) {\n return 'unanswered';\n }\n\n const totalCorrectAnswers = getTotalCorrect(model);\n const correctAnswers = getCorrectSelected(prompts, answers);\n\n if (totalCorrectAnswers === correctAnswers) {\n return 'correct';\n } else if (correctAnswers === 0) {\n return 'incorrect';\n } else if (isPartialScoring) {\n return 'partial';\n }\n\n return 'incorrect';\n};\n\nconst getCorrectness = (question, env, answers) => {\n if (env.mode === 'evaluate') {\n return getResponseCorrectness(question, answers, env);\n }\n};\n\nconst getCorrectSelected = (prompts = [], answers) => {\n let correctAnswers = 0;\n\n prompts.forEach((p) => {\n if (p.relatedAnswer === answers[p.id]) {\n correctAnswers += 1;\n }\n });\n\n return correctAnswers;\n};\n\nconst getTotalCorrect = (question) => {\n return question && question.prompts ? question.prompts.length : 0;\n};\n\nconst getPartialScore = (question, answers) => {\n const count = getCorrectSelected(question && question.prompts, answers);\n const totalCorrect = getTotalCorrect(question);\n\n return parseFloat((count / totalCorrect).toFixed(2));\n};\n\nconst getOutComeScore = (question, env, answers) => {\n const correctness = getCorrectness(question, env, answers);\n\n return correctness === 'correct' ? 1 : correctness === 'partial' && true ? getPartialScore(question, answers) : 0;\n};\n\n/**\n * Generates detailed trace log for match-list scoring evaluation\n * @param {Object} question\n * @param {Object} session\n * @param {Object} env\n * @returns {Array<string>} traceLog\n */\nexport const getLogTrace = (question, session, env) => {\n const traceLog = [];\n\n const prompts = question?.prompts || [];\n const answers = session?.value || {};\n\n if (!answers || Object.keys(answers).length === 0) {\n traceLog.push('Student did not provide any answer.');\n return traceLog;\n }\n\n const totalPrompts = prompts.length;\n traceLog.push(`Match-list item contains ${totalPrompts} question(s).`);\n\n let correctCount = 0;\n let answeredCount = 0;\n\n prompts.forEach((prompt) => {\n const expectedAnswer = prompt.relatedAnswer;\n const studentAnswer = answers[prompt.id];\n\n if (studentAnswer !== undefined) {\n answeredCount += 1;\n\n if (studentAnswer === expectedAnswer) {\n correctCount += 1;\n traceLog.push(`Question ${prompt.id}: correctly matched.`);\n } else {\n traceLog.push(\n `Question ${prompt.id}: incorrectly matched (selected '${studentAnswer}', expected '${expectedAnswer}').`,\n );\n }\n } else {\n traceLog.push(`Question ${prompt.id}: no answer provided.`);\n }\n });\n\n traceLog.push(\n `Student correctly matched ${correctCount} out of ${totalPrompts} question(s).`,\n );\n\n const partialScoringEnabled = partialScoring.enabled(question, env);\n\n if (partialScoringEnabled) {\n traceLog.push('Score calculated using partial scoring.');\n traceLog.push(\n 'Score is the number of correct matches divided by the total number of questions.',\n );\n } else {\n traceLog.push('Score calculated using all-or-nothing scoring.');\n traceLog.push(\n 'Student must match all questions correctly to receive full credit.',\n );\n }\n\n const rawScore = getOutComeScore(question, env, answers);\n const finalScore = partialScoringEnabled ? rawScore : rawScore === 1 ? 1 : 0;\n\n traceLog.push(`Final score: ${finalScore}.`);\n\n return traceLog;\n};\n\nexport const outcome = (question, session, env) => {\n return new Promise((resolve) => {\n if (env.mode !== 'evaluate') {\n resolve({ score: undefined, completed: undefined, logTrace: [] });\n } else {\n if (!session || isEmpty(session)) {\n resolve({ score: 0, empty: true, logTrace: ['Student did not provide any answer.'] });\n } else {\n const out = {\n score: getOutComeScore(question, env, session.value),\n empty: false,\n logTrace: getLogTrace(question, session, env),\n };\n\n resolve(out);\n }\n }\n });\n};\n\nexport function createDefaultModel(model = {}) {\n return new Promise((resolve) => {\n resolve({\n ...defaults,\n ...model,\n });\n });\n}\n\nexport const normalize = (model) => ({ ...defaults, ...model });\n\n/**\n *\n * @param {*} question\n * @param {*} session\n * @param {*} env\n * @param {*} updateSession - optional - a function that will set the properties passed into it on the session.\n */\nexport async function model(question, session, env, updateSession) {\n question = normalize(question);\n const correctness = getCorrectness(question, env, session && session.value);\n const correctResponse = {};\n const score = `${getOutComeScore(question, env, session && session.value) * 100}%`;\n const correctInfo = {\n score,\n correctness,\n };\n\n const shuffledValues = {};\n let prompts = question.prompts;\n let answers = question.answers;\n\n const us = (part) => (id, element, update) => {\n return new Promise((resolve) => {\n shuffledValues[part] = update.shuffledValues;\n resolve();\n });\n };\n\n const lockChoiceOrder = lockChoices(question, session, env);\n\n if (!lockChoiceOrder) {\n prompts = await getShuffledChoices(\n prompts,\n { shuffledValues: ((session && session.shuffledValues) || {}).prompts },\n us('prompts'),\n 'id',\n );\n answers = await getShuffledChoices(\n answers,\n { shuffledValues: ((session && session.shuffledValues) || {}).answers },\n us('answers'),\n 'id',\n );\n }\n\n if (!isEmpty(shuffledValues)) {\n if (updateSession && typeof updateSession === 'function') {\n updateSession(session.id, session.element, {\n shuffledValues,\n }).catch((e) => {\n // eslint-disable-next-line no-console\n console.error('update session failed', e);\n });\n }\n }\n\n if (question && prompts) {\n prompts.forEach((prompt) => {\n correctResponse[prompt.id] = prompt.relatedAnswer;\n });\n }\n\n const feedback =\n env.mode === 'evaluate'\n ? await getFeedbackForCorrectness(correctInfo.correctness, question.feedback)\n : undefined;\n\n const base = {\n config: {\n ...question,\n prompts,\n answers,\n },\n correctness: correctInfo,\n feedback,\n mode: env.mode,\n };\n\n if (env.role === 'instructor' && (env.mode === 'view' || env.mode === 'evaluate')) {\n base.rationale = question.rationale;\n } else {\n base.rationale = null;\n }\n\n const out = Object.assign(base, {\n correctResponse,\n });\n\n log('out: ', out);\n return out;\n}\n\nexport const createCorrectResponseSession = (question, env) => {\n return new Promise((resolve) => {\n if (env.mode !== 'evaluate' && env.role === 'instructor') {\n const { prompts, answers } = question;\n const value = {};\n\n prompts.forEach((p) => {\n if (answers.filter((a) => a.id === p.relatedAnswer).length) {\n value[p.id] = p.relatedAnswer;\n }\n });\n\n resolve({\n value,\n id: '1',\n });\n } else {\n resolve(null);\n }\n });\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../../shared/feedback/dist/index.js","../../../../../shared/controller-utils/dist/index.js","../../../src/controller/defaults.ts","../../../src/controller/index.ts"],"sourcesContent":["//#region src/defaults.ts\nvar e = {\n\tcorrect: {\n\t\ttype: \"default\",\n\t\tdefault: \"Correct\",\n\t\tcustom: \"Correct\"\n\t},\n\tincorrect: {\n\t\ttype: \"default\",\n\t\tdefault: \"Incorrect\",\n\t\tcustom: \"Incorrect\"\n\t},\n\tpartial: {\n\t\ttype: \"default\",\n\t\tdefault: \"Nearly\",\n\t\tcustom: \"Nearly\"\n\t},\n\tunanswered: {\n\t\ttype: \"default\",\n\t\tdefault: \"You have not entered a response\",\n\t\tcustom: \"You have not entered a response\"\n\t}\n};\n//#endregion\n//#region src/utils.ts\nfunction t(e) {\n\treturn e === \"partially-correct\" ? \"partial\" : e;\n}\n//#endregion\n//#region src/index.ts\nfunction n(n, r) {\n\tlet i = {\n\t\t...e,\n\t\t...r\n\t}, a = t(n), o = e[a] || {}, s = i[a] || o;\n\treturn {\n\t\tconfig: s,\n\t\tfallback: o[s.type || \"default\"]\n\t};\n}\nfunction r(e, t = {}) {\n\treturn new Promise((r) => {\n\t\tlet { config: a, fallback: o } = n(e, t);\n\t\ti(a, o).then(r);\n\t});\n}\nfunction i(e, t) {\n\treturn new Promise((n) => {\n\t\tif (!e || e.type === \"none\") {\n\t\t\tn(void 0);\n\t\t\treturn;\n\t\t}\n\t\tn(e[e.type] || t);\n\t});\n}\nfunction a(e, t = {}) {\n\tlet { config: r, fallback: i } = n(e, t);\n\treturn o(r, i);\n}\nfunction o(e, t) {\n\tif (!(!e || e.type === \"none\")) return e[e.type] || t;\n}\n//#endregion\nexport { e as defaultFeedback, o as getActualFeedback, a as getActualFeedbackForCorrectness, i as getFeedback, r as getFeedbackForCorrectness, t as normalizeCorrectness };\n","//#region \\0rolldown/runtime.js\nvar e = Object.defineProperty, t = (t, n) => {\n\tlet r = {};\n\tfor (var i in t) e(r, i, {\n\t\tget: t[i],\n\t\tenumerable: !0\n\t});\n\treturn n || e(r, Symbol.toStringTag, { value: \"Module\" }), r;\n};\n//#endregion\n//#region src/persistence.ts\nfunction n(e) {\n\treturn Array.isArray(e) ? e.filter((e) => e != null) : [];\n}\nfunction r(e) {\n\tlet t = [...e];\n\tfor (let e = t.length - 1; e > 0; e--) {\n\t\tlet n = Math.floor(Math.random() * (e + 1));\n\t\t[t[e], t[n]] = [t[n], t[e]];\n\t}\n\treturn t;\n}\nfunction i(e) {\n\treturn e == null ? !0 : Array.isArray(e) ? e.length === 0 : typeof e == \"object\" && Object.keys(e).length === 0;\n}\nasync function a(e, t, a, o = \"value\") {\n\tlet s = n(t?.data?.shuffledValues ?? t?.shuffledValues ?? []);\n\tif (!t) {\n\t\tconsole.warn(\"Unable to save shuffled choices because there's no session.\");\n\t\treturn;\n\t}\n\tif (!i(s)) return n(s.map((t) => e.find((e) => e[o] === t)));\n\tlet c = r(e);\n\tif (a && typeof a == \"function\") try {\n\t\tlet e = n(c.map((e) => e[o]));\n\t\ti(e) ? console.error(`shuffledValues is an empty array - refusing to call updateSession. shuffledChoices: ${JSON.stringify(c)}, key: ${o}`) : await a(t.id, t.element, { shuffledValues: e });\n\t} catch (e) {\n\t\tconsole.warn(\"Unable to save shuffled order for choices\"), console.error(e);\n\t}\n\telse console.warn(\"Unable to save shuffled choices, shuffle will happen every time.\");\n\treturn c;\n}\nfunction o(e, t, n) {\n\treturn !!(e.lockChoiceOrder || n?.[\"@pie-element\"]?.lockChoiceOrder);\n}\n//#endregion\n//#region src/partial-scoring.ts\nvar s = /* @__PURE__ */ t({ enabled: () => c });\nfunction c(e, t, n) {\n\treturn e?.partialScoring === !1 || t?.partialScoring === !1 ? !1 : typeof n != \"boolean\" || n;\n}\n//#endregion\nexport { a as getShuffledChoices, o as lockChoices, s as partialScoring };\n","import type { MatchListAnswer, MatchListModel, MatchListPrompt } from '../types.js';\n\nexport default {\n config: {\n answers: [] as MatchListAnswer[],\n prompt: '',\n prompts: [] as MatchListPrompt[],\n shuffled: false,\n },\n} satisfies Omit<MatchListModel, 'id' | 'element'>;\n","import { isEmpty } from '@pie-element/shared-lodash';\nimport { getFeedbackForCorrectness, type Correctness, type Feedback } from '@pie-element/shared-feedback';\nimport { lockChoices, getShuffledChoices, partialScoring } from '@pie-element/shared-controller-utils';\nimport type { PieEnvironment, PieUpdateSession } from '@pie-element/shared-types';\nimport debug from 'debug';\n\nconst log = debug('@pie-element:match-list:controller');\n\nimport defaults from './defaults.js';\nimport type { MatchListAnswer, MatchListModel, MatchListPrompt, MatchListSession } from '../types.js';\n\ntype SessionValue = NonNullable<MatchListSession['value']>;\n\nexport type MatchListOutcome = { score?: number; empty?: boolean; completed?: boolean; logTrace: string[] };\n\nexport type ModelView = {\n config: MatchListModel & { prompts: MatchListPrompt[] | undefined; answers: MatchListAnswer[] | undefined };\n correctness: { score: string; correctness: Correctness | undefined };\n feedback: string | undefined;\n mode: PieEnvironment['mode'];\n rationale?: string | null;\n correctResponse: Record<string, string | number>;\n};\n\nconst getResponseCorrectness = (\n model: MatchListModel,\n answers: SessionValue | undefined,\n env: PieEnvironment,\n): Correctness => {\n const isPartialScoring = partialScoring.enabled(model, env);\n const prompts = model.prompts;\n\n if (!answers || Object.keys(answers).length === 0) {\n return 'unanswered';\n }\n\n const totalCorrectAnswers = getTotalCorrect(model);\n const correctAnswers = getCorrectSelected(prompts, answers);\n\n if (totalCorrectAnswers === correctAnswers) {\n return 'correct';\n } else if (correctAnswers === 0) {\n return 'incorrect';\n } else if (isPartialScoring) {\n return 'partial';\n }\n\n return 'incorrect';\n};\n\nconst getCorrectness = (question: MatchListModel, env: PieEnvironment, answers: SessionValue | undefined) => {\n if (env.mode === 'evaluate') {\n return getResponseCorrectness(question, answers, env);\n }\n};\n\nconst getCorrectSelected = (prompts: MatchListPrompt[] = [], answers: SessionValue) => {\n let correctAnswers = 0;\n\n prompts.forEach((p) => {\n if (p.relatedAnswer === answers[p.id]) {\n correctAnswers += 1;\n }\n });\n\n return correctAnswers;\n};\n\nconst getTotalCorrect = (question: MatchListModel) => {\n return question && question.prompts ? question.prompts.length : 0;\n};\n\nconst getPartialScore = (question: MatchListModel, answers: SessionValue) => {\n const count = getCorrectSelected(question && question.prompts, answers);\n const totalCorrect = getTotalCorrect(question);\n\n return parseFloat((count / totalCorrect).toFixed(2));\n};\n\nconst getOutComeScore = (question: MatchListModel, env: PieEnvironment, answers: SessionValue | undefined) => {\n const correctness = getCorrectness(question, env, answers);\n\n return correctness === 'correct' ? 1 : correctness === 'partial' && true ? getPartialScore(question, answers!) : 0;\n};\n\n/**\n * Generates detailed trace log for match-list scoring evaluation\n */\nexport const getLogTrace = (\n question: MatchListModel,\n session: MatchListSession | undefined,\n env: PieEnvironment,\n): string[] => {\n const traceLog: string[] = [];\n\n const prompts = question?.prompts || [];\n const answers = session?.value || {};\n\n if (!answers || Object.keys(answers).length === 0) {\n traceLog.push('Student did not provide any answer.');\n return traceLog;\n }\n\n const totalPrompts = prompts.length;\n traceLog.push(`Match-list item contains ${totalPrompts} question(s).`);\n\n let correctCount = 0;\n let answeredCount = 0;\n\n prompts.forEach((prompt) => {\n const expectedAnswer = prompt.relatedAnswer;\n const studentAnswer = answers[prompt.id];\n\n if (studentAnswer !== undefined) {\n answeredCount += 1;\n\n if (studentAnswer === expectedAnswer) {\n correctCount += 1;\n traceLog.push(`Question ${prompt.id}: correctly matched.`);\n } else {\n traceLog.push(\n `Question ${prompt.id}: incorrectly matched (selected '${studentAnswer}', expected '${expectedAnswer}').`,\n );\n }\n } else {\n traceLog.push(`Question ${prompt.id}: no answer provided.`);\n }\n });\n\n traceLog.push(\n `Student correctly matched ${correctCount} out of ${totalPrompts} question(s).`,\n );\n\n const partialScoringEnabled = partialScoring.enabled(question, env);\n\n if (partialScoringEnabled) {\n traceLog.push('Score calculated using partial scoring.');\n traceLog.push(\n 'Score is the number of correct matches divided by the total number of questions.',\n );\n } else {\n traceLog.push('Score calculated using all-or-nothing scoring.');\n traceLog.push(\n 'Student must match all questions correctly to receive full credit.',\n );\n }\n\n const rawScore = getOutComeScore(question, env, answers);\n const finalScore = partialScoringEnabled ? rawScore : rawScore === 1 ? 1 : 0;\n\n traceLog.push(`Final score: ${finalScore}.`);\n\n return traceLog;\n};\n\nexport const outcome = (question: MatchListModel, session: MatchListSession | undefined, env: PieEnvironment) => {\n return new Promise<MatchListOutcome>((resolve) => {\n if (env.mode !== 'evaluate') {\n resolve({ score: undefined, completed: undefined, logTrace: [] });\n } else {\n if (!session || isEmpty(session)) {\n resolve({ score: 0, empty: true, logTrace: ['Student did not provide any answer.'] });\n } else {\n const out = {\n score: getOutComeScore(question, env, session!.value),\n empty: false,\n logTrace: getLogTrace(question, session, env),\n };\n\n resolve(out);\n }\n }\n });\n};\n\nexport function createDefaultModel(model: Partial<MatchListModel> = {}) {\n return new Promise<Omit<MatchListModel, 'id' | 'element'> & Partial<MatchListModel>>((resolve) => {\n resolve({\n ...defaults,\n ...model,\n });\n });\n}\n\nexport const normalize = (model: MatchListModel): MatchListModel => ({ ...defaults, ...model });\n\n/**\n * @param updateSession - optional - a function that will set the properties passed into it on the session.\n */\nexport async function model(\n question: MatchListModel,\n session: MatchListSession | undefined,\n env: PieEnvironment,\n updateSession?: PieUpdateSession,\n): Promise<ModelView> {\n question = normalize(question);\n const correctness = getCorrectness(question, env, session && session.value);\n const correctResponse: Record<string, string | number> = {};\n const score = `${getOutComeScore(question, env, session && session.value) * 100}%`;\n const correctInfo = {\n score,\n correctness,\n };\n\n const shuffledValues: NonNullable<MatchListSession['shuffledValues']> = {};\n let prompts = question.prompts;\n let answers = question.answers;\n\n const us = (part: 'prompts' | 'answers'): PieUpdateSession => (_id, _element, update) => {\n return new Promise((resolve) => {\n shuffledValues[part] = update.shuffledValues as (string | number)[];\n resolve();\n });\n };\n\n const lockChoiceOrder = lockChoices(question, session as { id?: string; element?: string } | undefined, env);\n\n if (!lockChoiceOrder) {\n prompts = await getShuffledChoices(\n prompts!,\n { shuffledValues: ((session && session.shuffledValues) || {}).prompts as string[] | undefined },\n us('prompts'),\n 'id',\n );\n answers = await getShuffledChoices(\n answers!,\n { shuffledValues: ((session && session.shuffledValues) || {}).answers as string[] | undefined },\n us('answers'),\n 'id',\n );\n }\n\n if (!isEmpty(shuffledValues)) {\n if (updateSession && typeof updateSession === 'function') {\n updateSession(session!.id as string, session!.element as string, {\n shuffledValues,\n }).catch((e: unknown) => {\n // eslint-disable-next-line no-console\n console.error('update session failed', e);\n });\n }\n }\n\n if (question && prompts) {\n prompts.forEach((prompt) => {\n correctResponse[prompt.id] = prompt.relatedAnswer;\n });\n }\n\n const feedback =\n env.mode === 'evaluate'\n ? await getFeedbackForCorrectness(correctInfo.correctness!, question.feedback as Partial<Feedback>)\n : undefined;\n\n const base: Omit<ModelView, 'correctResponse'> = {\n config: {\n ...question,\n prompts,\n answers,\n },\n correctness: correctInfo,\n feedback,\n mode: env.mode,\n };\n\n if (env.role === 'instructor' && (env.mode === 'view' || env.mode === 'evaluate')) {\n base.rationale = question.rationale;\n } else {\n base.rationale = null;\n }\n\n const out = Object.assign(base, {\n correctResponse,\n });\n\n log('out: ', out);\n return out;\n}\n\nexport const createCorrectResponseSession = (question: MatchListModel, env: PieEnvironment) => {\n return new Promise<MatchListSession | null>((resolve) => {\n if (env.mode !== 'evaluate' && env.role === 'instructor') {\n const { prompts, answers } = question;\n const value: SessionValue = {};\n\n prompts!.forEach((p) => {\n if (answers!.filter((a) => a.id === p.relatedAnswer).length) {\n value[p.id] = p.relatedAnswer;\n }\n });\n\n resolve({\n value,\n id: '1',\n });\n } else {\n resolve(null);\n }\n });\n};\n"],"mappings":"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"}
@@ -1,9 +1,10 @@
1
+ import type { MatchListAnswer, MatchListPrompt } from '../types.js';
1
2
  declare const _default: {
2
3
  config: {
3
- answers: never[];
4
+ answers: MatchListAnswer[];
4
5
  prompt: string;
5
- prompts: never[];
6
- shuffled: boolean;
6
+ prompts: MatchListPrompt[];
7
+ shuffled: false;
7
8
  };
8
9
  };
9
10
  export default _default;
@@ -1,30 +1,35 @@
1
+ import { type Correctness } from '@pie-element/shared-feedback';
2
+ import type { PieEnvironment, PieUpdateSession } from '@pie-element/shared-types';
3
+ import type { MatchListAnswer, MatchListModel, MatchListPrompt, MatchListSession } from '../types.js';
4
+ export type MatchListOutcome = {
5
+ score?: number;
6
+ empty?: boolean;
7
+ completed?: boolean;
8
+ logTrace: string[];
9
+ };
10
+ export type ModelView = {
11
+ config: MatchListModel & {
12
+ prompts: MatchListPrompt[] | undefined;
13
+ answers: MatchListAnswer[] | undefined;
14
+ };
15
+ correctness: {
16
+ score: string;
17
+ correctness: Correctness | undefined;
18
+ };
19
+ feedback: string | undefined;
20
+ mode: PieEnvironment['mode'];
21
+ rationale?: string | null;
22
+ correctResponse: Record<string, string | number>;
23
+ };
1
24
  /**
2
25
  * Generates detailed trace log for match-list scoring evaluation
3
- * @param {Object} question
4
- * @param {Object} session
5
- * @param {Object} env
6
- * @returns {Array<string>} traceLog
7
26
  */
8
- export declare const getLogTrace: (question: any, session: any, env: any) => string[];
9
- export declare const outcome: (question: any, session: any, env: any) => Promise<unknown>;
10
- export declare function createDefaultModel(model?: {}): Promise<unknown>;
11
- export declare const normalize: (model: any) => any;
27
+ export declare const getLogTrace: (question: MatchListModel, session: MatchListSession | undefined, env: PieEnvironment) => string[];
28
+ export declare const outcome: (question: MatchListModel, session: MatchListSession | undefined, env: PieEnvironment) => Promise<MatchListOutcome>;
29
+ export declare function createDefaultModel(model?: Partial<MatchListModel>): Promise<Omit<MatchListModel, "id" | "element"> & Partial<MatchListModel>>;
30
+ export declare const normalize: (model: MatchListModel) => MatchListModel;
12
31
  /**
13
- *
14
- * @param {*} question
15
- * @param {*} session
16
- * @param {*} env
17
- * @param {*} updateSession - optional - a function that will set the properties passed into it on the session.
32
+ * @param updateSession - optional - a function that will set the properties passed into it on the session.
18
33
  */
19
- export declare function model(question: any, session: any, env: any, updateSession: any): Promise<{
20
- config: any;
21
- correctness: {
22
- score: string;
23
- correctness: string | undefined;
24
- };
25
- feedback: string | undefined;
26
- mode: any;
27
- } & {
28
- correctResponse: {};
29
- }>;
30
- export declare const createCorrectResponseSession: (question: any, env: any) => Promise<unknown>;
34
+ export declare function model(question: MatchListModel, session: MatchListSession | undefined, env: PieEnvironment, updateSession?: PieUpdateSession): Promise<ModelView>;
35
+ export declare const createCorrectResponseSession: (question: MatchListModel, env: PieEnvironment) => Promise<MatchListSession | null>;
package/dist/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { default } from './delivery/index.js';
2
+ export type * from './types.js';
@@ -0,0 +1,435 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://cdn.jsdelivr.net/npm/@pie-element/match-list@8.0.4/dist/schemas/model.schema.json",
4
+ "type": "object",
5
+ "properties": {
6
+ "id": {
7
+ "type": "string",
8
+ "description": "Identifier to identify the Pie Element in html markup, Must be unique within a pie item config."
9
+ },
10
+ "element": {
11
+ "type": "string",
12
+ "description": "The html Element tag name: the key in the item config's `elements` map, e.g. \"multiple-choice\""
13
+ },
14
+ "accessibilityCatalogs": {
15
+ "type": "array",
16
+ "items": {
17
+ "$ref": "#/definitions/AccessibilityCatalog"
18
+ },
19
+ "description": "QTI-aligned accessibility catalogs whose cards dock to content nodes by `data-catalog-idref`"
20
+ },
21
+ "prompt": {
22
+ "type": "string",
23
+ "description": "The question prompt or item stem"
24
+ },
25
+ "prompts": {
26
+ "type": "array",
27
+ "items": {
28
+ "$ref": "#/definitions/MatchListPrompt"
29
+ },
30
+ "description": "The question prompts that are going to be displayed. The default model carries none."
31
+ },
32
+ "answers": {
33
+ "type": "array",
34
+ "items": {
35
+ "$ref": "#/definitions/MatchListAnswer"
36
+ },
37
+ "description": "The answer rows that are going to be displayed. The default model carries none."
38
+ },
39
+ "lockChoiceOrder": {
40
+ "type": "boolean",
41
+ "description": "Indicates if answers should be locked in order according to the model or if they should be shuffed true - order is respected according to model false - order is shuffled"
42
+ },
43
+ "duplicates": {
44
+ "type": "boolean",
45
+ "description": "Indicates if duplicates are allowed"
46
+ },
47
+ "partialScoring": {
48
+ "type": "boolean",
49
+ "description": "Indicates if partial scoring should be used. Scoring treats an absent value as enabled."
50
+ },
51
+ "feedback": {
52
+ "$ref": "#/definitions/MatchListFeedback",
53
+ "description": "Feedback shown in evaluate mode"
54
+ },
55
+ "rationale": {
56
+ "type": "string",
57
+ "description": "Indicates rationale for correct answer; shown to instructors in view and evaluate mode"
58
+ },
59
+ "language": {
60
+ "type": "string",
61
+ "description": "Indicates the language of the component Supported options: en, es, en_US, en-US, es_ES, es-ES, es_MX, es-MX"
62
+ },
63
+ "config": {
64
+ "type": "object",
65
+ "properties": {
66
+ "answers": {
67
+ "type": "array",
68
+ "items": {
69
+ "$ref": "#/definitions/MatchListAnswer"
70
+ }
71
+ },
72
+ "prompt": {
73
+ "type": "string"
74
+ },
75
+ "prompts": {
76
+ "type": "array",
77
+ "items": {
78
+ "$ref": "#/definitions/MatchListPrompt"
79
+ }
80
+ },
81
+ "shuffled": {
82
+ "type": "boolean"
83
+ }
84
+ },
85
+ "description": "Not read by this element: the controller defaults nest their values under this key, so the default model carries it, and the controller reads `prompts`, `answers` and `prompt` from the root"
86
+ }
87
+ },
88
+ "required": [
89
+ "element",
90
+ "id"
91
+ ],
92
+ "description": "Model for the `@pie-element/match-list` Interaction",
93
+ "definitions": {
94
+ "AccessibilityCatalog": {
95
+ "type": "object",
96
+ "properties": {
97
+ "identifier": {
98
+ "type": "string"
99
+ },
100
+ "cards": {
101
+ "type": "array",
102
+ "items": {
103
+ "$ref": "#/definitions/CatalogCard"
104
+ }
105
+ }
106
+ },
107
+ "required": [
108
+ "identifier",
109
+ "cards"
110
+ ]
111
+ },
112
+ "CatalogCard": {
113
+ "type": "object",
114
+ "properties": {
115
+ "catalog": {
116
+ "type": "string"
117
+ },
118
+ "language": {
119
+ "type": "string"
120
+ },
121
+ "content": {
122
+ "type": "string"
123
+ },
124
+ "payload": {
125
+ "$ref": "#/definitions/CatalogCardPayload"
126
+ }
127
+ },
128
+ "required": [
129
+ "catalog"
130
+ ],
131
+ "description": "QTI-aligned accessibility catalog entry: one alternate representation of a content node, docked to it by `data-catalog-idref`.\n\nMaps onto QTI 3's `qti-card`. `catalog` is QTI's `@support` and is the only discriminant — there is deliberately no second `kind` field. `language` is the card entry's `xml:lang`. QTI's single content slot is represented by exactly one of two fields, never both, with nothing mirrored between them:\n\n- `content` — the string form, for alternates a string can express: SSML for `spoken`, plain text for `simplified-language`.\n- `payload` — the structured form, for what a string cannot express. Today only a signing video.\n\nOne generic payload slot rather than a field per accommodation (`signLanguage`, `braille`, …), which is the shape pie-players canonicalises and the shape this repo now follows. A field per accommodation makes every new structured alternate a breaking widening of this type in every consumer that reads cards, and it gives the same fact two names across repos — which produced a real bug: a card carrying `signLanguage` rendered its video in the player and was simultaneously reported as carrying no payload by the player's \"what alternates exist\" path, because only one of the two read paths knew about the alias.\n\n`catalog` is a bare `string` because the vocabulary is open-ended and unknown tokens must be ignored rather than rejected. TypeScript therefore cannot express \"a `sign-language` card must carry a payload\", so a `{ catalog: 'sign-language', content: '<url>' }` object still satisfies this type. That legacy bare-URL form is not supported: runtime validation on the consuming side must reject it rather than render the URL as visible text. Narrow with `isSignLanguageCard`."
132
+ },
133
+ "CatalogCardPayload": {
134
+ "anyOf": [
135
+ {
136
+ "$ref": "#/definitions/SignLanguageCardPayload"
137
+ },
138
+ {
139
+ "$ref": "#/definitions/SpokenAudioCardPayload"
140
+ }
141
+ ],
142
+ "description": "The structured forms a card's `payload` may take.\n\nWhich one applies is decided by the card's `catalog`, so this union carries no discriminant of its own — a second discriminant could contradict the first. Consumers select a card by catalog type and then validate the payload structurally, which they must do regardless: catalog data is authored, wire-facing and untrusted."
143
+ },
144
+ "SignLanguageCardPayload": {
145
+ "type": "object",
146
+ "properties": {
147
+ "signLang": {
148
+ "type": "string",
149
+ "description": "Language tag for the signed translation — BCP 47, which admits ISO 639-3 subtags for languages with no 639-1/2 code. \"ase\" is ASL, matching QTI 3 `xml:lang`.\n\nThis is the language of the *adaptation*, not the item's base content language (AfA/PNP's `languageOfAdaptation`). The distinction is load bearing: a Spanish item's signed alternate is LSM, not ASL, so `signLang` must never be inferred from the item or assessment content language.\n\nOptional, and redundant on almost every card. The card's `language` states the same code and is the only field pie-players resolves a card on — resolution runs before anything knows the card is a signing card, so it can only key on the generic field — and the player falls back to it when this is absent. Author it only where the two differ: a card tagged with the item's content language (`language: 'en-US'`, `signLang: 'ase'`) so resolution reaches it by the default-language rung. The pie-api-aws importer emits `language` alone."
150
+ },
151
+ "media": {
152
+ "$ref": "#/definitions/MediaAssetRef"
153
+ },
154
+ "fragment": {
155
+ "$ref": "#/definitions/MediaFragmentRange"
156
+ }
157
+ },
158
+ "required": [
159
+ "media"
160
+ ],
161
+ "description": "Payload for a `sign-language` catalog card: a signed translation of the content node the card is docked to."
162
+ },
163
+ "MediaAssetRef": {
164
+ "type": "object",
165
+ "properties": {
166
+ "version": {
167
+ "type": "number",
168
+ "const": 1
169
+ },
170
+ "id": {
171
+ "type": "string"
172
+ },
173
+ "kind": {
174
+ "$ref": "#/definitions/MediaKind"
175
+ },
176
+ "sources": {
177
+ "type": "array",
178
+ "items": {
179
+ "$ref": "#/definitions/MediaSource"
180
+ }
181
+ },
182
+ "poster": {
183
+ "type": "string"
184
+ },
185
+ "thumbnail": {
186
+ "type": "string"
187
+ },
188
+ "durationSeconds": {
189
+ "type": "number"
190
+ },
191
+ "tracks": {
192
+ "type": "array",
193
+ "items": {
194
+ "$ref": "#/definitions/TextTrackRef"
195
+ }
196
+ },
197
+ "transcript": {
198
+ "$ref": "#/definitions/TranscriptRef"
199
+ },
200
+ "label": {
201
+ "type": "string"
202
+ },
203
+ "description": {
204
+ "type": "string"
205
+ },
206
+ "lang": {
207
+ "type": "string",
208
+ "description": "Language of the asset itself, as authored metadata. Sign-language cards leave it unset: the card's `language` already states the adaptation language, and a second copy is a second thing to keep in agreement."
209
+ }
210
+ },
211
+ "required": [
212
+ "version",
213
+ "id",
214
+ "kind",
215
+ "sources"
216
+ ],
217
+ "description": "Reference to a media asset and its playable encodings.\n\nStructurally mirrors the `MediaAssetRef` in the pie-players `media-asset-contract` PRD. The two definitions are deliberately kept as separate declarations rather than a shared package dependency: this repo owns the item-model side of the contract, pie-players owns the player side, and both read the same authored JSON, so structural typing is sufficient for interop. Fixture parity — not a package edge — is what pins them together.\n\nSign-language catalog cards were the first consumer and use only the core source fields. The optional presentation, track, and transcript metadata is declared for broader consumers such as video stimuli; it remains meaningless for a signing clip, where captions would duplicate the text already on screen."
218
+ },
219
+ "MediaKind": {
220
+ "type": "string",
221
+ "enum": [
222
+ "image",
223
+ "audio",
224
+ "video",
225
+ "other"
226
+ ]
227
+ },
228
+ "MediaSource": {
229
+ "type": "object",
230
+ "properties": {
231
+ "src": {
232
+ "type": "string"
233
+ },
234
+ "type": {
235
+ "type": "string",
236
+ "description": "MIME type, e.g. \"video/mp4\"."
237
+ },
238
+ "width": {
239
+ "type": "number"
240
+ },
241
+ "height": {
242
+ "type": "number"
243
+ },
244
+ "bitrate": {
245
+ "type": "number"
246
+ }
247
+ },
248
+ "required": [
249
+ "src"
250
+ ],
251
+ "description": "One playable encoding of a media asset."
252
+ },
253
+ "TextTrackRef": {
254
+ "type": "object",
255
+ "properties": {
256
+ "src": {
257
+ "type": "string"
258
+ },
259
+ "kind": {
260
+ "type": "string",
261
+ "enum": [
262
+ "captions",
263
+ "subtitles",
264
+ "descriptions",
265
+ "chapters",
266
+ "metadata"
267
+ ]
268
+ },
269
+ "lang": {
270
+ "type": "string"
271
+ },
272
+ "label": {
273
+ "type": "string"
274
+ },
275
+ "default": {
276
+ "type": "boolean"
277
+ }
278
+ },
279
+ "required": [
280
+ "src",
281
+ "kind",
282
+ "lang",
283
+ "label"
284
+ ],
285
+ "description": "One authored text track associated with an audio or video asset."
286
+ },
287
+ "TranscriptRef": {
288
+ "type": "object",
289
+ "properties": {
290
+ "src": {
291
+ "type": "string"
292
+ },
293
+ "html": {
294
+ "type": "string"
295
+ },
296
+ "plainText": {
297
+ "type": "string"
298
+ },
299
+ "lang": {
300
+ "type": "string"
301
+ }
302
+ },
303
+ "description": "Inline or externally hosted transcript content for a media asset."
304
+ },
305
+ "MediaFragmentRange": {
306
+ "type": "object",
307
+ "properties": {
308
+ "startSeconds": {
309
+ "type": "number"
310
+ },
311
+ "endSeconds": {
312
+ "type": "number"
313
+ }
314
+ },
315
+ "required": [
316
+ "startSeconds"
317
+ ],
318
+ "description": "A time slice within a media asset, so one recording can serve several content nodes. Mirrors QTI 3's Media Fragments URI usage."
319
+ },
320
+ "SpokenAudioCardPayload": {
321
+ "type": "object",
322
+ "properties": {
323
+ "media": {
324
+ "$ref": "#/definitions/MediaAssetRef"
325
+ },
326
+ "fragment": {
327
+ "$ref": "#/definitions/MediaFragmentRange"
328
+ }
329
+ },
330
+ "required": [
331
+ "media"
332
+ ],
333
+ "description": "Payload for a `spoken` catalog card that is a recording rather than a script.\n\nQTI 3 treats recorded audio and synthesized speech as the same support: both are `spoken`, so this is not a new accommodation, it is the other form the existing one can take. A node commonly carries both this and a `content` card in the same language — the script is both what the audio was generated from and the fallback for when the audio cannot play; pie-players resolves between them with `CatalogLookupOptions.form`.\n\nNo `kind` discriminant, for the same reason `SignLanguageCardPayload` has none: the card's `catalog` already says what this is."
334
+ },
335
+ "MatchListPrompt": {
336
+ "type": "object",
337
+ "properties": {
338
+ "id": {
339
+ "type": [
340
+ "string",
341
+ "number"
342
+ ],
343
+ "description": "Identifier for a question prompt"
344
+ },
345
+ "title": {
346
+ "type": "string",
347
+ "description": "Text that will be displayed in the question prompt row"
348
+ },
349
+ "relatedAnswer": {
350
+ "type": [
351
+ "string",
352
+ "number"
353
+ ],
354
+ "description": "Id for the correct answer for this question prompt"
355
+ }
356
+ },
357
+ "required": [
358
+ "id",
359
+ "title",
360
+ "relatedAnswer"
361
+ ],
362
+ "description": "Multiple question prompts"
363
+ },
364
+ "MatchListAnswer": {
365
+ "type": "object",
366
+ "properties": {
367
+ "id": {
368
+ "type": [
369
+ "string",
370
+ "number"
371
+ ],
372
+ "description": "Identifier for an answer row; the response counts as complete only when every placed id is a number"
373
+ },
374
+ "title": {
375
+ "type": "string",
376
+ "description": "Text that will be displayed in the answer row"
377
+ }
378
+ },
379
+ "required": [
380
+ "id",
381
+ "title"
382
+ ],
383
+ "description": "Multiple answers"
384
+ },
385
+ "MatchListFeedback": {
386
+ "type": "object",
387
+ "properties": {
388
+ "correct": {
389
+ "$ref": "#/definitions/MatchListFeedbackType",
390
+ "description": "Indicates the configuration for feedback when answer is correct"
391
+ },
392
+ "incorrect": {
393
+ "$ref": "#/definitions/MatchListFeedbackType",
394
+ "description": "Indicates the configuration for feedback when answer is incorrect"
395
+ },
396
+ "partial": {
397
+ "$ref": "#/definitions/MatchListFeedbackType",
398
+ "description": "Indicates the configuration for feedback when answer is partially correct"
399
+ },
400
+ "unanswered": {
401
+ "$ref": "#/definitions/MatchListFeedbackType",
402
+ "description": "Indicates the configuration for feedback when there is no answer"
403
+ }
404
+ },
405
+ "description": "Feedback for each correctness level; a level left out gets its default message"
406
+ },
407
+ "MatchListFeedbackType": {
408
+ "type": "object",
409
+ "properties": {
410
+ "type": {
411
+ "type": "string",
412
+ "enum": [
413
+ "default",
414
+ "none",
415
+ "custom"
416
+ ],
417
+ "description": "Indicates the feedback type"
418
+ },
419
+ "default": {
420
+ "type": "string",
421
+ "description": "Indicates the feedback value"
422
+ },
423
+ "custom": {
424
+ "type": "string",
425
+ "description": "Indicates the feedback custom value"
426
+ }
427
+ },
428
+ "required": [
429
+ "type"
430
+ ],
431
+ "description": "Feedback for one correctness level. A `custom` type without `custom` text shows the default message."
432
+ }
433
+ },
434
+ "additionalProperties": false
435
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "https://cdn.jsdelivr.net/npm/@pie-element/match-list@8.0.4/dist/schemas/session.schema.json",
4
+ "type": "object",
5
+ "properties": {
6
+ "id": {
7
+ "type": "string",
8
+ "description": "Identifier of the session"
9
+ },
10
+ "element": {
11
+ "type": "string",
12
+ "description": "The element the session belongs to"
13
+ },
14
+ "value": {
15
+ "type": "object",
16
+ "additionalProperties": {
17
+ "type": [
18
+ "string",
19
+ "number"
20
+ ]
21
+ },
22
+ "description": "The answer placed in each response area: the answer's `id`, keyed by the prompt's `id`"
23
+ },
24
+ "shuffledValues": {
25
+ "type": "object",
26
+ "properties": {
27
+ "prompts": {
28
+ "type": "array",
29
+ "items": {
30
+ "type": [
31
+ "string",
32
+ "number"
33
+ ]
34
+ }
35
+ },
36
+ "answers": {
37
+ "type": "array",
38
+ "items": {
39
+ "type": [
40
+ "string",
41
+ "number"
42
+ ]
43
+ }
44
+ }
45
+ },
46
+ "description": "Prompt and answer ids in the order they were shuffled for this learner, when choice order is not locked"
47
+ }
48
+ },
49
+ "definitions": {}
50
+ }
@@ -0,0 +1,91 @@
1
+ import type { PieModel } from '@pie-element/shared-types';
2
+ /** Multiple question prompts */
3
+ export interface MatchListPrompt {
4
+ /** Identifier for a question prompt */
5
+ id: string | number;
6
+ /** Text that will be displayed in the question prompt row */
7
+ title: string;
8
+ /** Id for the correct answer for this question prompt */
9
+ relatedAnswer: string | number;
10
+ }
11
+ /** Multiple answers */
12
+ export interface MatchListAnswer {
13
+ /** Identifier for an answer row; the response counts as complete only when every placed id is a number */
14
+ id: string | number;
15
+ /** Text that will be displayed in the answer row */
16
+ title: string;
17
+ }
18
+ /**
19
+ * Feedback for one correctness level. A `custom` type without `custom` text shows the default
20
+ * message.
21
+ */
22
+ export interface MatchListFeedbackType {
23
+ /** Indicates the feedback type */
24
+ type: 'default' | 'none' | 'custom';
25
+ /** Indicates the feedback value */
26
+ default?: string;
27
+ /** Indicates the feedback custom value */
28
+ custom?: string;
29
+ }
30
+ /** Feedback for each correctness level; a level left out gets its default message */
31
+ export interface MatchListFeedback {
32
+ /** Indicates the configuration for feedback when answer is correct */
33
+ correct?: MatchListFeedbackType;
34
+ /** Indicates the configuration for feedback when answer is incorrect */
35
+ incorrect?: MatchListFeedbackType;
36
+ /** Indicates the configuration for feedback when answer is partially correct */
37
+ partial?: MatchListFeedbackType;
38
+ /** Indicates the configuration for feedback when there is no answer */
39
+ unanswered?: MatchListFeedbackType;
40
+ }
41
+ /** Model for the `@pie-element/match-list` Interaction */
42
+ export interface MatchListModel extends PieModel {
43
+ /** The question prompt or item stem*/
44
+ prompt?: string;
45
+ /** The question prompts that are going to be displayed. The default model carries none. */
46
+ prompts?: MatchListPrompt[];
47
+ /** The answer rows that are going to be displayed. The default model carries none. */
48
+ answers?: MatchListAnswer[];
49
+ /**
50
+ * Indicates if answers should be locked in order according to the model or if they should be shuffed
51
+ * true - order is respected according to model
52
+ * false - order is shuffled
53
+ */
54
+ lockChoiceOrder?: boolean;
55
+ /** Indicates if duplicates are allowed */
56
+ duplicates?: boolean;
57
+ /** Indicates if partial scoring should be used. Scoring treats an absent value as enabled. */
58
+ partialScoring?: boolean;
59
+ /** Feedback shown in evaluate mode */
60
+ feedback?: MatchListFeedback;
61
+ /** Indicates rationale for correct answer; shown to instructors in view and evaluate mode */
62
+ rationale?: string;
63
+ /**
64
+ * Indicates the language of the component
65
+ * Supported options: en, es, en_US, en-US, es_ES, es-ES, es_MX, es-MX
66
+ */
67
+ language?: string;
68
+ /**
69
+ * Not read by this element: the controller defaults nest their values under this key, so the
70
+ * default model carries it, and the controller reads `prompts`, `answers` and `prompt` from the root
71
+ */
72
+ config?: {
73
+ answers?: MatchListAnswer[];
74
+ prompt?: string;
75
+ prompts?: MatchListPrompt[];
76
+ shuffled?: boolean;
77
+ };
78
+ }
79
+ export interface MatchListSession {
80
+ /** Identifier of the session */
81
+ id?: string;
82
+ /** The element the session belongs to */
83
+ element?: string;
84
+ /** The answer placed in each response area: the answer's `id`, keyed by the prompt's `id` */
85
+ value?: Record<string, string | number>;
86
+ /** Prompt and answer ids in the order they were shuffled for this learner, when choice order is not locked */
87
+ shuffledValues?: {
88
+ prompts?: (string | number)[];
89
+ answers?: (string | number)[];
90
+ };
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pie-element/match-list",
3
- "version": "8.0.4-next.20261011174919",
3
+ "version": "8.0.4",
4
4
  "description": "",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,19 +13,20 @@
13
13
  "@emotion/react": "^11.14.0",
14
14
  "@mui/icons-material": "^7.3.4",
15
15
  "@mui/material": "^7.3.4",
16
- "@pie-element/shared-utils": "0.1.4-next.20261008072957",
17
- "@pie-lib/correct-answer-toggle": "5.0.4-next.20261011174919",
18
- "@pie-lib/drag": "5.0.4-next.20261009061152",
19
- "@pie-lib/render-ui": "8.0.4-next.20261009061152",
16
+ "@pie-element/shared-utils": "0.1.4",
17
+ "@pie-lib/correct-answer-toggle": "5.0.4",
18
+ "@pie-lib/drag": "5.0.4",
19
+ "@pie-lib/render-ui": "8.0.4",
20
20
  "debug": "^4.1.1",
21
21
  "prop-types": "^15.8.1",
22
- "@pie-element/shared-lodash": "0.1.3-next.20261006160832",
23
- "@pie-element/shared-feedback": "0.1.3-next.20261006160832",
24
- "@pie-element/shared-controller-utils": "0.1.2-next.20261002215153",
25
- "@pie-element/shared-player-events": "0.1.2-next.20261002215153",
26
- "@pie-element/shared-math-rendering-mathjax": "0.1.5-next.20261009061152",
22
+ "@pie-element/shared-lodash": "0.1.2",
23
+ "@pie-element/shared-feedback": "0.1.2",
24
+ "@pie-element/shared-controller-utils": "0.1.2",
25
+ "@pie-element/shared-player-events": "0.1.1",
26
+ "@pie-element/shared-math-rendering-mathjax": "0.1.5",
27
27
  "react": "^18.2.0",
28
- "react-dom": "^18.2.0"
28
+ "react-dom": "^18.2.0",
29
+ "@pie-element/shared-types": "0.2.1"
29
30
  },
30
31
  "exports": {
31
32
  ".": {
@@ -56,6 +57,8 @@
56
57
  "types": "./dist/runtime-support.d.ts",
57
58
  "default": "./dist/runtime-support.js"
58
59
  },
60
+ "./schemas/model.schema.json": "./dist/schemas/model.schema.json",
61
+ "./schemas/session.schema.json": "./dist/schemas/session.schema.json",
59
62
  "./package.json": "./package.json"
60
63
  },
61
64
  "type": "module",
@@ -67,7 +70,7 @@
67
70
  ],
68
71
  "sideEffects": false,
69
72
  "scripts": {
70
- "build": "bun x vite build && bun x vite build --config ../../../tools/vite/element-browser.config.ts && bun x vite build --config vite.config.iife.ts && bun x tsc --emitDeclarationOnly",
73
+ "build": "bun x vite build && bun x vite build --config ../../../tools/vite/element-browser.config.ts && bun x vite build --config vite.config.iife.ts && bun x tsc --emitDeclarationOnly && bun ../../../tools/schemas/build.ts",
71
74
  "dev": "bun x vite",
72
75
  "demo": "bun x vite --mode demo",
73
76
  "test": "bun x vitest run"