@khanacademy/perseus 87.0.0 → 87.1.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.
Files changed (28) hide show
  1. package/dist/components/drag-and-drop/answer-tile/answer-tile.d.ts +88 -0
  2. package/dist/components/drag-and-drop/answer-tile/index.d.ts +2 -0
  3. package/dist/components/drag-and-drop/blank/blank-component.d.ts +41 -0
  4. package/dist/components/drag-and-drop/blank/index.d.ts +2 -0
  5. package/dist/components/drag-and-drop/choice-bank.d.ts +10 -3
  6. package/dist/components/drag-and-drop/dnd-action-menu/dnd-action-menu.d.ts +39 -0
  7. package/dist/components/drag-and-drop/dnd-action-menu/index.d.ts +2 -0
  8. package/dist/components/drag-and-drop/dnd-action-menu/merged-ref-opener.d.ts +21 -0
  9. package/dist/components/drag-and-drop/drag-ids.d.ts +23 -0
  10. package/dist/components/drag-and-drop/perseus-dnd-provider.d.ts +34 -0
  11. package/dist/components/drag-and-drop/temp-strings.d.ts +36 -0
  12. package/dist/components/drag-and-drop/tile-placements.d.ts +33 -0
  13. package/dist/components/drag-and-drop/use-tile-move-actions.d.ts +28 -0
  14. package/dist/es/index.css +1 -1
  15. package/dist/es/index.css.map +1 -1
  16. package/dist/es/index.js +16 -11
  17. package/dist/es/index.js.map +1 -1
  18. package/dist/index.css +1 -1
  19. package/dist/index.css.map +1 -1
  20. package/dist/index.js +18 -13
  21. package/dist/index.js.map +1 -1
  22. package/dist/widgets/categorizer/categorizer.d.ts +3 -5
  23. package/dist/widgets/dropdown/dropdown.d.ts +3 -4
  24. package/dist/widgets/graded-group-set/graded-group-set.d.ts +2 -3
  25. package/dist/widgets/matcher/matcher.d.ts +3 -4
  26. package/dist/widgets/number-line/number-line.d.ts +6 -4
  27. package/dist/widgets/python-program/python-program.d.ts +3 -4
  28. package/package.json +31 -30
package/dist/es/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as React from 'react';
2
- import React__default, { useContext, forwardRef, useId, useRef, useEffect, useCallback, useImperativeHandle, useState, useMemo, createContext, useLayoutEffect, createElement } from 'react';
2
+ import React__default, { useContext, useMemo, forwardRef, useId, useRef, useEffect, useCallback, useImperativeHandle, useState, createContext, useLayoutEffect, createElement } from 'react';
3
3
  import { KeypadContext } from '@khanacademy/keypad-context';
4
4
  import { MathInputI18nContext, DesktopKeypad, getCursorContext, CursorContext, getKeyTranslator, createMathField, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, keypadElementPropType } from '@khanacademy/math-input';
5
5
  import { View, RenderStateRoot, useOnMountEffect, useLatestRef, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
@@ -37,6 +37,7 @@ import caretRightIcon from '@phosphor-icons/core/regular/caret-right.svg';
37
37
  import { PhosphorIcon } from '@khanacademy/wonder-blocks-icon';
38
38
  import checkIcon from '@phosphor-icons/core/bold/check-bold.svg';
39
39
  import minusIcon from '@phosphor-icons/core/bold/minus-circle-bold.svg';
40
+ import { useDroppable, useDragOperation } from '@dnd-kit/react';
40
41
  import Banner from '@khanacademy/wonder-blocks-banner';
41
42
  import { OptionItem, SingleSelect, MultiSelect } from '@khanacademy/wonder-blocks-dropdown';
42
43
  import Button from '@khanacademy/wonder-blocks-button';
@@ -1408,7 +1409,7 @@ var jsxRuntimeExports = requireJsxRuntime();
1408
1409
 
1409
1410
  const mockStrings={characterCount:({used,num})=>num===1?`${used} / ${num} Character`:`${used} / ${num} Characters`,closeKeypad:"close math keypad",openKeypad:"open math keypad",mathInputBox:"Math input box",removeHighlight:"Remove highlight",addHighlight:"Add highlight",hintPos:({pos})=>`Hint #${pos}`,errorRendering:({error})=>`Error rendering: ${error}`,APPROXIMATED_PI_ERROR:"Your answer is close, but you may "+"have approximated pi. Enter your "+"answer as a multiple of pi, like "+"<code>12\\ \\text{pi}</code> or "+"<code>2/3\\ \\text{pi}</code>",EMPTY_RESPONSE_ERROR:"There are still more parts of this question to answer.",EXTRA_SYMBOLS_ERROR:"We could not understand your "+"answer. Please check your answer for extra "+"text or symbols.",NEEDS_TO_BE_SIMPLFIED_ERROR:"Your answer is almost correct, "+"but it needs to be simplified.",MISSING_PERCENT_ERROR:"Your answer is almost correct, "+"but it is missing a "+"<code>\\%</code> at the end.",MULTIPLICATION_SIGN_ERROR:"I'm a computer. I only understand "+"multiplication if you use an asterisk "+"(*) as the multiplication sign.",USER_INPUT_EMPTY:"Your answer is empty.",USER_INPUT_TOO_LONG:"Please shorten your response.",WRONG_CASE_ERROR:"Your answer includes use of a variable with the wrong case.",WRONG_LETTER_ERROR:"Your answer includes a wrong variable letter.",invalidSelection:"Make sure you select something for every row.",INVALID_CHOICE_SELECTION:"Invalid choice selection",ERROR_TITLE:"Oops!",ERROR_MESSAGE:"Sorry, I don't understand that!",hints:"Hints",getAnotherHint:"Get another hint",deprecatedStandin:"Sorry, this part of the question is no longer available. 😅 Don't worry, you won't be graded on this part. Keep going!",keepTrying:"Keep trying",tryAgain:"Try again",check:"Check",correctExcited:"Correct!",nextQuestion:"Next question",skipToTitle:({title})=>`Skip to ${title}`,current:"Current",correct:"Correct",correctSelected:"Correct (selected)",incorrect:"Incorrect",incorrectSelected:"Incorrect (selected)",hideExplanation:"Hide explanation",explain:"Explain",INVALID_MESSAGE_PREFIX:"We couldn't grade your answer.",DEFAULT_INVALID_MESSAGE_1:"It looks like you left something blank or ",DEFAULT_INVALID_MESSAGE_2:"entered in an invalid answer.",integerExample:"an integer (like $-3$ or $45$)",properExample:"a proper fraction (like $1/3$ or $12/13$)",simplifiedProperExample:"a simplified proper fraction (like $1/2$ or $3/4$)",improperExample:"an improper fraction (like $3/2$ or $17/15$)",simplifiedImproperExample:"a simplified improper fraction (like $5/4$ or $9/8$)",mixedExample:"a mixed number (like $1\\ 3/4$ or $2\\ 5/6$)",decimalExample:"an exact decimal (like $-0.85$ or $3.14$)",percentExample:"a percent (like $6\\%$ or $12.34\\%$)",piExample:"a multiple of pi (like $2/3\\ \\text{pi}$ or $5\\ \\text{pi}$)",yourAnswer:"**Your answer should be** ",yourAnswerCanBe:"**Your answer can be** ",yourAnswerLabel:"Your answer:",addPoints:"Click to add points",addVertices:"Click to add vertices",tapMultiple:"Tap each dot on the image to select all answers that apply.",tapSingle:"Tap each dot on the image to select an answer.",clickMultiple:"Click each dot on the image to select all answers that apply.",clickSingle:"Click each dot on the image to select an answer.",choices:"Choices:",answers:({num})=>num===1?`${num} answer`:`${num} answers`,hideAnswersToggleLabel:"Hide answer choices",moves:({num})=>num===1?`Moves: ${num}`:`Moves: ${num}`,clickTiles:"Click on the tiles to change the lights.",turnOffLights:"You must turn on all of the lights to continue.",fillAllCells:"Make sure you fill in all cells in the matrix.",molecularDrawing:({content})=>`A molecular structure drawing. SMILES notation: ${content}`,switchDirection:"Switch direction",circleOpen:"Make circle open",circleFilled:"Make circle filled",numDivisions:"Number of divisions:",divisions:({divRangeString})=>`Please make sure the number of divisions is in the range ${divRangeString}.`,dragHandles:"Drag handles to make graph",tapAddPoints:"Tap to add points",false:"False",true:"True",no:"No",yes:"Yes",chooseCorrectNum:"Please choose the correct number of answers.",notNoneOfTheAbove:"'None of the above' may not be selected when other answers are selected.",noneOfTheAbove:"None of the above",chooseNumAnswers:({numCorrect})=>`Choose ${numCorrect} answers:`,chooseAllAnswers:"Choose all answers that apply:",chooseOneAnswer:"Choose 1 answer:",choiceCheckedCorrect:({letter})=>`(Choice ${letter}, Checked, Correct)`,choiceCorrect:({letter})=>`(Choice ${letter}, Correct)`,choiceCheckedIncorrect:({letter})=>`(Choice ${letter}, Checked, Incorrect)`,choiceIncorrect:({letter})=>`(Choice ${letter}, Incorrect)`,choiceChecked:({letter})=>`(Choice ${letter}, Checked)`,choice:({letter})=>`(Choice ${letter})`,notSelected:"not selected",choicesSelected:({num})=>num===1?`${num} choice selected`:`${num} choices selected`,bringBack:"Bring back",openMenuForChoice:({letter})=>`Open menu for Choice ${letter}`,letters:"A B C D E F G H I J K L M N O P Q R S T U V W X Y Z",scrollAnswers:"Scroll Answers",scrollStart:"Scroll to view start of the content",scrollEnd:"Scroll to view the end of the content",rightArrow:"Reaction arrow pointing to the right.",dontUnderstandUnits:"I couldn't understand those units.",checkSigFigs:"Check your significant figures.",answerNumericallyIncorrect:"That answer is numerically incorrect.",checkUnits:"Check your units.",dontUnderstand:"I don't understand that",loading:"Loading...",videoTranscript:"See video transcript",somethingWrong:"Something went wrong.",videoWrapper:"Khan Academy video wrapper",pythonProgram:"Python program",computerScienceProgram:"Computer science program",embeddedContent:"Embedded content",mathInputTitle:"mathematics keyboard",mathInputDescription:"Use keyboard/mouse to interact with math-based input fields",sin:"sin",cos:"cos",tan:"tan",simulationLoadFail:"Sorry, this simulation cannot load.",simulationLocaleWarning:"Sorry, this simulation isn't available in your language.",openInNewTab:"Open simulation in a new tab",fullscreen:"Fullscreen",exitFullscreen:"Exit fullscreen",selectAnAnswer:"Select an answer",srGraphInstructions:"Enable Forms or Focus mode and use the Tab key to move through the interactive elements in the graph. When an interactive element has focus, use Arrow keys to move it.",srUnlimitedGraphInstructions:"Press Shift + Enter to interact with the graph. Enable Forms or Focus mode and use the Tab key to move through the interactive elements in the graph and access the graph Action Bar. When an interactive element has focus, use Arrow keys to move it or use the Delete key to remove it from the graph. Use the buttons in the Action Bar to add or adjust elements within the graph.",graphKeyboardPrompt:"Press Shift + Enter to interact with the graph",addPoint:"Add Point",removePoint:"Remove Point",closePolygon:"Close shape",openPolygon:"Re-open shape",ungradedInteractiveGraph:"Use this graph to check your thinking, but it does not count as your answer.",srPointAtCoordinates:({pointLabel,x,y})=>`Point ${pointLabel} at ${x} comma ${y}.`,srInteractiveElements:({elements})=>`Interactive elements: ${elements}`,srNoInteractiveElements:"No interactive elements",srCircleGraph:"A circle on a coordinate plane.",srCircleShape:({centerX,centerY})=>`Circle. The center point is at ${centerX} comma ${centerY}.`,srCircleRadiusPointRight:({radiusPointX,radiusPointY})=>`Right radius endpoint at ${radiusPointX} comma ${radiusPointY}.`,srCircleRadiusPointLeft:({radiusPointX,radiusPointY})=>`Left radius endpoint at ${radiusPointX} comma ${radiusPointY}.`,srCircleRadiusPointRightWithLabel:({pointLabel,radiusPointX,radiusPointY})=>`Right radius endpoint ${pointLabel} at ${radiusPointX} comma ${radiusPointY}.`,srCircleRadiusPointLeftWithLabel:({pointLabel,radiusPointX,radiusPointY})=>`Left radius endpoint ${pointLabel} at ${radiusPointX} comma ${radiusPointY}.`,srCircleRadius:({radius})=>`Circle radius is ${radius}.`,srCircleOuterPoints:({point1X,point1Y,point2X,point2Y,point3X,point3Y,point4X,point4Y})=>`Points on the circle at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, ${point3X} comma ${point3Y}, ${point4X} comma ${point4Y}.`,srLinearGraph:"A line on a coordinate plane.",srLinearGraphPoints:({point1X,point1Y,point2X,point2Y})=>`The line has two points, point 1 at ${point1X} comma ${point1Y} and point 2 at ${point2X} comma ${point2Y}.`,srLinearGraphSlopeIncreasing:"Its slope increases from left to right.",srLinearGraphSlopeDecreasing:"Its slope decreases from left to right.",srLinearGraphSlopeHorizontal:"Its slope is zero.",srLinearGraphSlopeVertical:"Its slope is undefined.",srLinearGraphXOnlyIntercept:({xIntercept})=>`The line crosses the X-axis at ${xIntercept} comma 0.`,srLinearGraphYOnlyIntercept:({yIntercept})=>`The line crosses the Y-axis at 0 comma ${yIntercept}.`,srLinearGraphBothIntercepts:({xIntercept,yIntercept})=>`The line crosses the X-axis at ${xIntercept} comma 0 and the Y-axis at 0 comma ${yIntercept}.`,srLinearGraphOriginIntercept:"The line crosses the X and Y axes at the graph's origin.",srLinearGrabHandle:({point1X,point1Y,point2X,point2Y})=>`Line going through point ${point1X} comma ${point1Y} and point ${point2X} comma ${point2Y}.`,srAngleStartingSide:({pointLabel,x,y})=>`Point ${pointLabel}, starting side at ${x} comma ${y}.`,srAngleEndingSide:({pointLabel,x,y})=>`Point ${pointLabel}, ending side at ${x} comma ${y}.`,srAngleVertexWithAngleMeasure:({pointLabel,x,y,angleMeasure})=>`Point ${pointLabel}, vertex at ${x} comma ${y}. Angle ${angleMeasure} degrees.`,srAngleGraphAriaLabel:"An angle on a coordinate plane.",srAngleGraphAriaDescription:({angleMeasure,vertexX,vertexY,startingSideX,startingSideY,endingSideX,endingSideY})=>`The angle measure is ${angleMeasure} degrees with a vertex at ${vertexX} comma ${vertexY}, a point on the starting side at ${startingSideX} comma ${startingSideY} and a point on the ending side at ${endingSideX} comma ${endingSideY}.`,srAngleInteractiveElements:({vertexX,vertexY,startingSideX,startingSideY,endingSideX,endingSideY})=>`An angle formed by 3 points. The vertex is at ${vertexX} comma ${vertexY}. The starting side point is at ${startingSideX} comma ${startingSideY}. The ending side point is at ${endingSideX} comma ${endingSideY}.`,srSingleSegmentGraphAriaLabel:"A line segment on a coordinate plane.",srMultipleSegmentGraphAriaLabel:({countOfSegments})=>`${countOfSegments} segments on a coordinate plane.`,srMultipleSegmentIndividualLabel:({point1X,point1Y,point2X,point2Y,indexOfSegment})=>`Segment ${indexOfSegment}: Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`,srSingleSegmentLabel:({point1X,point1Y,point2X,point2Y})=>`Endpoint 1 at ${point1X} comma ${point1Y}. Endpoint 2 at ${point2X} comma ${point2Y}.`,srSegmentLength:({length})=>`Segment length ${length} units.`,srSingleSegmentGraphEndpointAriaLabel:({pointLabel,x,y})=>`Endpoint ${pointLabel} at ${x} comma ${y}.`,srMultipleSegmentGraphEndpointAriaLabel:({pointLabel,x,y,indexOfSegment})=>`Endpoint ${pointLabel} on segment ${indexOfSegment} at ${x} comma ${y}.`,srSegmentGrabHandle:({point1X,point1Y,point2X,point2Y})=>`Segment from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`,srLinearSystemGraph:"Two lines on a coordinate plane.",srLinearSystemPoints:({lineNumber,point1X,point1Y,point2X,point2Y})=>`Line ${lineNumber} has two points, point 1 at ${point1X} comma ${point1Y} and point 2 at ${point2X} comma ${point2Y}.`,srLinearSystemPoint:({lineNumber,pointLabel,x,y})=>`Point ${pointLabel} on line ${lineNumber} at ${x} comma ${y}.`,srLinearSystemGrabHandle:({lineNumber,point1X,point1Y,point2X,point2Y})=>`Line ${lineNumber} going through point ${point1X} comma ${point1Y} and point ${point2X} comma ${point2Y}.`,srLinearSystemIntersection:({x,y})=>`Line 1 and line 2 intersect at point ${x} comma ${y}.`,srLinearSystemParallel:"Line 1 and line 2 are parallel.",srRayGraph:"A ray on a coordinate plane.",srRayPoints:({point1X,point1Y,point2X,point2Y})=>`The endpoint is at ${point1X} comma ${point1Y} and the ray goes through point ${point2X} comma ${point2Y}.`,srRayGrabHandle:({point1X,point1Y,point2X,point2Y})=>`Ray with endpoint ${point1X} comma ${point1Y} going through point ${point2X} comma ${point2Y}.`,srRayEndpoint:({x,y})=>`Endpoint at ${x} comma ${y}.`,srRayEndpointWithLabel:({pointLabel,x,y})=>`Endpoint ${pointLabel} at ${x} comma ${y}.`,srRayTerminalPoint:({x,y})=>`Through point at ${x} comma ${y}.`,srRayTerminalPointWithLabel:({pointLabel,x,y})=>`Through point ${pointLabel} at ${x} comma ${y}.`,srVectorGraph:"A vector on a coordinate plane.",srVectorPoints:({tailX,tailY,headX,headY})=>`The tail is at ${tailX} comma ${tailY} and the head is at ${headX} comma ${headY}.`,srVectorHeadPoint:({x,y})=>`Vector head at ${x} comma ${y}.`,srVectorMagnitudeDirection:({magnitude,direction})=>`The vector has a magnitude of ${magnitude} and a direction of ${direction} degrees.`,srVectorGrabHandle:({tailX,tailY,headX,headY})=>`Vector from ${tailX} comma ${tailY} to ${headX} comma ${headY}.`,srQuadraticGraph:"A parabola on a 4-quadrant coordinate plane.",srQuadraticFaceUp:"The parabola opens upward.",srQuadraticFaceDown:"The parabola opens downward.",srQuadraticGraphVertexOrigin:"Vertex is at the origin.",srQuadraticGraphVertexXAxis:"Vertex is on the X-axis.",srQuadraticGraphVertexYAxis:"Vertex is on the Y-axis.",srQuadraticGraphVertexQuadrant:({quadrant})=>`Vertex is in quadrant ${quadrant}.`,srQuadraticTwoXIntercepts:({intercept1,intercept2})=>`The X-intercepts are at ${intercept1} comma 0 and ${intercept2} comma 0.`,srQuadraticOneXIntercept:({intercept})=>`The X-intercept is at ${intercept} comma 0.`,srQuadraticYIntercept:({intercept})=>`The Y-intercept is at 0 comma ${intercept}.`,srQuadraticPointOrigin:({pointLabel})=>`Point ${pointLabel} on parabola at the origin.`,srQuadraticPointAxis:({pointLabel,x,y})=>`Point ${pointLabel} on parabola at ${x} comma ${y}.`,srQuadraticPointQuadrant:({pointLabel,x,y,quadrant})=>`Point ${pointLabel} on parabola in quadrant ${quadrant} at ${x} comma ${y}.`,srQuadraticInteractiveElements:({point1X,point1Y,point2X,point2Y,point3X,point3Y})=>`Parabola with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and ${point3X} comma ${point3Y}.`,srPolygonGraph:"A polygon.",srPolygonGraphCoordinatePlane:"A polygon on a coordinate plane.",srPolygonGraphPointsNum:({num})=>`The polygon has ${num} points.`,srPolygonGraphPointsOne:"The polygon has 1 point.",srPolygonElementsNum:({num})=>`A polygon with ${num} points.`,srPolygonElementsOne:"A polygon with 1 point.",srPolygonPointAngleApprox:({angle})=>`Angle approximately equal to ${angle} degrees.`,srPolygonPointAngle:({angle})=>`Angle equal to ${angle} degrees.`,srPolygonSideLength:({pointNum,length})=>`A line segment, length equal to ${length} units, connects to point ${pointNum}.`,srPolygonSideLengthApprox:({pointNum,length})=>`A line segment, length approximately equal to ${length} units, connects to point ${pointNum}.`,srUnlimitedPolygonEmpty:"An empty coordinate plane.",srSinusoidGraph:"A sinusoid function on a coordinate plane.",srSinusoidRootPoint:({x,y})=>`Midline intersection at ${x} comma ${y}.`,srSinusoidRootPointWithLabel:({pointLabel,x,y})=>`Midline intersection point ${pointLabel} at ${x} comma ${y}.`,srSinusoidMaxPoint:({x,y})=>`Maximum point at ${x} comma ${y}.`,srSinusoidMaxPointWithLabel:({pointLabel,x,y})=>`Maximum point ${pointLabel} at ${x} comma ${y}.`,srSinusoidMinPoint:({x,y})=>`Minimum point at ${x} comma ${y}.`,srSinusoidMinPointWithLabel:({pointLabel,x,y})=>`Minimum point ${pointLabel} at ${x} comma ${y}.`,srSinusoidFlatPoint:({x,y})=>`Line through point at ${x} comma ${y}.`,srSinusoidFlatPointWithLabel:({pointLabel,x,y})=>`Line through point ${pointLabel} at ${x} comma ${y}.`,srSinusoidDescription:({minValue,maxValue,cycleStart,cycleEnd})=>`The graph shows a wave with a minimum value of ${minValue} and a maximum value of ${maxValue}. The wave completes a full cycle from ${cycleStart} to ${cycleEnd}.`,srSinusoidInteractiveElements:({point1X,point1Y,point2X,point2Y})=>`Sinusoid graph with midline intersection point at ${point1X} comma ${point1Y} and extremum point at ${point2X} comma ${point2Y}.`,srExponentialGraph:"An exponential curve on a coordinate plane.",srExponentialPoint:({pointLabel,x,y})=>`Point ${pointLabel} on an exponential curve at ${x} comma ${y}.`,srExponentialDescriptionRightNeg:({point1X,point1Y,point2X,point2Y,asymptoteY})=>`The curve passes through ${point1X} comma ${point1Y} and ${point2X} comma ${point2Y} as the curve approaches y equals ${asymptoteY} from the right and extends to negative infinity.`,srExponentialDescriptionLeftPos:({point1X,point1Y,point2X,point2Y,asymptoteY})=>`The curve passes through ${point1X} comma ${point1Y} and ${point2X} comma ${point2Y} as the curve approaches y equals ${asymptoteY} from the left and extends to positive infinity.`,srExponentialAboveAsymptote:"The curve lies above the asymptote.",srExponentialBelowAsymptote:"The curve lies below the asymptote.",srExponentialNoCurve:({point1X,point1Y,point2X,point2Y,asymptoteY})=>`No exponential curve can be drawn through ${point1X} comma ${point1Y} and ${point2X} comma ${point2Y} with a horizontal asymptote at y equals ${asymptoteY}. Move both points to the same side of the asymptote to draw the curve.`,srExponentialIntercepts:({xIntercept,yIntercept})=>`The x-intercept is at ${xIntercept} comma 0. The y-intercept is at 0 comma ${yIntercept}.`,srExponentialYIntercept:({yIntercept})=>`The y-intercept is at 0 comma ${yIntercept}.`,srExponentialInteractiveElements:({point1X,point1Y,point2X,point2Y,asymptoteY})=>`Exponential graph with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and a horizontal asymptote at y equals ${asymptoteY}.`,srExponentialAsymptote:({asymptoteY})=>`Horizontal asymptote at y equals ${asymptoteY}`,srLogarithmGraph:"A logarithmic curve on a coordinate plane.",srLogarithmPoint:({pointLabel,x,y})=>`Point ${pointLabel} on a logarithmic curve at ${x} comma ${y}.`,srLogarithmDescriptionRightNeg:({point1X,point1Y,point2X,point2Y,asymptoteX})=>`The curve passes through ${point1X} comma ${point1Y} and ${point2X} comma ${point2Y} as the curve approaches x equals ${asymptoteX} from the right and extends to negative infinity.`,srLogarithmDescriptionRightPos:({point1X,point1Y,point2X,point2Y,asymptoteX})=>`The curve passes through ${point1X} comma ${point1Y} and ${point2X} comma ${point2Y} as the curve approaches x equals ${asymptoteX} from the right and extends to positive infinity.`,srLogarithmDescriptionLeftNeg:({point1X,point1Y,point2X,point2Y,asymptoteX})=>`The curve passes through ${point1X} comma ${point1Y} and ${point2X} comma ${point2Y} as the curve approaches x equals ${asymptoteX} from the left and extends to negative infinity.`,srLogarithmDescriptionLeftPos:({point1X,point1Y,point2X,point2Y,asymptoteX})=>`The curve passes through ${point1X} comma ${point1Y} and ${point2X} comma ${point2Y} as the curve approaches x equals ${asymptoteX} from the left and extends to positive infinity.`,srLogarithmToRightOfAsymptote:"The curve is to the right of the asymptote.",srLogarithmToLeftOfAsymptote:"The curve is to the left of the asymptote.",srLogarithmNoCurve:({point1X,point1Y,point2X,point2Y,asymptoteX})=>`No curve can be drawn through ${point1X} comma ${point1Y} and ${point2X} comma ${point2Y} with a vertical asymptote at x equals ${asymptoteX}. Move both points to the same side of the asymptote to draw the curve.`,srLogarithmIntercepts:({xIntercept,yIntercept})=>`The x-intercept is at ${xIntercept} comma 0. The y-intercept is at 0 comma ${yIntercept}.`,srLogarithmXIntercept:({xIntercept})=>`The x-intercept is at ${xIntercept} comma 0.`,srLogarithmInteractiveElements:({point1X,point1Y,point2X,point2Y,asymptoteX})=>`Logarithmic graph with points at ${point1X} comma ${point1Y}, ${point2X} comma ${point2Y}, and a vertical asymptote at x equals ${asymptoteX}.`,srLogarithmAsymptote:({asymptoteX})=>`Vertical asymptote at x equals ${asymptoteX}.`,srAbsoluteValueGraph:"An absolute value on a coordinate plane.",srAbsoluteValueVertexPoint:({x,y})=>`Vertex point at ${x} comma ${y}.`,srAbsoluteValueVertexPointWithLabel:({pointLabel,x,y})=>`Vertex point ${pointLabel} at ${x} comma ${y}.`,srAbsoluteValueArmPoint:({x,y})=>`Point on arm at ${x} comma ${y}.`,srAbsoluteValueArmPointWithLabel:({pointLabel,x,y})=>`Point ${pointLabel} on arm at ${x} comma ${y}.`,srAbsoluteValueSlope:({slope})=>`The slope is ${slope}.`,srAbsoluteValueOpensUp:"The graph opens upward.",srAbsoluteValueOpensDown:"The graph opens downward.",srAbsoluteValueVertex:({x,y})=>`Vertex is at ${x} comma ${y}.`,srAbsoluteValueVertexOrigin:"Vertex is at the origin.",srAbsoluteValueTwoXIntercepts:({intercept1,intercept2})=>`The X-intercepts are at ${intercept1} comma 0 and ${intercept2} comma 0.`,srAbsoluteValueOneXIntercept:({intercept})=>`The X-intercept is at ${intercept} comma 0.`,srAbsoluteValueYIntercept:({intercept})=>`The Y-intercept is at 0 comma ${intercept}.`,srAbsoluteValueInteractiveElements:({point1X,point1Y,point2X,point2Y,slope})=>`Absolute value graph with vertex point at ${point1X} comma ${point1Y}, arm point at ${point2X} comma ${point2Y} and slope of ${slope}.`,srTangentGraph:"A tangent curve on a coordinate plane.",srTangentInflectionPoint:({x,y})=>`Inflection point at ${x} comma ${y}.`,srTangentInflectionPointWithLabel:({pointLabel,x,y})=>`Inflection point ${pointLabel} at ${x} comma ${y}.`,srTangentControlPoint:({x,y})=>`Control point at ${x} comma ${y}.`,srTangentControlPointWithLabel:({pointLabel,x,y})=>`Control point ${pointLabel} at ${x} comma ${y}.`,srTangentDescriptionPoints:({inflectionX,inflectionY,controlX,controlY})=>`The curve passes through an inflection point at ${inflectionX} comma ${inflectionY} and a control point at ${controlX} comma ${controlY}.`,srTangentIncreasing:({period})=>`The curve increases through the inflection point, repeating every ${period} units.`,srTangentDecreasing:({period})=>`The curve decreases through the inflection point, repeating every ${period} units.`,srTangentAsymptotes:({leftAsymptote,rightAsymptote})=>`The nearest vertical asymptotes are at x equals ${leftAsymptote} and x equals ${rightAsymptote}.`,srTangentInteractiveElements:({point1X,point1Y,point2X,point2Y})=>`Tangent graph with inflection point at ${point1X} comma ${point1Y} and control point at ${point2X} comma ${point2Y}.`,imageExploreButton:"Explore image",imageExploreButtonAriaLabel:"Explore image and description",imageAlternativeTitle:"Explore image and description",imageDescriptionLabel:"Description",imageZoomAriaLabel:"Make image bigger.",imageResetZoomAriaLabel:"Close image.",gifPlayButtonLabel:"Play Animation",gifPauseButtonLabel:"Pause Animation",definitionIdentifier:({word})=>`Definition of: ${word}`};const errorToString={APPROXIMATED_PI_ERROR:"APPROXIMATED_PI_ERROR",CHOOSE_CORRECT_NUM_ERROR:"chooseCorrectNum",EMPTY_RESPONSE_ERROR:"EMPTY_RESPONSE_ERROR",EXTRA_SYMBOLS_ERROR:"EXTRA_SYMBOLS_ERROR",FILL_ALL_CELLS_ERROR:"fillAllCells",INVALID_CHOICE_SELECTION:"INVALID_CHOICE_SELECTION",INVALID_SELECTION_ERROR:"invalidSelection",MISSING_PERCENT_ERROR:"MISSING_PERCENT_ERROR",MULTIPLICATION_SIGN_ERROR:"MULTIPLICATION_SIGN_ERROR",NEEDS_TO_BE_SIMPLIFIED_ERROR:"NEEDS_TO_BE_SIMPLFIED_ERROR",NOT_NONE_ABOVE_ERROR:"notNoneOfTheAbove",USER_INPUT_EMPTY:"USER_INPUT_EMPTY",USER_INPUT_TOO_LONG:"USER_INPUT_TOO_LONG",WRONG_CASE_ERROR:"WRONG_CASE_ERROR",WRONG_LETTER_ERROR:"WRONG_LETTER_ERROR"};function mapErrorToString(err,translatedStrings){if(!err){return err}return translatedStrings[errorToString[err]]||err}
1410
1411
 
1411
- const mockPerseusI18nContext={strings:mockStrings,locale:"en"};const PerseusI18nContext=React.createContext(process.env.NODE_ENV==="test"||process.env.STORYBOOK?mockPerseusI18nContext:null);function PerseusI18nContextProvider({children,strings,locale}){return jsxRuntimeExports.jsx(PerseusI18nContext.Provider,{value:{strings,locale},children:children})}const usePerseusI18n=()=>useContext(PerseusI18nContext);
1412
+ const mockPerseusI18nContext={strings:mockStrings,locale:"en"};const PerseusI18nContext=React.createContext(process.env.NODE_ENV==="test"||process.env.STORYBOOK?mockPerseusI18nContext:null);function PerseusI18nContextProvider({children,strings,locale}){const value=useMemo(()=>({strings,locale}),[strings,locale]);return jsxRuntimeExports.jsx(PerseusI18nContext.Provider,{value:value,children:children})}const usePerseusI18n=()=>useContext(PerseusI18nContext);
1412
1413
 
1413
1414
  var a11y = StyleSheet.create({srOnly:{border:0,clip:"rect(0,0,0,0)",height:1,margin:-1,overflow:"hidden",padding:0,position:"absolute",width:1}});
1414
1415
 
@@ -1606,7 +1607,7 @@ const WB_MODAL_PADDING_TOTAL=64;const ZoomedImageView=props=>{const i18n=usePers
1606
1607
 
1607
1608
  const ZoomImageButton=props=>{const{imgSrc}=props;const i18n=usePerseusI18n();const uniqueId=React.useId().replace(/:/g,"");const zoomedImageUniqueId=`zoomed-image-${uniqueId}`;const handleClick=(event,openModal)=>{const mouseEvent=event;if(mouseEvent.metaKey||mouseEvent.ctrlKey){window.open(imgSrc,"_blank");}else {openModal();}};return jsxRuntimeExports.jsx(ModalLauncher,{initialFocusId:zoomedImageUniqueId,modal:({closeModal})=>jsxRuntimeExports.jsx(ZoomedImageView,{...props,initialFocusId:zoomedImageUniqueId,onClose:closeModal}),children:({openModal})=>jsxRuntimeExports.jsx(Clickable,{"aria-label":i18n.strings.imageZoomAriaLabel,onClick:event=>handleClick(event,openModal),style:{position:"absolute",width:"100%",height:"100%",overflow:"hidden",cursor:"zoom-in"},children:()=>{return jsxRuntimeExports.jsx(React.Fragment,{})}})})};
1608
1609
 
1609
- function isImageProbablyPhotograph(imageUrl){return /\.(jpg|jpeg)$/i.test(imageUrl)}function spinner(){return jsxRuntimeExports.jsx("span",{style:{insetBlockStart:0,insetInlineStart:0,width:"100%",height:"100%",position:"absolute",minInlineSize:"20px",display:"flex",justifyContent:"center",alignContent:"center"},children:jsxRuntimeExports.jsx(CircularSpinner,{size:"medium"})})}class SvgImage extends React.Component{componentDidMount(){this._isMounted=true;if(Util.isLabeledSVG(this.props.src)){this.loadResources();}}UNSAFE_componentWillReceiveProps(nextProps){if(this.props.src!==nextProps.src){this._isLoadingGraphie=false;this.setState({imageLoaded:false,dataLoaded:false});}}shouldComponentUpdate(nextProps,nextState){if(!_.isEqual(this.props,nextProps)){return true}const wasLoaded=this.isLoadedInState(this.state);const nextLoaded=this.isLoadedInState(nextState);return wasLoaded!==nextLoaded}componentDidUpdate(prevProps,prevState){const wasLoaded=this.isLoadedInState(prevState);const isLoaded=this.isLoadedInState(this.state);if(Util.isLabeledSVG(this.props.src)&&!isLoaded&&!this._isLoadingGraphie){this.loadResources();}if(!wasLoaded&&isLoaded){this.props.setAssetStatus(this.props.src,true);}}componentWillUnmount(){this._isMounted=false;}isLoadedInState(state){return Util.isLabeledSVG(this.props.src)?state.imageLoaded&&state.dataLoaded:state.imageLoaded}loadResources(){this._isLoadingGraphie=true;loadGraphie(this.props.src,(data,localized)=>{this._isLoadingGraphie=false;if(this._isMounted&&data.labels&&data.range){const labelsRendered={};data.labels.forEach(label=>{labelsRendered[label.content]=false;});this.setState({dataLoaded:true,labelDataIsLocalized:localized,labelsRendered,labels:data.labels,range:data.range});}});}sizeProvided(){return this.props.width!=null&&this.props.height!=null}_tryGetPixels(value){value=value||"";if(!value.endsWith("px")){return null}return parseFloat(value)||null}render(){const imageSrc=this.props.src;const imageProps={alt:this.props.alt,title:this.props.title};const width=this.props.width&&this.props.width*this.props.scale;const height=this.props.height&&this.props.height*this.props.scale;const dimensions={width,height};const responsive=this.props.responsive&&!!(width&&height);let extraGraphie;if(this.props.extraGraphie&&this.props.extraGraphie.labels.length){extraGraphie=jsxRuntimeExports.jsx(Graphie,{box:this.props.extraGraphie.box,range:this.props.extraGraphie.range,options:{labels:this.props.extraGraphie.labels},responsive:true,addMouseLayer:false,setup:this.setupGraphie});}if(!Util.isLabeledSVG(imageSrc)){if(responsive){const isGifControlled=this.props.isGifPlaying!==undefined;const imageContent=jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[!isGifControlled&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(ImageLoader$1,{src:imageSrc,imgProps:imageProps,preloader:spinner,onUpdate:this.handleUpdate}),extraGraphie]}),isGifControlled&&jsxRuntimeExports.jsx(GifImage,{alt:this.props.alt,width:this.props.width,height:this.props.height,scale:this.props.scale,isPlaying:!!this.props.isGifPlaying,onLoop:this.props.onGifLoop??(()=>{}),onLoad:()=>{this.onImageLoad();},gifFrames:this.props.gifFrames})]});return jsxRuntimeExports.jsxs(FixedToResponsive,{className:"svg-image",width:width,height:height,constrainHeight:this.props.constrainHeight,allowFullBleed:this.props.allowFullBleed&&isImageProbablyPhotograph(imageSrc),scale:this.props.scale,children:[imageContent,this.props.allowZoom&&jsxRuntimeExports.jsx(ZoomImageButton,{...this.props,imgSrc:imageSrc,width:this.props.width,height:this.props.height})]})}imageProps.style=dimensions;return jsxRuntimeExports.jsx(ImageLoader$1,{src:imageSrc,preloader:spinner,imgProps:imageProps,onUpdate:this.handleUpdate})}const imageUrl=Util.getSvgUrl(imageSrc);let graphie;if(this.isLoadedInState(this.state)){let box;if(this.sizeProvided()){box=[width,height];}else if(this.state.imageDimensions){box=[this.state.imageDimensions[0]*this.props.scale,this.state.imageDimensions[1]*this.props.scale];}else {throw new PerseusError("svg-image has no dimensions",Errors.InvalidInput,{metadata:{src:this.props.src}})}graphie=jsxRuntimeExports.jsx(Graphie,{ref:"graphie",box:box,scale:[40*this.props.scale,40*this.props.scale],range:this.state.range,options:_.pick(this.state,"labels"),responsive:responsive,addMouseLayer:false,setup:this.setupGraphie});}if(responsive){const imageContent=jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(ImageLoader$1,{src:imageUrl,onLoad:this.onImageLoad,onUpdate:this.handleUpdate,preloader:spinner,imgProps:imageProps}),graphie,extraGraphie]});return jsxRuntimeExports.jsxs(FixedToResponsive,{className:"svg-image",width:width,height:height,constrainHeight:this.props.constrainHeight,scale:this.props.scale,children:[imageContent,this.props.allowZoom&&jsxRuntimeExports.jsx(ZoomImageButton,{...this.props,imgSrc:imageUrl,width:this.props.width,height:this.props.height})]})}imageProps.style=dimensions;return jsxRuntimeExports.jsxs("div",{className:"unresponsive-svg-image",style:dimensions,children:[jsxRuntimeExports.jsx(ImageLoader$1,{src:imageUrl,onLoad:this.onImageLoad,onUpdate:this.handleUpdate,preloader:spinner,imgProps:imageProps}),graphie]})}constructor(props){super(props),this.onImageLoad=()=>{if(this.sizeProvided()){this.setState({imageLoaded:true});}else {Util.getImageSize(this.props.src,(width,height)=>{if(this._isMounted){this.setState({imageLoaded:true,imageDimensions:[width,height]});}});}},this.setupGraphie=(graphie,options)=>{const newLabelsRendered={};_.map(options.labels,labelData=>{const{JIPT}=getDependencies();if(JIPT.useJIPT&&this.state.labelDataIsLocalized){const elem=graphie.label(labelData.coordinates,labelData.content,labelData.alignment,false);getDependencies().svgImageJiptLabels.addLabel(elem,labelData.typesetAsMath);}else if(labelData.coordinates){const styling=this.props.scale!==1?{"font-size":100*this.props.scale+"%"}:null;const label=graphie.label(labelData.coordinates,labelData.content,labelData.alignment,labelData.typesetAsMath,styling);const labelStyle=label[0].style;let labelTop=this._tryGetPixels(labelStyle.top);let labelLeft=this._tryGetPixels(labelStyle.left);if(labelTop===null||labelLeft===null){const labelPosition=label.position();labelTop=labelPosition.top;labelLeft=labelPosition.left;}const svgHeight=(this.props.height||0)*this.props.scale;const svgWidth=(this.props.width||0)*this.props.scale;label.css({top:labelTop/svgHeight*100+"%",left:labelLeft/svgWidth*100+"%"});_.each(labelData.style,(styleValue,styleName)=>{label.css(styleName,styleValue);});if(labelData.style?.color){label.css("color",toClosestMathColor(labelData.style.color));}}newLabelsRendered[labelData.content]=true;});this.setState(prev=>({labelsRendered:{...prev.labelsRendered,...newLabelsRendered}}));},this.handleUpdate=status=>{this.props.onUpdate();if(!Util.isLabeledSVG(this.props.src)&&status==="loaded"){this.setState({imageLoaded:true});}};props.setAssetStatus(props.src,false);this._isMounted=false;this._isLoadingGraphie=false;this.state={imageLoaded:false,imageDimensions:null,dataLoaded:false,labelDataIsLocalized:false,labels:[],labelsRendered:{},range:[[0,0],[0,0]]};}}SvgImage.contextType=PerseusI18nContext;SvgImage.defaultProps={constrainHeight:false,onUpdate:()=>{},responsive:true,src:"",scale:1,zoomToFullSizeOnMobile:false,setAssetStatus:(src,status)=>{}};
1610
+ function isImageProbablyPhotograph(imageUrl){return /\.(jpg|jpeg)$/i.test(imageUrl)}function spinner(){return jsxRuntimeExports.jsx("span",{style:{insetBlockStart:0,insetInlineStart:0,width:"100%",height:"100%",position:"absolute",minInlineSize:"20px",display:"flex",justifyContent:"center",alignContent:"center"},children:jsxRuntimeExports.jsx(CircularSpinner,{size:"medium"})})}class SvgImage extends React.Component{componentDidMount(){this._isMounted=true;if(Util.isLabeledSVG(this.props.src)){this.loadResources();}}UNSAFE_componentWillReceiveProps(nextProps){if(this.props.src!==nextProps.src){this._isLoadingGraphie=false;this.setState({imageLoaded:false,dataLoaded:false});}}shouldComponentUpdate(nextProps,nextState){if(!_.isEqual(this.props,nextProps)){return true}const wasLoaded=this.isLoadedInState(this.state);const nextLoaded=this.isLoadedInState(nextState);return wasLoaded!==nextLoaded}componentDidUpdate(prevProps,prevState){const wasLoaded=this.isLoadedInState(prevState);const isLoaded=this.isLoadedInState(this.state);if(Util.isLabeledSVG(this.props.src)&&!this.state.dataLoaded&&!this._isLoadingGraphie){this.loadResources();}if(!wasLoaded&&isLoaded){this.props.setAssetStatus(this.props.src,true);}}componentWillUnmount(){this._isMounted=false;}isLoadedInState(state){return Util.isLabeledSVG(this.props.src)?state.imageLoaded&&state.dataLoaded:state.imageLoaded}loadResources(){this._isLoadingGraphie=true;loadGraphie(this.props.src,(data,localized)=>{this._isLoadingGraphie=false;if(this._isMounted&&data.labels&&data.range){const labelsRendered={};data.labels.forEach(label=>{labelsRendered[label.content]=false;});this.setState({dataLoaded:true,labelDataIsLocalized:localized,labelsRendered,labels:data.labels,range:data.range});}});}sizeProvided(){return this.props.width!=null&&this.props.height!=null}_tryGetPixels(value){value=value||"";if(!value.endsWith("px")){return null}return parseFloat(value)||null}render(){const imageSrc=this.props.src;const imageProps={alt:this.props.alt,title:this.props.title};const width=this.props.width&&this.props.width*this.props.scale;const height=this.props.height&&this.props.height*this.props.scale;const dimensions={width,height};const responsive=this.props.responsive&&!!(width&&height);let extraGraphie;if(this.props.extraGraphie&&this.props.extraGraphie.labels.length){extraGraphie=jsxRuntimeExports.jsx(Graphie,{box:this.props.extraGraphie.box,range:this.props.extraGraphie.range,options:{labels:this.props.extraGraphie.labels},responsive:true,addMouseLayer:false,setup:this.setupGraphie});}if(!Util.isLabeledSVG(imageSrc)){if(responsive){const isGifControlled=this.props.isGifPlaying!==undefined;const imageContent=jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[!isGifControlled&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(ImageLoader$1,{src:imageSrc,imgProps:imageProps,preloader:spinner,onUpdate:this.handleUpdate}),extraGraphie]}),isGifControlled&&jsxRuntimeExports.jsx(GifImage,{alt:this.props.alt,width:this.props.width,height:this.props.height,scale:this.props.scale,isPlaying:!!this.props.isGifPlaying,onLoop:this.props.onGifLoop??(()=>{}),onLoad:()=>{this.onImageLoad();},gifFrames:this.props.gifFrames})]});return jsxRuntimeExports.jsxs(FixedToResponsive,{className:"svg-image",width:width,height:height,constrainHeight:this.props.constrainHeight,allowFullBleed:this.props.allowFullBleed&&isImageProbablyPhotograph(imageSrc),scale:this.props.scale,children:[imageContent,this.props.allowZoom&&jsxRuntimeExports.jsx(ZoomImageButton,{...this.props,imgSrc:imageSrc,width:this.props.width,height:this.props.height})]})}imageProps.style=dimensions;return jsxRuntimeExports.jsx(ImageLoader$1,{src:imageSrc,preloader:spinner,imgProps:imageProps,onUpdate:this.handleUpdate})}const imageUrl=Util.getSvgUrl(imageSrc);let graphie;if(this.isLoadedInState(this.state)){let box;if(this.sizeProvided()){box=[width,height];}else if(this.state.imageDimensions){box=[this.state.imageDimensions[0]*this.props.scale,this.state.imageDimensions[1]*this.props.scale];}else {throw new PerseusError("svg-image has no dimensions",Errors.InvalidInput,{metadata:{src:this.props.src}})}graphie=jsxRuntimeExports.jsx(Graphie,{ref:"graphie",box:box,scale:[40*this.props.scale,40*this.props.scale],range:this.state.range,options:_.pick(this.state,"labels"),responsive:responsive,addMouseLayer:false,setup:this.setupGraphie});}if(responsive){const imageContent=jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(ImageLoader$1,{src:imageUrl,onLoad:this.onImageLoad,onUpdate:this.handleUpdate,preloader:spinner,imgProps:imageProps}),graphie,extraGraphie]});return jsxRuntimeExports.jsxs(FixedToResponsive,{className:"svg-image",width:width,height:height,constrainHeight:this.props.constrainHeight,scale:this.props.scale,children:[imageContent,this.props.allowZoom&&jsxRuntimeExports.jsx(ZoomImageButton,{...this.props,imgSrc:imageUrl,width:this.props.width,height:this.props.height})]})}imageProps.style=dimensions;return jsxRuntimeExports.jsxs("div",{className:"unresponsive-svg-image",style:dimensions,children:[jsxRuntimeExports.jsx(ImageLoader$1,{src:imageUrl,onLoad:this.onImageLoad,onUpdate:this.handleUpdate,preloader:spinner,imgProps:imageProps}),graphie]})}constructor(props){super(props),this.onImageLoad=()=>{if(this.sizeProvided()){this.setState({imageLoaded:true});}else {Util.getImageSize(this.props.src,(width,height)=>{if(this._isMounted){this.setState({imageLoaded:true,imageDimensions:[width,height]});}});}},this.setupGraphie=(graphie,options)=>{const newLabelsRendered={};_.map(options.labels,labelData=>{const{JIPT}=getDependencies();if(JIPT.useJIPT&&this.state.labelDataIsLocalized){const elem=graphie.label(labelData.coordinates,labelData.content,labelData.alignment,false);getDependencies().svgImageJiptLabels.addLabel(elem,labelData.typesetAsMath);}else if(labelData.coordinates){const styling=this.props.scale!==1?{"font-size":100*this.props.scale+"%"}:null;const label=graphie.label(labelData.coordinates,labelData.content,labelData.alignment,labelData.typesetAsMath,styling);const labelStyle=label[0].style;let labelTop=this._tryGetPixels(labelStyle.top);let labelLeft=this._tryGetPixels(labelStyle.left);if(labelTop===null||labelLeft===null){const labelPosition=label.position();labelTop=labelPosition.top;labelLeft=labelPosition.left;}const svgHeight=(this.props.height||0)*this.props.scale;const svgWidth=(this.props.width||0)*this.props.scale;label.css({top:labelTop/svgHeight*100+"%",left:labelLeft/svgWidth*100+"%"});_.each(labelData.style,(styleValue,styleName)=>{label.css(styleName,styleValue);});if(labelData.style?.color){label.css("color",toClosestMathColor(labelData.style.color));}}newLabelsRendered[labelData.content]=true;});this.setState(prev=>({labelsRendered:{...prev.labelsRendered,...newLabelsRendered}}));},this.handleUpdate=status=>{this.props.onUpdate();if(!Util.isLabeledSVG(this.props.src)&&status==="loaded"){this.setState({imageLoaded:true});}};props.setAssetStatus(props.src,false);this._isMounted=false;this._isLoadingGraphie=false;this.state={imageLoaded:false,imageDimensions:null,dataLoaded:false,labelDataIsLocalized:false,labels:[],labelsRendered:{},range:[[0,0],[0,0]]};}}SvgImage.contextType=PerseusI18nContext;SvgImage.defaultProps={constrainHeight:false,onUpdate:()=>{},responsive:true,src:"",scale:1,zoomToFullSizeOnMobile:false,setAssetStatus:(src,status)=>{}};
1610
1611
 
1611
1612
  class Tex extends React.Component{render(){const{TeX:BaseTeX}=getDependencies();return jsxRuntimeExports.jsx(BaseTeX,{onRender:this.handleRender,children:this.props.children})}constructor(props){super(props),this.handleRender=()=>{this.setState({rendered:true});this.props.onRender();if(!this._hasRendered){this._hasRendered=true;this.props.setAssetStatus(this.props.children,true);}};this.props.setAssetStatus(this.props.children,false);this.state={rendered:false};this._hasRendered=false;}}Tex.defaultProps={onRender:()=>{},setAssetStatus:(src,status)=>{}};
1612
1613
 
@@ -1720,9 +1721,13 @@ function getStartUserInput$g(){return {selectedChoiceIds:[]}}var Radio = {name:"
1720
1721
 
1721
1722
  var basicWidgets = [Radio,InputNumber,NumericInput$1,Expression$1];
1722
1723
 
1723
- var styles$p = {"container":"perseus_LCi2Yw7w","super-sub":"perseus_8JKzE83C"};
1724
+ function readTileDragData(data){const tileId=data?.tileId;if(typeof tileId!=="string"){return null}const fromBlankId=data?.fromBlankId;return typeof fromBlankId==="string"?{tileId,fromBlankId}:{tileId}}
1724
1725
 
1725
- const BlankWidget=forwardRef(function BlankWidget(props,ref){const classes=[styles$p.container].concat(props.options.displayType!=="normal"?[styles$p["super-sub"]]:[]).join(" ");useImperativeHandle(ref,()=>({}));return jsxRuntimeExports.jsx("div",{className:classes,"data-testid":"blank-widget"})});var Blank = {name:"blank",displayName:"Blank",widget:BlankWidget,isLintable:true,hidden:true};
1726
+ var styles$p = {"container":"perseus_w5JEPtr3","superSub":"perseus_YYUlZ0FC","superscript":"perseus_zuqfZw15","subscript":"perseus_WBbw9tQT","filled":"perseus_1Uom-GrS","keepsWidth":"perseus_djiO5HzC","dropTarget":"perseus_ZiDXp0R-"};
1727
+
1728
+ function BlankComponent(props){const{blankId,displayType,children,minWidth,keepsWidthWhenFilled,className,testId}=props;const{ref,isDropTarget}=useDroppable({id:blankId});const{source}=useDragOperation();const isFilled=React.Children.toArray(children).length>0;const dragged=readTileDragData(source?.data);const isTileDraggingOut=isFilled&&dragged?.fromBlankId===blankId;const showsPlacedTile=isFilled&&!isTileDraggingOut;const minWidthStyle=minWidth!=null?{"--blank-min-inline-size":`${minWidth}px`}:undefined;const slotClasses=classNames$2(styles$p.container,DISPLAY_TYPE_CLASSES[displayType],showsPlacedTile&&styles$p.filled,keepsWidthWhenFilled&&styles$p.keepsWidth,isDropTarget&&styles$p.dropTarget,className);return jsxRuntimeExports.jsx("div",{ref:ref,className:slotClasses,style:minWidthStyle,"data-blank":"true","data-testid":testId,children:children})}const DISPLAY_TYPE_CLASSES={normal:undefined,superscript:classNames$2(styles$p.superSub,styles$p.superscript),subscript:classNames$2(styles$p.superSub,styles$p.subscript)};
1729
+
1730
+ const BlankWidget=forwardRef(function BlankWidget(props,ref){useImperativeHandle(ref,()=>({}));return jsxRuntimeExports.jsx(BlankComponent,{blankId:props.widgetId,displayType:props.options.displayType,testId:"blank-widget"})});var Blank = {name:"blank",displayName:"Blank",widget:BlankWidget,isLintable:true,hidden:true};
1726
1731
 
1727
1732
  const{pureXsMax,pureSmMax,pureMdMin,pureMdMax,pureLgMin,pureLgMax}=constants;var mediaQueries = {md:`@media screen and (min-width: ${pureMdMin}) and `+`(max-width: ${pureMdMax})`,xsOrSmaller:`@media screen and (max-width: ${pureXsMax})`,smOrSmaller:`@media screen and (max-width: ${pureSmMax})`,mdOrSmaller:`@media screen and (max-width: ${pureMdMax})`,lgOrSmaller:`@media screen and (max-width: ${pureLgMax})`,lgOrLarger:`@media screen and (min-width: ${pureLgMin})`};
1728
1733
 
@@ -1730,7 +1735,7 @@ const{circleSize,radioMarginWidth}=constants;var sharedStyles = StyleSheet.creat
1730
1735
 
1731
1736
  const getPromptJSON$n=widgetData=>{return {type:"categorizer",options:{items:widgetData.options.items,categories:widgetData.options.categories},userInput:{itemToCategoryMapping:widgetData.userInput.values}}};
1732
1737
 
1733
- const Categorizer=forwardRef(function Categorizer(props,ref){const{items,categories,randomizeItems}=props.options;const{userInput,problemNum,apiOptions,linterContext,handleUserInput,trackInteraction,dependencies}=props;const{strings}=usePerseusI18n();const idPrefix=useMemo(()=>_.uniqueId("perseus_radio_"),[]);useOnMountEffect(()=>{dependencies.analytics.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"categorizer",widgetId:props.widgetId}});});useImperativeHandle(ref,()=>({getPromptJSON:()=>getPromptJSON$n(props),getSerializedState(){const{userInput,options,...rest}=props;return {...options,...rest,values:userInput.values}}}));function _handleUserInput(itemNum,catNum){const values=[...userInput.values];values[itemNum]=catNum;handleUserInput({values});trackInteraction();}let indexedItems=items.map((item,n)=>[item,n]);if(randomizeItems){indexedItems=shuffle(indexedItems,problemNum);}const table=jsxRuntimeExports.jsxs("table",{className:"categorizer-table "+css(styles$o.mobileTable),children:[jsxRuntimeExports.jsx("thead",{children:jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("td",{className:css(styles$o.emptyHeaderCell)}),categories.map((category,i)=>{return jsxRuntimeExports.jsx("th",{className:css(styles$o.header),children:jsxRuntimeExports.jsx(Renderer,{content:category,linterContext:linterContext,strings:strings})},i)})]})}),jsxRuntimeExports.jsx("tbody",{children:indexedItems.map(indexedItem=>{const item=indexedItem[0];const itemNum=indexedItem[1];const uniqueId=idPrefix+"_"+itemNum;return jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("td",{children:jsxRuntimeExports.jsx(Renderer,{content:item,linterContext:linterContext,strings:strings})}),categories.map((catName,catNum)=>{const selected=userInput.values[itemNum]===catNum;return jsxRuntimeExports.jsx("td",{className:"category "+css(styles$o.cell),children:jsxRuntimeExports.jsxs("div",{role:"button","aria-label":catName,onClick:()=>_handleUserInput(itemNum,catNum),children:[apiOptions.isMobile&&jsxRuntimeExports.jsx("input",{type:"radio",name:uniqueId,className:css(sharedStyles.responsiveInput,sharedStyles.responsiveRadioInput,props.static&&sharedStyles.responsiveRadioInputStatic),checked:selected,onChange:()=>_handleUserInput(itemNum,catNum),onClick:e=>e.stopPropagation()}),!apiOptions.isMobile&&jsxRuntimeExports.jsx("span",{className:css(styles$o.radioSpan,selected&&styles$o.checkedRadioSpan,props.static&&selected&&styles$o.staticCheckedRadioSpan),children:selected?jsxRuntimeExports.jsx(InlineIcon,{...iconCircle}):jsxRuntimeExports.jsx(InlineIcon,{...iconCircleThin})})]})},catNum)})]},itemNum)})})]});const extraClassNames=classNames$2({"categorizer-container":true,"static-mode":props.static});const inlineStyles=apiOptions.isMobile?[styles$o.fullBleedContainer]:[];return jsxRuntimeExports.jsx("div",{className:extraClassNames+" "+css(...inlineStyles),children:table})});const styles$o=StyleSheet.create({mobileTable:{[mediaQueries.smOrSmaller]:{minInlineSize:"auto"}},fullBleedContainer:{[mediaQueries.mdOrSmaller]:{marginInlineStart:-16,marginInlineEnd:-16,overflowX:"auto"}},header:{textAlign:"center",verticalAlign:"bottom"},cell:{textAlign:"center",padding:0,color:semanticColor.core.border.neutral.subtle,verticalAlign:"middle"},emptyHeaderCell:{backgroundColor:"inherit",borderBlockEnd:`${border$1.width.medium} solid ${semanticColor.core.border.neutral.subtle}`},radioSpan:{fontSize:sizing.size_280,paddingInlineEnd:3,cursor:"pointer",":hover":{color:semanticColor.core.foreground.instructive.subtle}},checkedRadioSpan:{color:semanticColor.core.foreground.instructive.default},staticCheckedRadioSpan:{color:semanticColor.core.foreground.disabled.strong}});function getUserInputFromSerializedState$e(serializedState){return {values:serializedState.values}}function getCorrectUserInput$7(options){return {values:options.values}}function getStartUserInput$f(){return {values:[]}}const WrappedCategorizer=withDependencies(Categorizer);var Categorizer$1 = {name:"categorizer",displayName:"Categorizer",widget:WrappedCategorizer,getUserInputFromSerializedState: getUserInputFromSerializedState$e,getCorrectUserInput: getCorrectUserInput$7,getStartUserInput: getStartUserInput$f,isLintable:true};
1738
+ const Categorizer=forwardRef(function Categorizer(props,ref){const{items,categories,randomizeItems}=props.options;const{userInput,problemNum,apiOptions,linterContext,handleUserInput,trackInteraction}=props;const dependencies=useDependencies();const{strings}=usePerseusI18n();const idPrefix=useMemo(()=>_.uniqueId("perseus_radio_"),[]);useOnMountEffect(()=>{dependencies.analytics.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"categorizer",widgetId:props.widgetId}});});useImperativeHandle(ref,()=>({getPromptJSON:()=>getPromptJSON$n(props),getSerializedState(){const{userInput,options,...rest}=props;return {...options,...rest,values:userInput.values}}}));function _handleUserInput(itemNum,catNum){const values=[...userInput.values];values[itemNum]=catNum;handleUserInput({values});trackInteraction();}let indexedItems=items.map((item,n)=>[item,n]);if(randomizeItems){indexedItems=shuffle(indexedItems,problemNum);}const table=jsxRuntimeExports.jsxs("table",{className:"categorizer-table "+css(styles$o.mobileTable),children:[jsxRuntimeExports.jsx("thead",{children:jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("td",{className:css(styles$o.emptyHeaderCell)}),categories.map((category,i)=>{return jsxRuntimeExports.jsx("th",{className:css(styles$o.header),children:jsxRuntimeExports.jsx(Renderer,{content:category,linterContext:linterContext,strings:strings})},i)})]})}),jsxRuntimeExports.jsx("tbody",{children:indexedItems.map(indexedItem=>{const item=indexedItem[0];const itemNum=indexedItem[1];const uniqueId=idPrefix+"_"+itemNum;return jsxRuntimeExports.jsxs("tr",{children:[jsxRuntimeExports.jsx("td",{children:jsxRuntimeExports.jsx(Renderer,{content:item,linterContext:linterContext,strings:strings})}),categories.map((catName,catNum)=>{const selected=userInput.values[itemNum]===catNum;return jsxRuntimeExports.jsx("td",{className:"category "+css(styles$o.cell),children:jsxRuntimeExports.jsxs("div",{role:"button","aria-label":catName,onClick:()=>_handleUserInput(itemNum,catNum),children:[apiOptions.isMobile&&jsxRuntimeExports.jsx("input",{type:"radio",name:uniqueId,className:css(sharedStyles.responsiveInput,sharedStyles.responsiveRadioInput,props.static&&sharedStyles.responsiveRadioInputStatic),checked:selected,onChange:()=>_handleUserInput(itemNum,catNum),onClick:e=>e.stopPropagation()}),!apiOptions.isMobile&&jsxRuntimeExports.jsx("span",{className:css(styles$o.radioSpan,selected&&styles$o.checkedRadioSpan,props.static&&selected&&styles$o.staticCheckedRadioSpan),children:selected?jsxRuntimeExports.jsx(InlineIcon,{...iconCircle}):jsxRuntimeExports.jsx(InlineIcon,{...iconCircleThin})})]})},catNum)})]},itemNum)})})]});const extraClassNames=classNames$2({"categorizer-container":true,"static-mode":props.static});const inlineStyles=apiOptions.isMobile?[styles$o.fullBleedContainer]:[];return jsxRuntimeExports.jsx("div",{className:extraClassNames+" "+css(...inlineStyles),children:table})});const styles$o=StyleSheet.create({mobileTable:{[mediaQueries.smOrSmaller]:{minInlineSize:"auto"}},fullBleedContainer:{[mediaQueries.mdOrSmaller]:{marginInlineStart:-16,marginInlineEnd:-16,overflowX:"auto"}},header:{textAlign:"center",verticalAlign:"bottom"},cell:{textAlign:"center",padding:0,color:semanticColor.core.border.neutral.subtle,verticalAlign:"middle"},emptyHeaderCell:{backgroundColor:"inherit",borderBlockEnd:`${border$1.width.medium} solid ${semanticColor.core.border.neutral.subtle}`},radioSpan:{fontSize:sizing.size_280,paddingInlineEnd:3,cursor:"pointer",":hover":{color:semanticColor.core.foreground.instructive.subtle}},checkedRadioSpan:{color:semanticColor.core.foreground.instructive.default},staticCheckedRadioSpan:{color:semanticColor.core.foreground.disabled.strong}});function getUserInputFromSerializedState$e(serializedState){return {values:serializedState.values}}function getCorrectUserInput$7(options){return {values:options.values}}function getStartUserInput$f(){return {values:[]}}var Categorizer$1 = {name:"categorizer",displayName:"Categorizer",widget:Categorizer,getUserInputFromSerializedState: getUserInputFromSerializedState$e,getCorrectUserInput: getCorrectUserInput$7,getStartUserInput: getStartUserInput$f,isLintable:true};
1734
1739
 
1735
1740
  const isFileProtocol=protocol=>{if(protocol&&protocol.toLowerCase()==="file:"){return true}return false};
1736
1741
 
@@ -1754,7 +1759,7 @@ class DeprecatedStandin extends React__default.Component{render(){return jsxRunt
1754
1759
 
1755
1760
  const getPromptJSON$k=widgetData=>{return {type:"dropdown",options:{items:widgetData.options.choices.map(choice=>choice.content)},userInput:{selectedIndex:widgetData.userInput.value-1}}};
1756
1761
 
1757
- const Dropdown=forwardRef(function Dropdown(props,ref){const{strings}=usePerseusI18n();const dropdownId=useId();const{choices=[],placeholder="",visibleLabel,ariaLabel}=props.options;const{apiOptions=ApiOptions.defaults,userInput={value:0},dependencies,widgetId,trackInteraction,handleUserInput}=props;const isStatic=props.static??false;useEffect(()=>{dependencies.analytics.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"dropdown",widgetId:widgetId}});},[]);const handleChange=selected=>{trackInteraction();handleUserInput({value:selected});};const rootRef=useRef(null);useImperativeHandle(ref,()=>({focus:()=>{if(apiOptions.readOnly||isStatic){return false}if(!rootRef.current){return false}const button=rootRef.current.querySelector("[role='combobox']");if(!(button instanceof HTMLElement)){return false}if(button instanceof HTMLButtonElement&&button.disabled||button.getAttribute("aria-disabled")==="true"){return false}const previouslyFocused=document.activeElement;button.focus();return document.activeElement===button&&previouslyFocused!==button},getPromptJSON:()=>{return getPromptJSON$k(props)},getSerializedState:()=>{const{userInput,options,...otherProps}=props;const{choices,...otherOptions}=options;return {...otherOptions,...otherProps,choices:choices.map(choice=>choice.content),selected:userInput.value}}}));const inline=isFeatureOn({apiOptions:props.apiOptions},"perseus-renderer-upgrade");const children=[jsxRuntimeExports.jsx(OptionItem,{value:"0",disabled:true,label:jsxRuntimeExports.jsx(Renderer,{content:placeholder,strings:strings,apiOptions:{flags:apiOptions.flags},inline:inline}),labelAsText:placeholder},"placeholder"),...choices.map((choice,i)=>jsxRuntimeExports.jsx(OptionItem,{value:String(i+1),label:jsxRuntimeExports.jsx(Renderer,{content:choice.content,strings:strings,apiOptions:{flags:apiOptions.flags},inline:inline}),labelAsText:choice.content},String(i+1)))];return jsxRuntimeExports.jsxs(View,{ref:rootRef,onClick:e=>{e.stopPropagation();},onTouchStart:e=>{e.stopPropagation();},children:[visibleLabel&&jsxRuntimeExports.jsx(BodyText,{size:"medium",weight:"bold",tag:"label",htmlFor:dropdownId,children:visibleLabel}),jsxRuntimeExports.jsx(SingleSelect,{id:dropdownId,placeholder:"",className:"perseus-dropdown",onChange:value=>handleChange(parseInt(value)),selectedValue:String(userInput.value),disabled:Boolean(apiOptions.readOnly||isStatic),"aria-label":ariaLabel||visibleLabel||strings.selectAnAnswer,showOpenerLabelAsText:false,children:children})]})});function getUserInputFromSerializedState$c(serializedState){return {value:serializedState.selected}}function getStartUserInput$d(){return {value:0}}function getCorrectUserInput$6(options){return {value:options.choices.findIndex(c=>c.correct)+1}}const WrappedDropdown=withDependencies(Dropdown);var Dropdown$1 = {name:"dropdown",displayName:"Drop down",widget:WrappedDropdown,getStartUserInput: getStartUserInput$d,getCorrectUserInput: getCorrectUserInput$6,getUserInputFromSerializedState: getUserInputFromSerializedState$c};
1762
+ const Dropdown=forwardRef(function Dropdown(props,ref){const{strings}=usePerseusI18n();const dropdownId=useId();const{choices=[],placeholder="",visibleLabel,ariaLabel}=props.options;const{apiOptions=ApiOptions.defaults,userInput={value:0},widgetId,trackInteraction,handleUserInput}=props;const isStatic=props.static??false;const dependencies=useDependencies();useEffect(()=>{dependencies.analytics.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"dropdown",widgetId:widgetId}});},[]);const handleChange=selected=>{trackInteraction();handleUserInput({value:selected});};const rootRef=useRef(null);useImperativeHandle(ref,()=>({focus:()=>{if(apiOptions.readOnly||isStatic){return false}if(!rootRef.current){return false}const button=rootRef.current.querySelector("[role='combobox']");if(!(button instanceof HTMLElement)){return false}if(button instanceof HTMLButtonElement&&button.disabled||button.getAttribute("aria-disabled")==="true"){return false}const previouslyFocused=document.activeElement;button.focus();return document.activeElement===button&&previouslyFocused!==button},getPromptJSON:()=>{return getPromptJSON$k(props)},getSerializedState:()=>{const{userInput,options,...otherProps}=props;const{choices,...otherOptions}=options;return {...otherOptions,...otherProps,choices:choices.map(choice=>choice.content),selected:userInput.value}}}));const inline=isFeatureOn({apiOptions:props.apiOptions},"perseus-renderer-upgrade");const children=[jsxRuntimeExports.jsx(OptionItem,{value:"0",disabled:true,label:jsxRuntimeExports.jsx(Renderer,{content:placeholder,strings:strings,apiOptions:{flags:apiOptions.flags},inline:inline}),labelAsText:placeholder},"placeholder"),...choices.map((choice,i)=>jsxRuntimeExports.jsx(OptionItem,{value:String(i+1),label:jsxRuntimeExports.jsx(Renderer,{content:choice.content,strings:strings,apiOptions:{flags:apiOptions.flags},inline:inline}),labelAsText:choice.content},String(i+1)))];return jsxRuntimeExports.jsxs(View,{ref:rootRef,onClick:e=>{e.stopPropagation();},onTouchStart:e=>{e.stopPropagation();},children:[visibleLabel&&jsxRuntimeExports.jsx(BodyText,{size:"medium",weight:"bold",tag:"label",htmlFor:dropdownId,children:visibleLabel}),jsxRuntimeExports.jsx(SingleSelect,{id:dropdownId,placeholder:"",className:"perseus-dropdown",onChange:value=>handleChange(parseInt(value)),selectedValue:String(userInput.value),disabled:Boolean(apiOptions.readOnly||isStatic),"aria-label":ariaLabel||visibleLabel||strings.selectAnAnswer,showOpenerLabelAsText:false,children:children})]})});function getUserInputFromSerializedState$c(serializedState){return {value:serializedState.selected}}function getStartUserInput$d(){return {value:0}}function getCorrectUserInput$6(options){return {value:options.choices.findIndex(c=>c.correct)+1}}var Dropdown$1 = {name:"dropdown",displayName:"Drop down",widget:Dropdown,getStartUserInput: getStartUserInput$d,getCorrectUserInput: getCorrectUserInput$6,getUserInputFromSerializedState: getUserInputFromSerializedState$c};
1758
1763
 
1759
1764
  function sharedInitializeUserInput(widgetOptions,problemNum){const startUserInput={};if(!widgetOptions){return startUserInput}Object.entries(widgetOptions).forEach(([id,widgetInfo])=>{const widgetExports=getWidgetExport(widgetInfo.type);if(widgetInfo.static&&widgetExports?.getCorrectUserInput){startUserInput[id]=widgetExports.getCorrectUserInput(widgetInfo.options);}else if(widgetExports?.getStartUserInput){startUserInput[id]=widgetExports.getStartUserInput(widgetInfo.options,problemNum??0);}});return startUserInput}function deriveUserInputFromSerializedState(serializedState,widgetsMap){const restoredUserInput={};Object.entries(serializedState).forEach(([widgetId,props])=>{const widgetType=getWidgetTypeByWidgetId(widgetId,widgetsMap);const widgetExport=getWidgetExport(widgetType);if(widgetExport?.getUserInputFromSerializedState){const restoreResult=widgetExport.getUserInputFromSerializedState(props,widgetsMap[widgetId].options);restoredUserInput[widgetId]=restoreResult;}});return restoredUserInput}function UserInputManager(props){const[userInput,setUserInput]=useState(props.initialUserInput||sharedInitializeUserInput(props.widgets,props.problemNum??0));function handleUserInput(id,nextUserInput,widgetsEmpty){const next={...userInput,[id]:nextUserInput};setUserInput(next);props.handleUserInput?.(next,widgetsEmpty);}function initializeUserInput(widgetOptions,problemNum){setUserInput(props.initialUserInput||sharedInitializeUserInput(widgetOptions,problemNum));}return props.children({userInput,handleUserInput,initializeUserInput})}
1760
1765
 
@@ -2067,7 +2072,7 @@ const addOffsetParentScroll=($el,position)=>{const $offsetParent=$el.offsetParen
2067
2072
 
2068
2073
  const getPromptJSON$9=widgetData=>{const{userInput,options}=widgetData;return {type:"matcher",options:{labels:options.labels,left:options.left,right:options.right,orderMatters:options.orderMatters},userInput:{left:userInput.left,right:userInput.right}}};
2069
2074
 
2070
- const HACKY_CSS_CLASSNAME="perseus-widget-matcher";const Matcher=forwardRef(function Matcher(props,ref){const{strings}=usePerseusI18n();const leftSortable=useRef(null);const rightSortable=useRef(null);const[leftHeight,setLeftHeight]=useState(0);const[rightHeight,setRightHeight]=useState(0);const[texRendererLoaded,setTexRendererLoaded]=useState(false);useOnMountEffect(()=>{props.dependencies.analytics.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"matcher",widgetId:props.widgetId}});});useImperativeHandle(ref,()=>({getPromptJSON:()=>getPromptJSON$9(props),moveLeftOptionToIndex:(option,index)=>{leftSortable.current?.moveOptionToIndex(option,index);},moveRightOptionToIndex:(option,index)=>{rightSortable.current?.moveOptionToIndex(option,index);}}));function getUserInputFromSortable(){if(!texRendererLoaded){return {left:[],right:[]}}return {left:leftSortable.current?.getOptions()??[],right:rightSortable.current?.getOptions()??[]}}function changeAndTrack(){props.handleUserInput(getUserInputFromSortable());props.trackInteraction();}if(!texRendererLoaded){const{TeX}=getDependencies();return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(CircularSpinner,{}),jsxRuntimeExports.jsx("div",{style:{display:"none"},children:jsxRuntimeExports.jsx(TeX,{onRender:()=>setTexRendererLoaded(true),children:"1"})})]})}const{labels,padding,orderMatters}=props.options;const showLabels=labels.some(isTruthy);const constraints={height:Math.max(leftHeight,rightHeight)};const cellMarginPx=props.apiOptions.isMobile?8:5;return jsxRuntimeExports.jsx("table",{className:css(styles$7.widget)+" "+HACKY_CSS_CLASSNAME,children:jsxRuntimeExports.jsxs("tbody",{children:[showLabels&&jsxRuntimeExports.jsxs("tr",{className:css(styles$7.row),children:[jsxRuntimeExports.jsx("th",{className:css(styles$7.column,styles$7.columnLabel),children:jsxRuntimeExports.jsx(Renderer,{content:labels[0]||"...",linterContext:props.linterContext,strings:strings})}),jsxRuntimeExports.jsx("th",{className:css(styles$7.column,styles$7.columnRight,styles$7.columnLabel),children:jsxRuntimeExports.jsx(Renderer,{content:labels[1]||"...",linterContext:props.linterContext,strings:strings})})]}),jsxRuntimeExports.jsxs("tr",{className:css(styles$7.row),children:[jsxRuntimeExports.jsx("td",{className:css(styles$7.column),children:jsxRuntimeExports.jsx(Sortable,{options:props.userInput.left,layout:"vertical",padding:padding,disabled:!orderMatters,constraints:constraints,onMeasure:dimensions=>setLeftHeight(Math.max(...dimensions.heights)),onChange:changeAndTrack,margin:cellMarginPx,linterContext:props.linterContext,ref:leftSortable})}),jsxRuntimeExports.jsx("td",{className:css(styles$7.column,styles$7.columnRight),children:jsxRuntimeExports.jsx(Sortable,{options:props.userInput.right,layout:"vertical",padding:padding,constraints:constraints,onMeasure:dimensions=>setRightHeight(Math.max(...dimensions.heights)),onChange:changeAndTrack,margin:cellMarginPx,linterContext:props.linterContext,ref:rightSortable})})]})]})})});function getStartUserInput$6(options,problemNum){const shuffled=shuffleMatcher(options,problemNum);return shuffled}function getUserInputFromSerializedState$6(serializedState){return {left:serializedState.left,right:serializedState.right}}const WrappedMatcher=withDependencies(Matcher);var Matcher$1 = {name:"matcher",displayName:"Matcher (two column)",hidden:true,widget:WrappedMatcher,isLintable:true,getStartUserInput: getStartUserInput$6,getUserInputFromSerializedState: getUserInputFromSerializedState$6};const padding=5;const border=`var(--wb-border-width-thin) solid var(--wb-semanticColor-core-border-neutral-strong)`;const styles$7=StyleSheet.create({widget:{paddingBlockStart:padding,maxInlineSize:"100%",minInlineSize:"auto"},row:{border:0},column:{padding:0,border:0},columnRight:{borderInlineStart:border},columnLabel:{fontWeight:"inherit",borderBlockEnd:border,padding:`0 ${padding}px ${padding}px ${padding}px`,textAlign:"center"}});
2075
+ const HACKY_CSS_CLASSNAME="perseus-widget-matcher";const Matcher=forwardRef(function Matcher(props,ref){const{strings}=usePerseusI18n();const dependencies=useDependencies();const leftSortable=useRef(null);const rightSortable=useRef(null);const[leftHeight,setLeftHeight]=useState(0);const[rightHeight,setRightHeight]=useState(0);const[texRendererLoaded,setTexRendererLoaded]=useState(false);useOnMountEffect(()=>{dependencies.analytics.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"matcher",widgetId:props.widgetId}});});useImperativeHandle(ref,()=>({getPromptJSON:()=>getPromptJSON$9(props),moveLeftOptionToIndex:(option,index)=>{leftSortable.current?.moveOptionToIndex(option,index);},moveRightOptionToIndex:(option,index)=>{rightSortable.current?.moveOptionToIndex(option,index);}}));function getUserInputFromSortable(){if(!texRendererLoaded){return {left:[],right:[]}}return {left:leftSortable.current?.getOptions()??[],right:rightSortable.current?.getOptions()??[]}}function changeAndTrack(){props.handleUserInput(getUserInputFromSortable());props.trackInteraction();}if(!texRendererLoaded){const{TeX}=getDependencies();return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(CircularSpinner,{}),jsxRuntimeExports.jsx("div",{style:{display:"none"},children:jsxRuntimeExports.jsx(TeX,{onRender:()=>setTexRendererLoaded(true),children:"1"})})]})}const{labels,padding,orderMatters}=props.options;const showLabels=labels.some(isTruthy);const constraints={height:Math.max(leftHeight,rightHeight)};const cellMarginPx=props.apiOptions.isMobile?8:5;return jsxRuntimeExports.jsx("table",{className:css(styles$7.widget)+" "+HACKY_CSS_CLASSNAME,children:jsxRuntimeExports.jsxs("tbody",{children:[showLabels&&jsxRuntimeExports.jsxs("tr",{className:css(styles$7.row),children:[jsxRuntimeExports.jsx("th",{className:css(styles$7.column,styles$7.columnLabel),children:jsxRuntimeExports.jsx(Renderer,{content:labels[0]||"...",linterContext:props.linterContext,strings:strings})}),jsxRuntimeExports.jsx("th",{className:css(styles$7.column,styles$7.columnRight,styles$7.columnLabel),children:jsxRuntimeExports.jsx(Renderer,{content:labels[1]||"...",linterContext:props.linterContext,strings:strings})})]}),jsxRuntimeExports.jsxs("tr",{className:css(styles$7.row),children:[jsxRuntimeExports.jsx("td",{className:css(styles$7.column),children:jsxRuntimeExports.jsx(Sortable,{options:props.userInput.left,layout:"vertical",padding:padding,disabled:!orderMatters,constraints:constraints,onMeasure:dimensions=>setLeftHeight(Math.max(...dimensions.heights)),onChange:changeAndTrack,margin:cellMarginPx,linterContext:props.linterContext,ref:leftSortable})}),jsxRuntimeExports.jsx("td",{className:css(styles$7.column,styles$7.columnRight),children:jsxRuntimeExports.jsx(Sortable,{options:props.userInput.right,layout:"vertical",padding:padding,constraints:constraints,onMeasure:dimensions=>setRightHeight(Math.max(...dimensions.heights)),onChange:changeAndTrack,margin:cellMarginPx,linterContext:props.linterContext,ref:rightSortable})})]})]})})});function getStartUserInput$6(options,problemNum){const shuffled=shuffleMatcher(options,problemNum);return shuffled}function getUserInputFromSerializedState$6(serializedState){return {left:serializedState.left,right:serializedState.right}}var Matcher$1 = {name:"matcher",displayName:"Matcher (two column)",hidden:true,widget:Matcher,isLintable:true,getStartUserInput: getStartUserInput$6,getUserInputFromSerializedState: getUserInputFromSerializedState$6};const padding=5;const border=`var(--wb-border-width-thin) solid var(--wb-semanticColor-core-border-neutral-strong)`;const styles$7=StyleSheet.create({widget:{paddingBlockStart:padding,maxInlineSize:"100%",minInlineSize:"auto"},row:{border:0},column:{padding:0,border:0},columnRight:{borderInlineStart:border},columnLabel:{fontWeight:"inherit",borderBlockEnd:border,padding:`0 ${padding}px ${padding}px ${padding}px`,textAlign:"center"}});
2071
2076
 
2072
2077
  const getPromptJSON$8=widgetData=>{return {type:"matrix",options:{height:widgetData.options.matrixBoardSize[0],width:widgetData.options.matrixBoardSize[1]},userInput:{answerRows:widgetData.userInput.answers}}};
2073
2078
 
@@ -2079,7 +2084,7 @@ class Measurer extends React.Component{componentDidMount(){this.setupGraphie();}
2079
2084
 
2080
2085
  const getPromptJSON$6=widgetData=>{const{userInput}=widgetData;return {type:"number-line",options:{range:widgetData.options.range,snapDivisions:widgetData.options.snapDivisions},userInput:{numLinePosition:userInput.numLinePosition,numDivisions:userInput.numDivisions,rel:userInput.rel}}};
2081
2086
 
2082
- const MovablePoint=Graphie.MovablePoint;const Line=Graphie.Line;const{assert}=InteractiveUtil;const bound=(x,gt,lt)=>Math.min(Math.max(x,gt),lt);const EN_DASH="–";const horizontalPadding=30;const MOBILE_BOX_WIDTH=288;const DESKTOP_BOX_WIDTH=460;function getNumberLineWidthPx(isMobile){return (isMobile?MOBILE_BOX_WIDTH:DESKTOP_BOX_WIDTH)-horizontalPadding*2}const reverseRel={eq:"eq",ge:"le",gt:"lt",le:"ge",lt:"gt"};const toggleStrictRel={eq:"eq",ge:"gt",gt:"ge",le:"lt",lt:"le"};function formatImproper(n,d){if(d===1){return ""+n}return `\\dfrac{${n}}{${d}}`}function formatMixed(n,d){if(n<0){return "-"+formatMixed(-n,d)}const w=Math.floor(n/d);if(w===0){return formatImproper(n,d)}if(n-w*d===0){return ""+w}return w+formatImproper(n-w*d,d)}function formatNonReduced(n,d,base){const factor=Math.floor(base/d);return formatImproper(n*factor,base)}const _label=(graphie,labelStyle,pos,value,base)=>{value=value||pos;if(labelStyle==="decimal"){return graphie.label([pos,-0.53],Math.round(value*100)/100,"center")}if(labelStyle==="improper"){const frac=KhanMath.toFraction(value);return graphie.label([pos,-0.17],formatImproper(frac[0],frac[1]),"below")}if(labelStyle==="mixed"){const frac=KhanMath.toFraction(value);return graphie.label([pos,-0.17],formatMixed(frac[0],frac[1]),"below")}if(labelStyle==="non-reduced"){const frac=KhanMath.toFraction(value);return graphie.label([pos,-0.17],formatNonReduced(frac[0],frac[1],base),"below")}};const TickMarks=Graphie.createSimpleClass((graphie,props)=>{if(!Number.isFinite(props.tickStep)||props.tickStep<=0){return []}const results=[];const{range,labelRange,labelStyle,labelTicks,tickStep,numDivisions}=props;const leftLabel=labelRange[0]==null?range[0]:labelRange[0];const rightLabel=labelRange[1]==null?range[1]:labelRange[1];let base;if(labelStyle==="non-reduced"){const fractions=[leftLabel,rightLabel];for(let i=0;i<=numDivisions;i++){const x=range[0]+i*tickStep;fractions.push(x);}const getDenom=x=>number.toFraction(x)[1];const denoms=fractions.map(getDenom);base=denoms.reduce((x,y)=>KhanMath.getLCM(x,y));}else {base=undefined;}const highlightedLineStyle={stroke:tokenValue(semanticColor.core.foreground.instructive.default),strokeWidth:3.5};const highlightedTextStyle={color:tokenValue(semanticColor.core.foreground.instructive.default)};const initialTicks=[...Array(Math.round(numDivisions)).keys()].map(index=>range[0]+index*tickStep);const byNumericAscending=(a,b)=>a-b;const allTicks=[...new Set([...initialTicks,leftLabel,rightLabel,...range])].sort(byNumericAscending);allTicks.forEach(tick=>{const tickIsHighlighted=tick===leftLabel||tick===rightLabel;const lineStyle=tickIsHighlighted?highlightedLineStyle:null;const textStyle=tickIsHighlighted?highlightedTextStyle:null;graphie.style(lineStyle,()=>{results.push(graphie.line([tick,-0.2],[tick,.2]));});if(labelTicks||tickIsHighlighted){graphie.style(textStyle,()=>{results.push(_label(graphie,labelStyle,tick,tick,base));});}});return results});function isValid(props){const{range,divisionRange,snapDivisions}=props.options;let initialX=props.userInput?.numLinePosition;initialX=initialX==null?range[0]:initialX;return range[0]<range[1]&&number.sign(initialX-range[0])>=0&&number.sign(initialX-range[1])<=0&&divisionRange[0]<divisionRange[1]&&0<props.userInput?.numDivisions&&0<snapDivisions}const NumberLine=forwardRef(function NumberLine(props,ref){const{strings}=usePerseusI18n();const propsRef=useLatestRef(props);const[numDivisionsEmpty,setNumDivisionsEmpty]=useState(false);const tickStep=getTickStep(props.options.range,props.userInput.numDivisions);const graphieRef=useRef(null);const tickCtrlRef=useRef(null);useOnMountEffect(()=>{props.dependencies.analytics.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"number-line",widgetId:props.widgetId}});});useImperativeHandle(ref,()=>({focus:()=>{if(props.options.isTickCtrl){tickCtrlRef.current?.focus();return true}return false},focusInputPath:path=>{if(path?.length===1){tickCtrlRef.current?.focus();}},blurInputPath:path=>{if(path?.length===1){tickCtrlRef.current?.blur();}},getInputPaths:()=>{if(props.options.isTickCtrl){return [["tick-ctrl"]]}return []},getDOMNodeForPath:inputPath=>{if(inputPath?.length===1){return ReactDOM__default.findDOMNode(tickCtrlRef.current)}return null},getSerializedState:()=>({alignment:props.alignment,static:props.static,range:props.options.range,labelRange:props.options.labelRange,labelStyle:props.options.labelStyle,labelTicks:props.options.labelTicks,divisionRange:props.options.divisionRange,snapDivisions:props.options.snapDivisions,isInequality:props.options.isInequality,showTooltips:props.options.showTooltips,isTickCtrl:props.options.isTickCtrl,numDivisions:props.userInput.numDivisions,numLinePosition:props.userInput.numLinePosition,rel:"ge"}),getPromptJSON(){return getPromptJSON$6(props)},movePosition}));const setupGraphie=useCallback((graphie,options)=>{const latestProps=propsRef.current;if(!isValid(latestProps)){return}const widthInPixels=getNumberLineWidthPx(latestProps.apiOptions.isMobile);const range=options.range;const scale=(range[1]-range[0])/widthInPixels;const buffer=horizontalPadding*scale;const left=range[0]-buffer;const right=range[1]+buffer;const{labelStyle}=latestProps.options;const hasFractionalLabels=labelStyle==="improper"||labelStyle==="mixed"||labelStyle==="non-reduced";const bottom=hasFractionalLabels?-1.5:-1;const top=1;graphie.init({range:[[left,right],[bottom,top]],scale:[1/scale,40],isMobile:latestProps.apiOptions.isMobile});const center=(range[0]+range[1])/2;graphie.line([center,0],[right,0],{arrows:"->"});graphie.line([center,0],[left,0],{arrows:"->"});},[propsRef]);function snapNumLinePosition(tickStep,numLinePosition){const{range,snapDivisions}=props.options;const left=range[0];const right=range[1];const snapX=tickStep/snapDivisions;let x=bound(numLinePosition,left,right);x=left+number.roundTo(x-left,snapX);assert(Number.isFinite(x));return x}function onNumDivisionsChange(numDivisions,cb){numDivisions=Math.round(numDivisions);numDivisions=numDivisions<0?numDivisions*-1:numDivisions;if(numDivisions){const newTickStep=getTickStep(props.options.range,numDivisions);const newNumLinePosition=snapNumLinePosition(newTickStep,props.userInput.numLinePosition);setNumDivisionsEmpty(false);props.handleUserInput({...props.userInput,numDivisions:numDivisions,numLinePosition:newNumLinePosition},cb);}else {setNumDivisionsEmpty(true);cb?.();}}function handleTickCtrlFocus(){props.onFocus(["tick-ctrl"]);}function handleTickCtrlBlur(){props.onBlur(["tick-ctrl"]);}function movePosition(targetPosition){props.handleUserInput({...props.userInput,numLinePosition:targetPosition});props.trackInteraction();}function handleReverse(){const newRel=reverseRel[props.userInput.rel];props.handleUserInput({...props.userInput,rel:newRel});}function handleToggleStrict(){const newRel=toggleStrictRel[props.userInput.rel];props.handleUserInput({...props.userInput,rel:newRel});}function renderNumberLinePoint(){const isOpen=["lt","gt"].includes(props.userInput.rel);const interactiveColor=tokenValue(semanticColor.core.foreground.instructive.default);const staticColor=tokenValue(semanticColor.core.foreground.disabled.strong);const knockoutColor=tokenValue(semanticColor.core.border.knockout.default);const hollowFill=tokenValue(semanticColor.core.foreground.knockout.default);const pointColor=props.static?staticColor:interactiveColor;const normalStyle={fill:isOpen?hollowFill:pointColor,stroke:isOpen?pointColor:knockoutColor,"stroke-width":isOpen?3:1};const highlightStyle={fill:isOpen?hollowFill:interactiveColor,"stroke-width":isOpen?3:1};const mobileDotStyle=props.options.isInequality&&isOpen?{fill:hollowFill,stroke:pointColor,"stroke-width":3}:{};return jsxRuntimeExports.jsx(MovablePoint,{ref:"numberLinePoint",pointSize:6,coord:[props.userInput.numLinePosition,0],constraints:[(coord,prevCoord)=>{return [coord[0],prevCoord[1]]},(coord,prevCoord)=>{const x=snapNumLinePosition(tickStep,coord[0]);return [x,coord[1]]}],shadow:!!props.apiOptions.isMobile&&!isOpen,normalStyle:normalStyle,highlightStyle:highlightStyle,onMove:coord=>{movePosition(coord[0]);},isMobile:props.apiOptions.isMobile,mobileStyleOverride:mobileDotStyle,showTooltips:props.options.showTooltips??false,xOnlyTooltip:true})}function getInequalityEndpoint(){const isGreater=["ge","gt"].includes(props.userInput.rel);const widthInPixels=getNumberLineWidthPx(props.apiOptions.isMobile);const range=props.options.range;const scale=(range[1]-range[0])/widthInPixels;const buffer=horizontalPadding*scale;const left=range[0]-buffer;const right=range[1]+buffer;const end=isGreater?[right,0]:[left,0];return end}function renderInequality(){if(props.options.isInequality){const end=getInequalityEndpoint();const style={arrows:"->",stroke:tokenValue(semanticColor.core.foreground.instructive.default),strokeWidth:3.5};return jsxRuntimeExports.jsx(Line,{start:[props.userInput.numLinePosition,0],end:end,style:style})}return null}function renderGraphie(){const range=props.options.range;return jsxRuntimeExports.jsxs(Graphie,{ref:graphieRef,box:[props.apiOptions.isMobile?MOBILE_BOX_WIDTH:DESKTOP_BOX_WIDTH,80],options:{range,isTickCtrl:props.options.isTickCtrl},onMouseDown:coord=>{const point=graphieRef.current?.movables.numberLinePoint;point?.grab(coord);},setup:setupGraphie,isMobile:props.apiOptions.isMobile,children:[jsxRuntimeExports.jsx(TickMarks,{range:props.options.range,labelTicks:props.options.labelTicks,labelStyle:props.options.labelStyle,labelRange:props.options.labelRange,tickStep:tickStep,numDivisions:props.userInput.numDivisions,isMobile:props.apiOptions.isMobile}),renderInequality(),renderNumberLinePoint()]},props.options.labelStyle)}const{divisionRange,isTickCtrl,isInequality}=props.options;const divRangeString=divisionRange[0]+EN_DASH+divisionRange[1];const invalidNumDivisions=props.userInput?.numDivisions<divisionRange[0]||props.userInput?.numDivisions>divisionRange[1];const inequalityControls=jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("input",{type:"button",className:"simple-button",value:strings.switchDirection,onClick:handleReverse}),jsxRuntimeExports.jsx("input",{type:"button",className:"simple-button",value:["le","ge"].includes(props.userInput?.rel)?strings.circleOpen:strings.circleFilled,onClick:handleToggleStrict})]});let tickCtrl;if(isTickCtrl){const Input=props.apiOptions.customKeypad?SimpleKeypadInput:NumberInput;tickCtrl=jsxRuntimeExports.jsxs("label",{children:[strings.numDivisions," ",jsxRuntimeExports.jsx(Input,{ref:tickCtrlRef,value:numDivisionsEmpty?null:props.userInput?.numDivisions||divisionRange[0],checkValidity:val=>val>=divisionRange[0]&&val<=divisionRange[1],onChange:onNumDivisionsChange,onFocus:handleTickCtrlFocus,onBlur:handleTickCtrlBlur,useArrowKeys:true,keypadElement:props.keypadElement})]});}return jsxRuntimeExports.jsxs("div",{className:"perseus-widget "+"perseus-widget-interactive-number-line",children:[tickCtrl,!isValid(props)?jsxRuntimeExports.jsx("div",{className:"perseus-error",children:"Invalid number line configuration."}):isTickCtrl&&invalidNumDivisions?jsxRuntimeExports.jsx("div",{className:"perseus-error",children:strings.divisions({divRangeString:divRangeString})}):renderGraphie(),!props.static&&isInequality&&inequalityControls]})});function getUserInputFromSerializedState$4(serializedState){return {numDivisions:serializedState.numDivisions,numLinePosition:serializedState.numLinePosition,rel:"eq"}}function getStartNumDivisions(options){const width=options.range[1]-options.range[0];let numDivisions;if(options.numDivisions!=null){numDivisions=options.numDivisions;}else if(options.tickStep!=null){numDivisions=width/options.tickStep;}else {numDivisions=undefined;}return numDivisions}function getTickStep(range,numDivisions){const[min,max]=range;const width=max-min;return width/numDivisions}function getCorrectUserInput$1(options){const numLinePosition=options.correctX!=null?options.correctX:options.range[0];return {numDivisions:getStartNumDivisions(options),numLinePosition,rel:options.isInequality&&options.correctRel||"eq"}}function getStartUserInput$4(options){const numLinePosition=options.initialX!=null?options.initialX:options.range[0];return {numDivisions:getStartNumDivisions(options),numLinePosition,rel:options.isInequality?"ge":"eq"}}const WrappedNumberLine=withDependencies(NumberLine);var NumberLine$1 = {name:"number-line",displayName:"Number line",widget:WrappedNumberLine,getCorrectUserInput: getCorrectUserInput$1,getStartUserInput: getStartUserInput$4,getUserInputFromSerializedState: getUserInputFromSerializedState$4};
2087
+ const MovablePoint=Graphie.MovablePoint;const Line=Graphie.Line;const{assert}=InteractiveUtil;const bound=(x,gt,lt)=>Math.min(Math.max(x,gt),lt);const EN_DASH="–";const horizontalPadding=30;const MOBILE_BOX_WIDTH=288;const DESKTOP_BOX_WIDTH=460;function getNumberLineWidthPx(isMobile){return (isMobile?MOBILE_BOX_WIDTH:DESKTOP_BOX_WIDTH)-horizontalPadding*2}const reverseRel={eq:"eq",ge:"le",gt:"lt",le:"ge",lt:"gt"};const toggleStrictRel={eq:"eq",ge:"gt",gt:"ge",le:"lt",lt:"le"};function formatImproper(n,d){if(d===1){return ""+n}return `\\dfrac{${n}}{${d}}`}function formatMixed(n,d){if(n<0){return "-"+formatMixed(-n,d)}const w=Math.floor(n/d);if(w===0){return formatImproper(n,d)}if(n-w*d===0){return ""+w}return w+formatImproper(n-w*d,d)}function formatNonReduced(n,d,base){const factor=Math.floor(base/d);return formatImproper(n*factor,base)}const _label=(graphie,labelStyle,pos,value,base)=>{value=value||pos;if(labelStyle==="decimal"){return graphie.label([pos,-0.53],Math.round(value*100)/100,"center")}if(labelStyle==="improper"){const frac=KhanMath.toFraction(value);return graphie.label([pos,-0.17],formatImproper(frac[0],frac[1]),"below")}if(labelStyle==="mixed"){const frac=KhanMath.toFraction(value);return graphie.label([pos,-0.17],formatMixed(frac[0],frac[1]),"below")}if(labelStyle==="non-reduced"){const frac=KhanMath.toFraction(value);return graphie.label([pos,-0.17],formatNonReduced(frac[0],frac[1],base),"below")}};const TickMarks=Graphie.createSimpleClass((graphie,props)=>{if(!Number.isFinite(props.tickStep)||props.tickStep<=0){return []}const results=[];const{range,labelRange,labelStyle,labelTicks,tickStep,numDivisions}=props;const leftLabel=labelRange[0]==null?range[0]:labelRange[0];const rightLabel=labelRange[1]==null?range[1]:labelRange[1];let base;if(labelStyle==="non-reduced"){const fractions=[leftLabel,rightLabel];for(let i=0;i<=numDivisions;i++){const x=range[0]+i*tickStep;fractions.push(x);}const getDenom=x=>number.toFraction(x)[1];const denoms=fractions.map(getDenom);base=denoms.reduce((x,y)=>KhanMath.getLCM(x,y));}else {base=undefined;}const highlightedLineStyle={stroke:tokenValue(semanticColor.core.foreground.instructive.default),strokeWidth:3.5};const highlightedTextStyle={color:tokenValue(semanticColor.core.foreground.instructive.default)};const initialTicks=[...Array(Math.round(numDivisions)).keys()].map(index=>range[0]+index*tickStep);const byNumericAscending=(a,b)=>a-b;const allTicks=[...new Set([...initialTicks,leftLabel,rightLabel,...range])].sort(byNumericAscending);allTicks.forEach(tick=>{const tickIsHighlighted=tick===leftLabel||tick===rightLabel;const lineStyle=tickIsHighlighted?highlightedLineStyle:null;const textStyle=tickIsHighlighted?highlightedTextStyle:null;graphie.style(lineStyle,()=>{results.push(graphie.line([tick,-0.2],[tick,.2]));});if(labelTicks||tickIsHighlighted){graphie.style(textStyle,()=>{results.push(_label(graphie,labelStyle,tick,tick,base));});}});return results});function isValid(props){const{range,divisionRange,snapDivisions}=props.options;let initialX=props.userInput?.numLinePosition;initialX=initialX==null?range[0]:initialX;return range[0]<range[1]&&number.sign(initialX-range[0])>=0&&number.sign(initialX-range[1])<=0&&divisionRange[0]<divisionRange[1]&&0<props.userInput?.numDivisions&&0<snapDivisions}const NumberLine=forwardRef(function NumberLine(props,ref){const{strings}=usePerseusI18n();const dependencies=useDependencies();const propsRef=useLatestRef(props);const[numDivisionsEmpty,setNumDivisionsEmpty]=useState(false);const tickStep=getTickStep(props.options.range,props.userInput.numDivisions);const graphieRef=useRef(null);const tickCtrlRef=useRef(null);useOnMountEffect(()=>{dependencies.analytics.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"number-line",widgetId:props.widgetId}});});useImperativeHandle(ref,()=>({focus:()=>{if(props.options.isTickCtrl){tickCtrlRef.current?.focus();return true}return false},focusInputPath:path=>{if(path?.length===1){tickCtrlRef.current?.focus();}},blurInputPath:path=>{if(path?.length===1){tickCtrlRef.current?.blur();}},getInputPaths:()=>{if(props.options.isTickCtrl){return [["tick-ctrl"]]}return []},getDOMNodeForPath:inputPath=>{if(inputPath?.length===1){return ReactDOM__default.findDOMNode(tickCtrlRef.current)}return null},getSerializedState:()=>({alignment:props.alignment,static:props.static,range:props.options.range,labelRange:props.options.labelRange,labelStyle:props.options.labelStyle,labelTicks:props.options.labelTicks,divisionRange:props.options.divisionRange,snapDivisions:props.options.snapDivisions,isInequality:props.options.isInequality,showTooltips:props.options.showTooltips,isTickCtrl:props.options.isTickCtrl,numDivisions:props.userInput.numDivisions,numLinePosition:props.userInput.numLinePosition,rel:"ge"}),getPromptJSON(){return getPromptJSON$6(props)},movePosition}));const setupGraphie=useCallback((graphie,options)=>{const latestProps=propsRef.current;if(!isValid(latestProps)){return}const widthInPixels=getNumberLineWidthPx(latestProps.apiOptions.isMobile);const range=options.range;const scale=(range[1]-range[0])/widthInPixels;const buffer=horizontalPadding*scale;const left=range[0]-buffer;const right=range[1]+buffer;const{labelStyle}=latestProps.options;const hasFractionalLabels=labelStyle==="improper"||labelStyle==="mixed"||labelStyle==="non-reduced";const bottom=hasFractionalLabels?-1.5:-1;const top=1;graphie.init({range:[[left,right],[bottom,top]],scale:[1/scale,40],isMobile:latestProps.apiOptions.isMobile});const center=(range[0]+range[1])/2;graphie.line([center,0],[right,0],{arrows:"->"});graphie.line([center,0],[left,0],{arrows:"->"});},[propsRef]);function snapNumLinePosition(tickStep,numLinePosition){const{range,snapDivisions}=props.options;const left=range[0];const right=range[1];const snapX=tickStep/snapDivisions;let x=bound(numLinePosition,left,right);x=left+number.roundTo(x-left,snapX);assert(Number.isFinite(x));return x}function onNumDivisionsChange(numDivisions,cb){numDivisions=Math.round(numDivisions);numDivisions=numDivisions<0?numDivisions*-1:numDivisions;if(numDivisions){const newTickStep=getTickStep(props.options.range,numDivisions);const newNumLinePosition=snapNumLinePosition(newTickStep,props.userInput.numLinePosition);setNumDivisionsEmpty(false);props.handleUserInput({...props.userInput,numDivisions:numDivisions,numLinePosition:newNumLinePosition},cb);}else {setNumDivisionsEmpty(true);cb?.();}}function handleTickCtrlFocus(){props.onFocus(["tick-ctrl"]);}function handleTickCtrlBlur(){props.onBlur(["tick-ctrl"]);}function movePosition(targetPosition){props.handleUserInput({...props.userInput,numLinePosition:targetPosition});props.trackInteraction();}function handleReverse(){const newRel=reverseRel[props.userInput.rel];props.handleUserInput({...props.userInput,rel:newRel});}function handleToggleStrict(){const newRel=toggleStrictRel[props.userInput.rel];props.handleUserInput({...props.userInput,rel:newRel});}function renderNumberLinePoint(){const isOpen=["lt","gt"].includes(props.userInput.rel);const interactiveColor=tokenValue(semanticColor.core.foreground.instructive.default);const staticColor=tokenValue(semanticColor.core.foreground.disabled.strong);const knockoutColor=tokenValue(semanticColor.core.border.knockout.default);const hollowFill=tokenValue(semanticColor.core.foreground.knockout.default);const pointColor=props.static?staticColor:interactiveColor;const normalStyle={fill:isOpen?hollowFill:pointColor,stroke:isOpen?pointColor:knockoutColor,"stroke-width":isOpen?3:1};const highlightStyle={fill:isOpen?hollowFill:interactiveColor,"stroke-width":isOpen?3:1};const mobileDotStyle=props.options.isInequality&&isOpen?{fill:hollowFill,stroke:pointColor,"stroke-width":3}:{};return jsxRuntimeExports.jsx(MovablePoint,{ref:"numberLinePoint",pointSize:6,coord:[props.userInput.numLinePosition,0],constraints:[(coord,prevCoord)=>{return [coord[0],prevCoord[1]]},(coord,prevCoord)=>{const x=snapNumLinePosition(tickStep,coord[0]);return [x,coord[1]]}],shadow:!!props.apiOptions.isMobile&&!isOpen,normalStyle:normalStyle,highlightStyle:highlightStyle,onMove:coord=>{movePosition(coord[0]);},isMobile:props.apiOptions.isMobile,mobileStyleOverride:mobileDotStyle,showTooltips:props.options.showTooltips??false,xOnlyTooltip:true})}function getInequalityEndpoint(){const isGreater=["ge","gt"].includes(props.userInput.rel);const widthInPixels=getNumberLineWidthPx(props.apiOptions.isMobile);const range=props.options.range;const scale=(range[1]-range[0])/widthInPixels;const buffer=horizontalPadding*scale;const left=range[0]-buffer;const right=range[1]+buffer;const end=isGreater?[right,0]:[left,0];return end}function renderInequality(){if(props.options.isInequality){const end=getInequalityEndpoint();const style={arrows:"->",stroke:tokenValue(semanticColor.core.foreground.instructive.default),strokeWidth:3.5};return jsxRuntimeExports.jsx(Line,{start:[props.userInput.numLinePosition,0],end:end,style:style})}return null}function renderGraphie(){const range=props.options.range;return jsxRuntimeExports.jsxs(Graphie,{ref:graphieRef,box:[props.apiOptions.isMobile?MOBILE_BOX_WIDTH:DESKTOP_BOX_WIDTH,80],options:{range,isTickCtrl:props.options.isTickCtrl},onMouseDown:coord=>{const point=graphieRef.current?.movables.numberLinePoint;point?.grab(coord);},setup:setupGraphie,isMobile:props.apiOptions.isMobile,children:[jsxRuntimeExports.jsx(TickMarks,{range:props.options.range,labelTicks:props.options.labelTicks,labelStyle:props.options.labelStyle,labelRange:props.options.labelRange,tickStep:tickStep,numDivisions:props.userInput.numDivisions,isMobile:props.apiOptions.isMobile}),renderInequality(),renderNumberLinePoint()]},props.options.labelStyle)}const{divisionRange,isTickCtrl,isInequality}=props.options;const divRangeString=divisionRange[0]+EN_DASH+divisionRange[1];const invalidNumDivisions=props.userInput?.numDivisions<divisionRange[0]||props.userInput?.numDivisions>divisionRange[1];const inequalityControls=jsxRuntimeExports.jsxs("div",{children:[jsxRuntimeExports.jsx("input",{type:"button",className:"simple-button",value:strings.switchDirection,onClick:handleReverse}),jsxRuntimeExports.jsx("input",{type:"button",className:"simple-button",value:["le","ge"].includes(props.userInput?.rel)?strings.circleOpen:strings.circleFilled,onClick:handleToggleStrict})]});let tickCtrl;if(isTickCtrl){const Input=props.apiOptions.customKeypad?SimpleKeypadInput:NumberInput;tickCtrl=jsxRuntimeExports.jsxs("label",{children:[strings.numDivisions," ",jsxRuntimeExports.jsx(Input,{ref:tickCtrlRef,value:numDivisionsEmpty?null:props.userInput?.numDivisions||divisionRange[0],checkValidity:val=>val>=divisionRange[0]&&val<=divisionRange[1],onChange:onNumDivisionsChange,onFocus:handleTickCtrlFocus,onBlur:handleTickCtrlBlur,useArrowKeys:true,keypadElement:props.keypadElement})]});}return jsxRuntimeExports.jsxs("div",{className:"perseus-widget "+"perseus-widget-interactive-number-line",children:[tickCtrl,!isValid(props)?jsxRuntimeExports.jsx("div",{className:"perseus-error",children:"Invalid number line configuration."}):isTickCtrl&&invalidNumDivisions?jsxRuntimeExports.jsx("div",{className:"perseus-error",children:strings.divisions({divRangeString:divRangeString})}):renderGraphie(),!props.static&&isInequality&&inequalityControls]})});function getUserInputFromSerializedState$4(serializedState){return {numDivisions:serializedState.numDivisions,numLinePosition:serializedState.numLinePosition,rel:"eq"}}function getStartNumDivisions(options){const width=options.range[1]-options.range[0];let numDivisions;if(options.numDivisions!=null){numDivisions=options.numDivisions;}else if(options.tickStep!=null){numDivisions=width/options.tickStep;}else {numDivisions=undefined;}return numDivisions}function getTickStep(range,numDivisions){const[min,max]=range;const width=max-min;return width/numDivisions}function getCorrectUserInput$1(options){const numLinePosition=options.correctX!=null?options.correctX:options.range[0];return {numDivisions:getStartNumDivisions(options),numLinePosition,rel:options.isInequality&&options.correctRel||"eq"}}function getStartUserInput$4(options){const numLinePosition=options.initialX!=null?options.initialX:options.range[0];return {numDivisions:getStartNumDivisions(options),numLinePosition,rel:options.isInequality?"ge":"eq"}}var NumberLine$1 = {name:"number-line",displayName:"Number line",widget:NumberLine,getCorrectUserInput: getCorrectUserInput$1,getStartUserInput: getStartUserInput$4,getUserInputFromSerializedState: getUserInputFromSerializedState$4};
2083
2088
 
2084
2089
  const getPromptJSON$5=widgetData=>{return {type:"orderer",options:{options:widgetData.options.options.map(option=>option.content)},userInput:{values:widgetData.userInput.current}}};
2085
2090
 
@@ -2095,7 +2100,7 @@ class Plotter extends React.Component{componentDidMount(){this._isMounted=true;t
2095
2100
 
2096
2101
  const getPromptJSON$2=()=>{return getUnsupportedPromptJSON("python-program")};
2097
2102
 
2098
- function getUrlFromProgramID(programID){return `/python-program/${programID}/embedded`}const PythonProgram=forwardRef(function PythonProgram(props,ref){const{strings,locale}=usePerseusI18n();const{programID,height}=props.options;const{dependencies}=props;useImperativeHandle(ref,()=>({getPromptJSON:()=>{return getPromptJSON$2()}}));const url=dependencies.generateUrl({url:getUrlFromProgramID(programID),context:"python_program:program_url",kaLocale:locale});const iframeStyle={height,width:"100%"};const sandboxOptions=["allow-popups","allow-same-origin","allow-scripts","allow-top-navigation"].join(" ");return jsxRuntimeExports.jsx(View,{style:styles$5.container,children:jsxRuntimeExports.jsx("iframe",{title:strings.pythonProgram,sandbox:sandboxOptions,src:url,style:iframeStyle,allowFullScreen:true})})});const styles$5=StyleSheet.create({container:{margin:"auto",width:"100%"}});const WrappedPythonProgram=withDependencies(PythonProgram);var PythonProgram$1 = {name:"python-program",displayName:"Python Program",widget:WrappedPythonProgram};
2103
+ function getUrlFromProgramID(programID){return `/python-program/${programID}/embedded`}const PythonProgram=forwardRef(function PythonProgram(props,ref){const{strings,locale}=usePerseusI18n();const dependencies=useDependencies();const{programID,height}=props.options;useImperativeHandle(ref,()=>({getPromptJSON:()=>{return getPromptJSON$2()}}));const url=dependencies.generateUrl({url:getUrlFromProgramID(programID),context:"python_program:program_url",kaLocale:locale});const iframeStyle={height,width:"100%"};const sandboxOptions=["allow-popups","allow-same-origin","allow-scripts","allow-top-navigation"].join(" ");return jsxRuntimeExports.jsx(View,{style:styles$5.container,children:jsxRuntimeExports.jsx("iframe",{title:strings.pythonProgram,sandbox:sandboxOptions,src:url,style:iframeStyle,allowFullScreen:true})})});const styles$5=StyleSheet.create({container:{margin:"auto",width:"100%"}});var PythonProgram$1 = {name:"python-program",displayName:"Python Program",widget:PythonProgram};
2099
2104
 
2100
2105
  const getPromptJSON$1=userInput=>{return {type:"sorter",userInput:{values:userInput.options,changed:userInput.changed}}};
2101
2106
 
@@ -2117,7 +2122,7 @@ var extraWidgets = [Blank,CSProgram$1,Categorizer$1,Definition$1,DeprecatedStand
2117
2122
 
2118
2123
  const init=function(){registerWidgets(basicWidgets);registerWidgets(extraWidgets);replaceDeprecatedWidgets();};
2119
2124
 
2120
- const libName="@khanacademy/perseus";const libVersion="87.0.0";addLibraryVersionToPerseusDebug(libName,libVersion);
2125
+ const libName="@khanacademy/perseus";const libVersion="87.1.0";addLibraryVersionToPerseusDebug(libName,libVersion);
2121
2126
 
2122
2127
  const apiVersion={major:12,minor:0};
2123
2128