@khanacademy/perseus-score 8.11.9 → 8.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/es/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-utils';
2
2
  import * as KAS from '@khanacademy/kas';
3
3
  import { KhanMath, geometry, coefficients, angles, number } from '@khanacademy/kmath';
4
- import { ErrorCodes, PerseusError, Errors, getDivideSymbol, getDecimalSeparator, approximateDeepEqual, GrapherUtil, approximateEqual, deepClone, getMatrixSize, convertInputNumberOptionsToNumericInput, getWidgetIdsFromContent, Registry } from '@khanacademy/perseus-core';
4
+ import { ErrorCodes, PerseusError, Errors, getDivideSymbol, getDecimalSeparator, approximateDeepEqual, GrapherUtil, approximateEqual, deepClone, getMatrixSize, getWidgetIdsFromContent, Registry } from '@khanacademy/perseus-core';
5
5
  import _ from 'underscore';
6
6
 
7
- const libName="@khanacademy/perseus-score";const libVersion="8.11.9";addLibraryVersionToPerseusDebug(libName,libVersion);
7
+ const libName="@khanacademy/perseus-score";const libVersion="8.12.0";addLibraryVersionToPerseusDebug(libName,libVersion);
8
8
 
9
9
  const MAXERROR_EPSILON=Math.pow(2,-42);const KhanAnswerTypes={predicate:{defaultForms:"integer, proper, improper, mixed, decimal",createValidatorFunctional:function(predicate,rawOptions){const options={simplify:"required",ratio:false,forms:KhanAnswerTypes.predicate.defaultForms,...rawOptions};let acceptableForms;if(!_.isArray(options.forms)){acceptableForms=options.forms.split(/\s*,\s*/);}else {acceptableForms=options.forms;}if(options.inexact===undefined){options.maxError=0;}const maxError=+(options.maxError??0)+MAXERROR_EPSILON;if(_.contains(acceptableForms,"percent")){acceptableForms=_.without(acceptableForms,"percent");acceptableForms.push("percent");}const fractionTransformer=function(text){text=text.replace(/\u2212/,"-").replace(/([+-])\s+/g,"$1").replace(/(^\s*)|(\s*$)/gi,"");const match=text.match(/^([+-]?\d+)\s*\/\s*([+-]?\d+)$/);const mobileDeviceMatch=text.match(/^([+-]?)\\frac\{([+-]?\d+)\}\{([+-]?\d+)\}$/);const parsedInt=parseInt(text,10);if(match||mobileDeviceMatch){let num;let denom;let simplified=true;if(match){num=parseFloat(match[1]);denom=parseFloat(match[2]);}else {num=parseFloat(mobileDeviceMatch[2]);if(mobileDeviceMatch[1]==="-"){if(num<0){simplified=false;}num=-num;}denom=parseFloat(mobileDeviceMatch[3]);}simplified=simplified&&denom>0&&(options.ratio||denom!==1)&&KhanMath.getGCD(num,denom)===1;return [{value:num/denom,exact:simplified}]}if(!isNaN(parsedInt)&&""+parsedInt===text){return [{value:parsedInt,exact:true}]}return []};const forms={integer:function(text){const decimal=forms.decimal(text);const rounded=forms.decimal(text,1);if(decimal[0]?.value!=null&&decimal[0].value===rounded[0]?.value||decimal[1]?.value!=null&&decimal[1].value===rounded[1]?.value){return decimal}return []},proper:function(text){const transformed=fractionTransformer(text);return transformed.flatMap(o=>{if(Math.abs(o.value)<1){return [o]}return []})},improper:function(text){const fractionExists=text.includes("/")||text.match(/\\(d?frac)/);if(!fractionExists){return []}const transformed=fractionTransformer(text);return transformed.flatMap(o=>{if(Math.abs(o.value)>=1){return [o]}return []})},pi:function(text){let match;let possibilities=[];text=text.replace(/\u2212/,"-");if(match=text.match(/^([+-]?)\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)){possibilities=[{value:parseFloat(match[1]+"1"),exact:true}];}else if(match=text.match(/^([+-]?\s*\d+\s*(?:\/\s*[+-]?\s*\d+)?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)){possibilities=fractionTransformer(match[1]);}else if(match=text.match(/^([+-]?)\s*(\d+)\s*([+-]?\d+)\s*\/\s*([+-]?\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)){const sign=parseFloat(match[1]+"1");const integ=parseFloat(match[2]);const num=parseFloat(match[3]);const denom=parseFloat(match[4]);const simplified=num<denom&&KhanMath.getGCD(num,denom)===1;possibilities=[{value:sign*(integ+num/denom),exact:simplified}];}else if(match=text.match(/^([+-]?\s*\d+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\s*\d+))?$/i)){possibilities=fractionTransformer(match[1]+"/"+match[3]);}else if(match=text.match(/^([+-]?)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)\s*(?:\/\s*([+-]?\d+))?$/i)){possibilities=fractionTransformer(match[1]+"1/"+match[3]);}else if(text==="0"){possibilities=[{value:0,exact:true}];}else if(match=text.match(/^(.+)\s*\*?\s*(\\?pi|p|\u03c0|\\?tau|t|\u03c4|pau)$/i)){possibilities=forms.decimal(match[1]);}else {possibilities=_.reduce(KhanAnswerTypes.predicate.defaultForms.split(/\s*,\s*/),function(memo,form){return memo.concat(forms[form](text))},[]);let approximatesPi=false;const number=parseFloat(text);if(!isNaN(number)&&number!==parseInt(text)){const piMult=Math.PI/12;const roundedNumber=piMult*Math.round(number/piMult);if(Math.abs(number-roundedNumber)<.01){approximatesPi=true;}}else if(text.match(/\/\s*7/)){approximatesPi=true;}if(approximatesPi){_.each(possibilities,function(possibility){possibility.piApprox=true;});}return possibilities}let multiplier=Math.PI;if(text.match(/\\?tau|t|\u03c4/)){multiplier=Math.PI*2;}if(text.match(/pau/)){multiplier=Math.PI*1.5;}possibilities.forEach(possibility=>{possibility.value*=multiplier;});return possibilities},coefficient:function(text){let possibilities=[];text=text.replace(/\u2212/,"-");if(text===""){possibilities=[{value:1,exact:true}];}else if(text==="-"){possibilities=[{value:-1,exact:true}];}return possibilities},log:function(text){let match;let possibilities=[];text=text.replace(/\u2212/,"-");text=text.replace(/[ ()]/g,"");if(match=text.match(/^log\s*(\S+)\s*$/i)){possibilities=forms.decimal(match[1]);}else if(text==="0"){possibilities=[{value:0,exact:true}];}return possibilities},percent:function(text){text=String(text).trim();let hasPercentSign=false;if(text.indexOf("%")===text.length-1){text=text.substring(0,text.length-1).trim();hasPercentSign=true;}const transformed=forms.decimal(text);transformed.forEach(t=>{t.exact=hasPercentSign;t.value=t.value/100;});return transformed},mixed:function(text){const match=text.replace(/\u2212/,"-").replace(/([+-])\s+/g,"$1").match(/^([+-]?)(\d+)\s+(\d+)\s*\/\s*(\d+)$/);if(match){const sign=parseFloat(match[1]+"1");const integ=parseFloat(match[2]);const num=parseFloat(match[3]);const denom=parseFloat(match[4]);const simplified=num<denom&&KhanMath.getGCD(num,denom)===1;return [{value:sign*(integ+num/denom),exact:simplified}]}return []},decimal:function(text,precision=1e10){const normal=function(text){text=String(text).trim();const match=text.replace(/\u2212/,"-").replace(/([+-])\s+/g,"$1").match(/^([+-]?(?:\d{1,3}(?:[, ]?\d{3})*\.?|\d{0,3}(?:[, ]?\d{3})*\.(?:\d{3}[, ]?)*\d{1,3}))$/);const badLeadingZero=text.match(/^0[0,]*,/);if(match&&!badLeadingZero){let x=parseFloat(match[1].replace(/[, ]/g,""));if(options.inexact===undefined){x=Math.round(x*precision)/precision;}return x}};const commas=function(text){text=text.replace(/([.,])/g,function(_,c){return c==="."?",":"."});return normal(text)};const results=[{value:normal(text),exact:true}];if(options.decimal_separator===","){results.push({value:commas(text),exact:true});}return results}};return function(guess){const fallback=options.fallback!=null?""+options.fallback:"";guess=String(guess).trim()||fallback;const score={empty:guess==="",correct:false,message:null,guess:guess};const findCorrectAnswer=()=>{for(const form of acceptableForms){const transformed=forms[form](guess);for(let j=0,l=transformed.length;j<l;j++){const val=transformed[j].value;const exact=transformed[j].exact;const piApprox=transformed[j].piApprox;if(predicate(val,maxError)){if(exact||options.simplify==="optional"){score.correct=true;score.message=options.message||null;score.empty=false;}else if(form==="percent"){score.empty=true;score.message=ErrorCodes.MISSING_PERCENT_ERROR;}else {if(options.simplify!=="enforced"){score.empty=true;}score.message=ErrorCodes.NEEDS_TO_BE_SIMPLIFIED_ERROR;}return false}if(piApprox&&predicate(val,Math.abs(val*.001))){score.empty=true;score.message=ErrorCodes.APPROXIMATED_PI_ERROR;}}}};findCorrectAnswer();if(score.correct===false){let interpretedGuess=false;_.each(forms,function(form){const anyAreNaN=_.any(form(guess),function(t){return t.value!=null&&!_.isNaN(t.value)});if(anyAreNaN){interpretedGuess=true;}});if(!interpretedGuess){score.empty=true;score.message=ErrorCodes.EXTRA_SYMBOLS_ERROR;return score}}return score}}},number:{createValidatorFunctional:function(correctAnswer,options){const correctFloat=parseFloat(correctAnswer);return KhanAnswerTypes.predicate.createValidatorFunctional((guess,maxError)=>Math.abs(guess-correctFloat)<maxError,{...options})}},expression:{parseSolution:function(solutionString,options){let solution=KAS.parse(solutionString,options);if(!solution.parsed){throw new PerseusError("The provided solution ("+solutionString+") didn't parse.",Errors.InvalidInput)}else if(options.simplified&&!solution.expr.isSimplified()){throw new PerseusError("The provided solution ("+solutionString+") isn't fully expanded and simplified.",Errors.InvalidInput)}else {solution=solution.expr;}return solution},createValidatorFunctional:function(solution,options){return function(guess){const score={empty:false,correct:false,message:null,guess:guess,ungraded:false};if(!guess){score.empty=true;return score}const answer=KAS.parse(guess,options);if(!answer.parsed){score.empty=true;return score}if(typeof solution==="string"){solution=KhanAnswerTypes.expression.parseSolution(solution,options);}const result=KAS.compare(answer.expr,solution,options);if(result.equal){score.correct=true;}else if(result.message){score.message=result.message;}else {const answerX=KAS.parse(guess.replace(/[xX]/g,"*"),options);if(answerX.parsed){const resultX=KAS.compare(answerX.expr,solution,options);if(resultX.equal){score.ungraded=true;score.message=ErrorCodes.MULTIPLICATION_SIGN_ERROR;}else if(resultX.message){score.message=resultX.message+" Also, I'm a computer. I only understand "+"multiplication if you use an "+"asterisk (*) as the multiplication "+"sign.";}}}return score}}}};
10
10
 
@@ -26,7 +26,7 @@ function getCoefficientsByType(data){if(data.coords==null){return undefined}if(d
26
26
 
27
27
  function scoreIframe(userInput){if(userInput==null){return {type:"invalid",message:null}}if(userInput.status==="correct"){return {type:"points",earned:1,total:1,message:userInput.message||null}}if(userInput.status==="incorrect"){return {type:"points",earned:0,total:1,message:userInput.message||null}}return {type:"invalid",message:"Keep going, you're not there yet!"}}
28
28
 
29
- const{getAbsoluteValueCoefficients}=coefficients;function scoreAbsoluteValue(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const userCoeffs=getAbsoluteValueCoefficients(userInput.coords);const rubricCoeffs=getAbsoluteValueCoefficients(rubric.coords);const isCorrect=userCoeffs!==undefined&&rubricCoeffs!==undefined&&approximateDeepEqual(userCoeffs,rubricCoeffs);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
29
+ const{getAbsoluteValueCoefficients}=coefficients;function scoreAbsoluteValue(userInput,rubric){if(!userInput.coords||!rubric.coords){return {type:"invalid",message:null}}const userCoeffs=getAbsoluteValueCoefficients(userInput.coords);const rubricCoeffs=getAbsoluteValueCoefficients(rubric.coords);const isCorrect=approximateDeepEqual(userCoeffs,rubricCoeffs);return {type:"points",earned:isCorrect?1:0,total:1,message:null}}
30
30
 
31
31
  const{collinear: collinear$3,clockwise}=geometry;const{getClockwiseAngle}=angles;function scoreAngle(userInput,rubric){const coords=userInput.coords;const correct=rubric.coords;const allowReflexAngles=rubric.allowReflexAngles;if(!coords){return {type:"invalid",message:null}}const areClockwise=clockwise([coords[0],coords[2],coords[1]]);const shouldReverseCoords=areClockwise&&!allowReflexAngles;const guess=shouldReverseCoords?coords.slice().reverse():coords;let match;if(rubric.match==="congruent"){const guessAngle=getClockwiseAngle(guess,allowReflexAngles);const correctAngle=correct?getClockwiseAngle(correct,allowReflexAngles):null;match=correctAngle!==null&&approximateEqual(guessAngle,correctAngle);}else {match=correct!=null&&approximateDeepEqual(guess[1],correct[1])&&collinear$3(correct[1],correct[0],guess[0])&&collinear$3(correct[1],correct[2],guess[2]);}return {type:"points",earned:match?1:0,total:1,message:null}}
32
32
 
@@ -94,8 +94,6 @@ function validateTable(userInput){const supplied=filterNonEmpty(userInput);const
94
94
 
95
95
  function scoreTable(userInput,rubric){const validationResult=validateTable(userInput);if(validationResult!=null){return validationResult}const supplied=filterNonEmpty(userInput);const solution=filterNonEmpty(rubric.answers);if(supplied.length!==solution.length){return {type:"points",earned:0,total:1,message:null}}const createValidator=KhanAnswerTypes.number.createValidatorFunctional;let message=null;const allCorrect=solution.every(function(rowSolution){for(let i=0;i<supplied.length;i++){const rowSupplied=supplied[i];const correct=rowSupplied.every(function(cellSupplied,i){const cellSolution=rowSolution[i];const validator=createValidator(cellSolution,{simplify:true});const result=validator(cellSupplied);if(result.message){message=result.message;}return result.correct});if(correct){supplied.splice(i,1);return true}}return false});return {type:"points",earned:allCorrect?1:0,total:1,message}}
96
96
 
97
- const inputNumberAnswerTypes={number:{name:"Numbers",forms:"integer, decimal, proper, improper, mixed"},decimal:{name:"Decimals",forms:"decimal"},integer:{name:"Integers",forms:"integer"},rational:{name:"Fractions and mixed numbers",forms:"integer, proper, improper, mixed"},improper:{name:"Improper numbers (no mixed)",forms:"integer, proper, improper"},mixed:{name:"Mixed numbers (no improper)",forms:"integer, proper, mixed"},percent:{name:"Numbers or percents",forms:"integer, decimal, proper, improper, mixed, percent"},pi:{name:"Numbers with pi",forms:"pi"}};function scoreInputNumber(userInput,rubric,locale){return scoreNumericInput(userInput,convertInputNumberOptionsToNumericInput(rubric),locale)}
98
-
99
97
  const noScore={type:"points",earned:0,total:0,message:null};function combineScores(scoreA,scoreB){let message;if(scoreA.type==="points"&&scoreB.type==="points"){if(scoreA.message&&scoreB.message&&scoreA.message!==scoreB.message){message=null;}else {message=scoreA.message||scoreB.message;}return {type:"points",earned:scoreA.earned+scoreB.earned,total:scoreA.total+scoreB.total,message:message}}if(scoreA.type==="points"&&scoreB.type==="invalid"){return scoreB}if(scoreA.type==="invalid"&&scoreB.type==="points"){return scoreA}if(scoreA.type==="invalid"&&scoreB.type==="invalid"){if(scoreA.message&&scoreB.message&&scoreA.message!==scoreB.message){message=null;}else {message=scoreA.message||scoreB.message;}return {type:"invalid",message:message}}throw new PerseusError("PerseusScore with unknown type encountered",Errors.InvalidInput,{metadata:{scoreA:JSON.stringify(scoreA),scoreB:JSON.stringify(scoreB)}})}function flattenScores(widgetScoreMap){return Object.values(widgetScoreMap).reduce(combineScores,noScore)}
100
98
 
101
99
  function scoreDeprecatedStandin(userInput,rubric,locale){return {type:"points",earned:1,total:1,message:null}}
@@ -118,15 +116,15 @@ function validateMockWidget(userInput){if(userInput?.currentValue==null||userInp
118
116
 
119
117
  function scoreMockWidget(userInput,rubric){const validationResult=validateMockWidget(userInput);if(validationResult!=null){return validationResult}return {type:"points",earned:userInput?.currentValue===rubric.value?1:0,total:1,message:""}}
120
118
 
121
- const widgets=new Registry("Score widget registry");function registerWidget(type,scorer,validator){const logic={scorer,validator};widgets.set(type,logic);}const getWidgetValidator=type=>{return widgets.get(type)?.validator??null};const getWidgetScorer=type=>{return widgets.get(type)?.scorer??null};registerWidget("categorizer",scoreCategorizer,validateCategorizer);registerWidget("cs-program",scoreCSProgram);registerWidget("deprecated-standin",scoreDeprecatedStandin);registerWidget("dropdown",scoreDropdown,validateDropdown);registerWidget("expression",scoreExpression,validateExpression);registerWidget("free-response",scoreFreeResponse,validateFreeResponse);registerWidget("grapher",scoreGrapher);registerWidget("group",scoreGroup,validateGroup);registerWidget("iframe",scoreIframe);registerWidget("input-number",scoreInputNumber);registerWidget("interactive-graph",scoreInteractiveGraph);registerWidget("label-image",scoreLabelImage,validateLabelImage);registerWidget("matcher",scoreMatcher);registerWidget("matrix",scoreMatrix,validateMatrix);registerWidget("mock-widget",scoreMockWidget,scoreMockWidget);registerWidget("number-line",scoreNumberLine);registerWidget("numeric-input",scoreNumericInput);registerWidget("orderer",scoreOrderer,validateOrderer);registerWidget("plotter",scorePlotter,validatePlotter);registerWidget("radio",scoreRadio,validateRadio);registerWidget("sorter",scoreSorter,validateSorter);registerWidget("table",scoreTable,validateTable);
119
+ const widgets=new Registry("Score widget registry");function registerWidget(type,scorer,validator){const logic={scorer,validator};widgets.set(type,logic);}const getWidgetValidator=type=>{return widgets.get(type)?.validator??null};const getWidgetScorer=type=>{return widgets.get(type)?.scorer??null};registerWidget("categorizer",scoreCategorizer,validateCategorizer);registerWidget("cs-program",scoreCSProgram);registerWidget("deprecated-standin",scoreDeprecatedStandin);registerWidget("dropdown",scoreDropdown,validateDropdown);registerWidget("expression",scoreExpression,validateExpression);registerWidget("free-response",scoreFreeResponse,validateFreeResponse);registerWidget("grapher",scoreGrapher);registerWidget("group",scoreGroup,validateGroup);registerWidget("iframe",scoreIframe);registerWidget("input-number",scoreNumericInput);registerWidget("interactive-graph",scoreInteractiveGraph);registerWidget("label-image",scoreLabelImage,validateLabelImage);registerWidget("matcher",scoreMatcher);registerWidget("matrix",scoreMatrix,validateMatrix);registerWidget("mock-widget",scoreMockWidget,scoreMockWidget);registerWidget("number-line",scoreNumberLine);registerWidget("numeric-input",scoreNumericInput);registerWidget("orderer",scoreOrderer,validateOrderer);registerWidget("plotter",scorePlotter,validatePlotter);registerWidget("radio",scoreRadio,validateRadio);registerWidget("sorter",scoreSorter,validateSorter);registerWidget("table",scoreTable,validateTable);
122
120
 
123
121
  function isWidgetScoreable(widget){if(!widget){return false}const widgetIsGraded=widget.graded==null||widget.graded;const widgetIsStatic=!!widget.static;const widgetHasScorer=getWidgetScorer(widget.type)!==null;return widgetIsGraded&&!widgetIsStatic&&widgetHasScorer}
124
122
 
125
123
  function getScoreableWidgets(perseusRenderData){const usedWidgetIds=getWidgetIdsFromContent(perseusRenderData.content);return usedWidgetIds.filter(id=>isWidgetScoreable(perseusRenderData.widgets[id]))}
126
124
 
127
- function onlyInvalidScores(scores){return Object.fromEntries(Object.entries(scores).filter(([key,value])=>value.type==="invalid"))}function combineScoreWithWidgetScores(score,widgetScores){if(score.type==="invalid"){return {...score,widgetScores:onlyInvalidScores(widgetScores)}}else {return {...score,widgetScores}}}function scorePerseusItem(perseusRenderData,userInputMap,locale){const scoreableWidgetIds=getScoreableWidgets(perseusRenderData);const widgetScores=scoreWidgetsFunctional(perseusRenderData.widgets,scoreableWidgetIds,userInputMap,locale);return combineScoreWithWidgetScores(flattenScores(widgetScores),widgetScores)}function scorePerseusItemWithInputNumberAsNumericInput(perseusRenderData,userInputMap,locale){const scoreableWidgetIds=getScoreableWidgets(perseusRenderData);const scores=scoreWidgetsFunctionalWithInputNumberAsNumericInput(perseusRenderData.widgets,scoreableWidgetIds,userInputMap,locale);return combineScoreWithWidgetScores(flattenScores(scores),scores)}function scoreWidgetsFunctional(widgets,widgetIds,userInputMap,locale){const gradedWidgetIds=widgetIds.filter(id=>isWidgetScoreable(widgets[id]));const widgetScores={};gradedWidgetIds.forEach(id=>{const widget=widgets[id];const userInput=userInputMap[id];const validator=getWidgetValidator(widget.type);const scorer=getWidgetScorer(widget.type);const score=validator?.(userInput,widget.options,locale)??scorer?.(userInput,widget.options,locale);if(score!=null){widgetScores[id]=score;}});return widgetScores}function scoreWidgetsFunctionalWithInputNumberAsNumericInput(widgets,widgetIds,userInputMap,locale){const gradedWidgetIds=widgetIds.filter(id=>isWidgetScoreable(widgets[id]));const widgetScores={};gradedWidgetIds.forEach(id=>{const widget=widgets[id];const userInput=userInputMap[id];let widgetType;let widgetOptions;if(widget.type==="input-number"){widgetType="numeric-input";widgetOptions=convertInputNumberOptionsToNumericInput(widget.options);}else {widgetType=widget.type;widgetOptions=widget.options;}const validator=getWidgetValidator(widgetType);const scorer=getWidgetScorer(widgetType);const score=validator?.(userInput,widgetOptions,locale)??scorer?.(userInput,widgetOptions,locale);if(score!=null){widgetScores[id]=score;}});return widgetScores}
125
+ function onlyInvalidScores(scores){return Object.fromEntries(Object.entries(scores).filter(([key,value])=>value.type==="invalid"))}function combineScoreWithWidgetScores(score,widgetScores){if(score.type==="invalid"){return {...score,widgetScores:onlyInvalidScores(widgetScores)}}else {return {...score,widgetScores}}}function scorePerseusItem(perseusRenderData,userInputMap,locale){const scoreableWidgetIds=getScoreableWidgets(perseusRenderData);const widgetScores=scoreWidgetsFunctional(perseusRenderData.widgets,scoreableWidgetIds,userInputMap,locale);return combineScoreWithWidgetScores(flattenScores(widgetScores),widgetScores)}function scoreWidgetsFunctional(widgets,widgetIds,userInputMap,locale){const gradedWidgetIds=widgetIds.filter(id=>isWidgetScoreable(widgets[id]));const widgetScores={};gradedWidgetIds.forEach(id=>{const widget=widgets[id];const userInput=userInputMap[id];const validator=getWidgetValidator(widget.type);const scorer=getWidgetScorer(widget.type);const score=validator?.(userInput,widget.options,locale)??scorer?.(userInput,widget.options,locale);if(score!=null){widgetScores[id]=score;}});return widgetScores}
128
126
 
129
127
  function hasEmptyDINERWidgets(itemData,userInputMap){const usedWidgetIds=getWidgetIdsFromContent(itemData.question.content);const widgets=itemData.question.widgets;for(const widgetId of usedWidgetIds){const widget=widgets[widgetId];const input=userInputMap[widgetId];switch(widget.type){case "dropdown":{if(input.value===0){return true}break}case "interactive-graph":{break}case "numeric-input":{if(!input.currentValue&&!widget.options.coefficient){return true}break}case "expression":{if(!input){return true}break}case "radio":{if(input.selectedChoiceIds.length===0){return true}break}}}return false}
130
128
 
131
- export { KhanAnswerTypes, emptyWidgetsFunctional, flattenScores, getWidgetScorer, getWidgetValidator, hasEmptyDINERWidgets, inputNumberAnswerTypes, isWidgetScoreable, libVersion, registerWidget, scoreCSProgram, scoreCategorizer, scoreDropdown, scoreExpression, scoreGrapher, scoreIframe, scoreInputNumber, scoreInteractiveGraph, scoreLabelImage, scoreLabelImageMarker, scoreMatcher, scoreMatrix, scoreNumberLine, scoreNumericInput, scoreOrderer, scorePerseusItem, scorePerseusItemWithInputNumberAsNumericInput, scorePlotter, scoreRadio, scoreSorter, scoreTable, scoreWidgetsFunctional, validateCategorizer, validateDropdown, validateExpression, validateMatrix, validateOrderer, validatePlotter, validateRadio, validateSorter, validateTable, validateUserInput };
129
+ export { KhanAnswerTypes, emptyWidgetsFunctional, flattenScores, getWidgetScorer, getWidgetValidator, hasEmptyDINERWidgets, isWidgetScoreable, libVersion, registerWidget, scoreCSProgram, scoreCategorizer, scoreDropdown, scoreExpression, scoreGrapher, scoreIframe, scoreNumericInput as scoreInputNumber, scoreInteractiveGraph, scoreLabelImage, scoreLabelImageMarker, scoreMatcher, scoreMatrix, scoreNumberLine, scoreNumericInput, scoreOrderer, scorePerseusItem, scorePlotter, scoreRadio, scoreSorter, scoreTable, scoreWidgetsFunctional, validateCategorizer, validateDropdown, validateExpression, validateMatrix, validateOrderer, validatePlotter, validateRadio, validateSorter, validateTable, validateUserInput };
132
130
  //# sourceMappingURL=index.js.map