@khanacademy/perseus 87.1.0 → 87.1.2

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 (24) hide show
  1. package/dist/components/lint.d.ts +9 -28
  2. package/dist/components/linter-link.d.ts +34 -0
  3. package/dist/es/index.js +15 -14
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/index.js +17 -14
  6. package/dist/index.js.map +1 -1
  7. package/dist/styles/constants.d.ts +0 -3
  8. package/dist/widgets/interactive-graphs/graphs/strings/absolute-value.d.ts +1 -1
  9. package/dist/widgets/interactive-graphs/graphs/strings/angle.d.ts +1 -1
  10. package/dist/widgets/interactive-graphs/graphs/strings/announcement.d.ts +1 -1
  11. package/dist/widgets/interactive-graphs/graphs/strings/circle.d.ts +1 -1
  12. package/dist/widgets/interactive-graphs/graphs/strings/coord-quadrant.d.ts +1 -1
  13. package/dist/widgets/interactive-graphs/graphs/strings/exponential.d.ts +1 -1
  14. package/dist/widgets/interactive-graphs/graphs/strings/linear-system.d.ts +1 -1
  15. package/dist/widgets/interactive-graphs/graphs/strings/logarithm.d.ts +1 -1
  16. package/dist/widgets/interactive-graphs/graphs/strings/point.d.ts +1 -1
  17. package/dist/widgets/interactive-graphs/graphs/strings/polygon.d.ts +1 -1
  18. package/dist/widgets/interactive-graphs/graphs/strings/quadratic.d.ts +1 -1
  19. package/dist/widgets/interactive-graphs/graphs/strings/ray.d.ts +1 -1
  20. package/dist/widgets/interactive-graphs/graphs/strings/segment.d.ts +1 -1
  21. package/dist/widgets/interactive-graphs/graphs/strings/sinusoid.d.ts +1 -1
  22. package/dist/widgets/interactive-graphs/graphs/strings/tangent.d.ts +1 -1
  23. package/dist/widgets/interactive-graphs/graphs/strings/vector.d.ts +1 -1
  24. package/package.json +9 -9
@@ -1,10 +1,5 @@
1
1
  import * as React from "react";
2
- declare enum Severity {
3
- Error = 1,
4
- Warning = 2,
5
- Recommendation = 3,
6
- OfflineReportingOnly = 4
7
- }
2
+ import type { Severity } from "./linter-link";
8
3
  type Props = {
9
4
  /** The children are the linty content we're highlighting */
10
5
  children: React.ReactNode;
@@ -27,38 +22,24 @@ type Props = {
27
22
  */
28
23
  severity?: Severity;
29
24
  };
30
- type State = {
31
- tooltipAbove: boolean;
32
- };
33
25
  /**
34
26
  * This component renders "lint" nodes in a markdown parse tree. Lint nodes
35
27
  * are inserted into the tree by the Perseus linter (see
36
28
  * perseus-linter/src/index).
37
29
  *
38
- * This component serves multiple purposes
39
- *
40
- * 1) It renders a small circle in the right margin to indicate that there
41
- * is lint on (or near) that line.
30
+ * 1) This component renders a small circle IconButton in the right margin
31
+ * to indicate that there is lint on (or near) that line.
42
32
  *
43
- * 2) The area around the circle is hoverable: when the mouse moves over it
44
- * the linty content is highlighted and a tooltip is displayed that explains
45
- * what the problem is.
33
+ * 2) When the mouse moves over the circle IconButton, the linty content
34
+ * is highlighted and a tooltip is displayed that explains what the problem is.
46
35
  *
47
- * 3) The hoverable area is also an HTML <a> tag. Clicking on it opens
48
- * a new tab and links to additional details about the specific lint rule.
36
+ * 3) Clicking on the IconButton opens a new tab and links to additional
37
+ * details about the specific lint rule.
49
38
  *
50
39
  * The CSS required to position the circles in the right margin is tricky
51
40
  * and it does not always work perfectly. When lint occurs on a block element
52
41
  * that has a right margin (like anything blockquoted) the circle will appear
53
- * to the left of where it belongs. And if there is more
42
+ * to the left of where it belongs.
54
43
  **/
55
- declare class Lint extends React.Component<Props, State> {
56
- _positionTimeout: number | undefined;
57
- state: State;
58
- componentDidMount(): void;
59
- componentWillUnmount(): void;
60
- getPosition: () => void;
61
- renderLink: (arg1: any) => React.ReactElement;
62
- render(): React.ReactNode;
63
- }
44
+ declare function Lint({ children, inline, message, ruleName, insideTable, blockHighlight, severity, }: Props): React.ReactElement;
64
45
  export default Lint;
@@ -0,0 +1,34 @@
1
+ import * as React from "react";
2
+ import type { CSSProperties } from "aphrodite";
3
+ /**
4
+ * How important a lint message is for the editor. These values
5
+ * mirror `Rule.Severity` in perseus-linter, which is where the severities
6
+ * on lint nodes originate.
7
+ *
8
+ * perseus-linter Severity also includes Recommendation = 3 and
9
+ * OfflineReportingOnly = 4, but those are not used here.
10
+ */
11
+ export declare enum Severity {
12
+ Error = 1,
13
+ Warning = 2
14
+ }
15
+ type Props = {
16
+ /** This is the text that appears in the tooltip */
17
+ message: string;
18
+ /** This is used as the fragment id (hash) in the URL of the link */
19
+ ruleName: string;
20
+ /**
21
+ * Determines the icon and the label at the top of the tooltip. Anything
22
+ * less severe than a warning is presented as a recommendation.
23
+ */
24
+ severity?: Severity;
25
+ /** Positioning styles, supplied by the caller. */
26
+ style: CSSProperties;
27
+ };
28
+ /**
29
+ * The indicator for a single lint message: a small icon button that links to
30
+ * the documentation for the rule that fired, wrapped in a Wonder Blocks
31
+ * tooltip that names the severity and explains the problem.
32
+ **/
33
+ export default function LinterLink({ message, ruleName, severity, style, }: Props): React.ReactElement;
34
+ export {};
package/dist/es/index.js CHANGED
@@ -21,7 +21,7 @@ import * as ReactDOM from 'react-dom';
21
21
  import ReactDOM__default, { createPortal } from 'react-dom';
22
22
  import _, { debounce as debounce$1 } from 'underscore';
23
23
  import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
24
- import { point, KhanMath, number, vector as vector$3, geometry, angles, coefficients } from '@khanacademy/kmath';
24
+ import { point, KhanMath, number, vector as vector$3, geometry, angles, coefficients, line } from '@khanacademy/kmath';
25
25
  import { entries, UnreachableCaseError, isTruthy, keys } from '@khanacademy/wonder-stuff-core';
26
26
  import $ from 'jquery';
27
27
  import { ModalDialog, ModalPanel, ModalLauncher, FlexibleDialog } from '@khanacademy/wonder-blocks-modal';
@@ -31,6 +31,8 @@ import { parseGIF, decompressFrames } from 'gifuct-js';
31
31
  import { withActionScheduler, useTimeout } from '@khanacademy/wonder-blocks-timing';
32
32
  import SimpleMarkdown from '@khanacademy/simple-markdown';
33
33
  import { pureMarkdownRules, traverseContent } from '@khanacademy/pure-markdown';
34
+ import dotOutlineIcon from '@phosphor-icons/core/fill/dot-outline-fill.svg';
35
+ import warningCircleIcon from '@phosphor-icons/core/fill/warning-circle-fill.svg';
34
36
  import { announceMessage } from '@khanacademy/wonder-blocks-announcer';
35
37
  import caretLeftIcon from '@phosphor-icons/core/regular/caret-left.svg';
36
38
  import caretRightIcon from '@phosphor-icons/core/regular/caret-right.svg';
@@ -44,7 +46,7 @@ import Button from '@khanacademy/wonder-blocks-button';
44
46
  import caretDown from '@phosphor-icons/core/regular/caret-down.svg';
45
47
  import caretUp from '@phosphor-icons/core/regular/caret-up.svg';
46
48
  import { LabeledField } from '@khanacademy/wonder-blocks-labeled-field';
47
- import warningCircleIcon from '@phosphor-icons/core/regular/warning-circle.svg';
49
+ import warningCircleIcon$1 from '@phosphor-icons/core/regular/warning-circle.svg';
48
50
  import invariant from 'tiny-invariant';
49
51
  import { vec, Coordinates, Ellipse, Plot as Plot$2, Line as Line$4, Point as Point$2, Polygon, useTransformContext, Text as Text$1, Polyline, Mafs } from 'mafs';
50
52
  import * as KAS from '@khanacademy/kas';
@@ -1415,7 +1417,7 @@ var a11y = StyleSheet.create({srOnly:{border:0,clip:"rect(0,0,0,0)",height:1,mar
1415
1417
 
1416
1418
  const debounce=(func,delay)=>{let timer=null;return (...args)=>{if(timer){clearTimeout(timer);}timer=window.setTimeout(()=>{func(...args);},delay);}};
1417
1419
 
1418
- class InnerMathInput extends React.Component{componentDidMount(){this.mathField()?.latex(this.props.value);}componentDidUpdate(prevProps){if(prevProps.value!==this.props.value){if(this.state.focused){return}const field=this.mathField();if(!field){return}const current=field.latex();if(this.props.value!==current){field.latex(this.props.value);}}}openKeypad(){if(this.props.buttonsVisible==="never"){return}this.setState({keypadOpen:true});}closeKeypad(){this.setState({keypadOpen:false});}render(){let className=classNames$2({"perseus-math-input":true,"mq-editable-field":true,"mq-math-mode":true});const popoverContentUniqueId=v4().slice(0,8);if(this.props.className){className=className+" "+this.props.className;}return jsxRuntimeExports.jsx(View,{style:[styles$B.outerWrapper,this.state.focused&&styles$B.wrapperFocused],children:jsxRuntimeExports.jsxs("div",{style:{display:"flex",alignItems:"center",padding:1},onClick:e=>{e.stopPropagation();const mathField=this.mathField();if(!mathField){return}this.setState({cursorContext:getCursorContext(mathField)});},children:[jsxRuntimeExports.jsx("span",{className:className,ref:ref=>this.__mathFieldWrapperRef=ref,onFocus:()=>this.focus(),onBlur:()=>this.blur()}),jsxRuntimeExports.jsx(Popover,{opened:this.state.keypadOpen,dismissEnabled:true,rootBoundary:"document","aria-label":this.context.strings.mathInputTitle,"aria-describedby":`popover-content-${popoverContentUniqueId}`,onClose:()=>this.closeKeypad(),content:()=>jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(Heading,{size:"large",id:`popover-content-${popoverContentUniqueId}`,style:a11y.srOnly,children:this.context.strings.mathInputDescription}),jsxRuntimeExports.jsx(PopoverContentCore,{style:styles$B.popoverContent,children:jsxRuntimeExports.jsx(DesktopKeypad,{onAnalyticsEvent:this.props.onAnalyticsEvent,extraKeys:this.props.extraKeys,onClickKey:this.handleKeypadPress,cursorContext:this.state.cursorContext,convertDotToTimes:this.props.convertDotToTimes,...this.props.keypadButtonSets??mapButtonSets(this.props?.buttonSets),showDismiss:true})})]}),children:this.props.buttonsVisible==="never"?jsxRuntimeExports.jsx(MathInputIcon,{hovered:false,focused:false,active:false,pressed:false}):jsxRuntimeExports.jsx(Clickable,{"aria-label":this.state.keypadOpen?this.context.strings.closeKeypad:this.context.strings.openKeypad,role:"button",hideDefaultFocusRing:true,onClick:()=>this.state.keypadOpen?this.closeKeypad():this.openKeypad(),children:props=>jsxRuntimeExports.jsx(MathInputIcon,{active:this.state.keypadOpen,...props})})})]})})}constructor(...args){super(...args),this.__mathFieldWrapperRef=null,this.__mathField=null,this.state={focused:false,keypadOpen:this.props.buttonsVisible==="always",cursorContext:CursorContext.NONE},this.insert=value=>{const input=this.mathField();const{locale}=this.context;const customKeyTranslator={...getKeyTranslator(locale,this.context.strings),FRAC:mathQuill=>{const contents=mathQuill.latex();mathQuill.typedText("/");if(mathQuill.latex()===contents){mathQuill.cmd("\\frac");}}};const inputModifier=customKeyTranslator[value];if(inputModifier){inputModifier(input,value);input?.focus();return}if(typeof value==="function"){value(input);}else if(value[0]==="\\"){input?.cmd(value).focus();}else {input?.write(value).focus();}input?.focus();},this.mathField=()=>{if(!this.__mathField&&this.__mathFieldWrapperRef){const{locale}=this.context;this.__mathField=createMathField(this.__mathFieldWrapperRef,locale,this.props.mathInputStrings,baseConfig=>({...baseConfig,handlers:{edit:debounce(mathField=>{let value=mathField.latex();value=value.replace(/<>/g,"\\ne");if(convertDotToTimesByLocale(locale,this.props.convertDotToTimes)){value=value.replace(/\\cdot/g,"\\times");const left=mathField.cursor()[mathQuillInstance.L];if(left&&left.ctrlSeq==="\\cdot "){mathField.controller().backspace();mathField.cmd("\\times");}}else {value=value.replace(/\\times/g,"\\cdot");}if(this.props.value!==value){this.props.onChange(value);}this.setState({cursorContext:getCursorContext(mathField)});},100),enter:()=>{if(this.__mathFieldWrapperRef){this.__mathFieldWrapperRef.dispatchEvent(new Event("submit",{bubbles:true,cancelable:true}));}},upOutOf:mathField=>{mathField.typedText("^");}}}));}this.__mathField?.setAriaLabel(this.props.ariaLabel);return this.__mathField},this.focus=()=>{this.mathField()?.focus();this.setState({focused:true});},this.blur=()=>this.setState({focused:false}),this.handleKeypadPress=(key,e)=>{if(key==="DISMISS"){this.closeKeypad();return}const{locale}=this.context;const translator=getKeyTranslator(locale,this.context.strings)[key];const mathField=this.mathField();if(mathField){if(translator){translator(mathField,key);}this.setState({cursorContext:getCursorContext(mathField)});}if(e?.type==="click"){this.focus();}};}}InnerMathInput.contextType=PerseusI18nContext;InnerMathInput.defaultProps={value:"",convertDotToTimes:false};class MathInput extends React.Component{blur(){this.inputRef.current?.blur();}focus(){this.inputRef.current?.focus();}insert(value){this.inputRef.current?.insert(value);}render(){return jsxRuntimeExports.jsx(InnerMathInput,{...this.props,ref:this.inputRef,mathInputStrings:this.context.strings})}constructor(...args){super(...args),this.inputRef=React.createRef();}}MathInput.contextType=MathInputI18nContext;MathInput.defaultProps={ariaLabel:"Math input"};const MathInputIcon=({hovered,focused,pressed,active})=>{let fillColor;switch(true){case focused||pressed||active:fillColor=semanticColor.core.foreground.knockout.default;break;case hovered:fillColor=semanticColor.core.foreground.instructive.strong;break;default:fillColor=semanticColor.core.foreground.neutral.default;break}let dynamicClass;switch(true){case focused||pressed:dynamicClass=styles$B.iconActive;break;case active:dynamicClass=styles$B.iconExpanded;break;case hovered:dynamicClass=styles$B.iconHovered;break;default:dynamicClass=styles$B.iconInactive;}return jsxRuntimeExports.jsx(View,{style:[styles$B.iconContainer,dynamicClass],children:jsxRuntimeExports.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",fill:fillColor,viewBox:"0 0 256 256",children:jsxRuntimeExports.jsx("path",{d:"M112,72a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16h64A8,8,0,0,1,112,72Zm-8,104H80V152a8,8,0,0,0-16,0v24H40a8,8,0,0,0,0,16H64v24a8,8,0,0,0,16,0V192h24a8,8,0,0,0,0-16Zm48,0h64a8,8,0,0,0,0-16H152a8,8,0,0,0,0,16Zm64,16H152a8,8,0,0,0,0,16h64a8,8,0,0,0,0-16Zm-61.66-90.34a8,8,0,0,0,11.32,0L184,83.31l18.34,18.35a8,8,0,0,0,11.32-11.32L195.31,72l18.35-18.34a8,8,0,0,0-11.32-11.32L184,60.69,165.66,42.34a8,8,0,0,0-11.32,11.32L172.69,72,154.34,90.34A8,8,0,0,0,154.34,101.66Z"})})})};const mapButtonSets=buttonSets=>{const keypadButtonSets={};if(!buttonSets){return keypadButtonSets}buttonSets.forEach(buttonSet=>{switch(buttonSet){case "advanced relations":keypadButtonSets.advancedRelations=true;break;case "basic relations":keypadButtonSets.basicRelations=true;break;case "basic+div":keypadButtonSets.divisionKey=true;break;case "logarithms":keypadButtonSets.logarithms=true;break;case "prealgebra":keypadButtonSets.preAlgebra=true;break;case "trig":keypadButtonSets.trigonometry=true;break;case "scientific":keypadButtonSets.scientific=true;break;}});return keypadButtonSets};const inputFocused={borderColor:semanticColor.core.border.instructive.default};const styles$B=StyleSheet.create({iconContainer:{display:"flex",justifyContent:"center",height:"100%",padding:sizing.size_060,borderRadius:border$1.radius.radius_040,borderWidth:border$1.width.medium,borderStyle:"solid"},iconInactive:{borderColor:semanticColor.core.transparent,backgroundColor:semanticColor.core.background.neutral.subtle},iconActive:{borderColor:semanticColor.focus.outer,boxShadow:`inset 0 0 0 ${border$1.width.medium} ${semanticColor.focus.inner}`,backgroundColor:semanticColor.core.background.neutral.default},iconExpanded:{borderColor:semanticColor.core.transparent,backgroundColor:semanticColor.core.background.neutral.default},iconHovered:{borderColor:semanticColor.core.transparent,backgroundColor:semanticColor.core.background.neutral.subtle},outerWrapper:{display:"inline-block",borderStyle:"solid",borderWidth:border$1.width.thin,borderColor:semanticColor.core.border.neutral.default,borderRadius:border$1.radius.radius_040,background:semanticColor.core.background.base.default,":hover":inputFocused},wrapperFocused:inputFocused,popoverContent:{padding:0,paddingBlockEnd:sizing.size_060,maxInlineSize:"initial"}});
1420
+ class InnerMathInput extends React.Component{componentDidMount(){this.mathField()?.latex(this.props.value);}componentDidUpdate(prevProps){if(prevProps.value!==this.props.value){if(this.state.focused){return}const field=this.mathField();if(!field){return}const current=field.latex();if(this.props.value!==current){field.latex(this.props.value);}}}openKeypad(){if(this.props.buttonsVisible==="never"){return}this.setState({keypadOpen:true});}closeKeypad(){this.setState({keypadOpen:false});}render(){let className=classNames$2({"perseus-math-input":true,"mq-editable-field":true,"mq-math-mode":true});const popoverContentUniqueId=v4().slice(0,8);if(this.props.className){className=className+" "+this.props.className;}return jsxRuntimeExports.jsx(View,{style:[styles$C.outerWrapper,this.state.focused&&styles$C.wrapperFocused],children:jsxRuntimeExports.jsxs("div",{style:{display:"flex",alignItems:"center",padding:1},onClick:e=>{e.stopPropagation();const mathField=this.mathField();if(!mathField){return}this.setState({cursorContext:getCursorContext(mathField)});},children:[jsxRuntimeExports.jsx("span",{className:className,ref:ref=>this.__mathFieldWrapperRef=ref,onFocus:()=>this.focus(),onBlur:()=>this.blur()}),jsxRuntimeExports.jsx(Popover,{opened:this.state.keypadOpen,dismissEnabled:true,rootBoundary:"document","aria-label":this.context.strings.mathInputTitle,"aria-describedby":`popover-content-${popoverContentUniqueId}`,onClose:()=>this.closeKeypad(),content:()=>jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(Heading,{size:"large",id:`popover-content-${popoverContentUniqueId}`,style:a11y.srOnly,children:this.context.strings.mathInputDescription}),jsxRuntimeExports.jsx(PopoverContentCore,{style:styles$C.popoverContent,children:jsxRuntimeExports.jsx(DesktopKeypad,{onAnalyticsEvent:this.props.onAnalyticsEvent,extraKeys:this.props.extraKeys,onClickKey:this.handleKeypadPress,cursorContext:this.state.cursorContext,convertDotToTimes:this.props.convertDotToTimes,...this.props.keypadButtonSets??mapButtonSets(this.props?.buttonSets),showDismiss:true})})]}),children:this.props.buttonsVisible==="never"?jsxRuntimeExports.jsx(MathInputIcon,{hovered:false,focused:false,active:false,pressed:false}):jsxRuntimeExports.jsx(Clickable,{"aria-label":this.state.keypadOpen?this.context.strings.closeKeypad:this.context.strings.openKeypad,role:"button",hideDefaultFocusRing:true,onClick:()=>this.state.keypadOpen?this.closeKeypad():this.openKeypad(),children:props=>jsxRuntimeExports.jsx(MathInputIcon,{active:this.state.keypadOpen,...props})})})]})})}constructor(...args){super(...args),this.__mathFieldWrapperRef=null,this.__mathField=null,this.state={focused:false,keypadOpen:this.props.buttonsVisible==="always",cursorContext:CursorContext.NONE},this.insert=value=>{const input=this.mathField();const{locale}=this.context;const customKeyTranslator={...getKeyTranslator(locale,this.context.strings),FRAC:mathQuill=>{const contents=mathQuill.latex();mathQuill.typedText("/");if(mathQuill.latex()===contents){mathQuill.cmd("\\frac");}}};const inputModifier=customKeyTranslator[value];if(inputModifier){inputModifier(input,value);input?.focus();return}if(typeof value==="function"){value(input);}else if(value[0]==="\\"){input?.cmd(value).focus();}else {input?.write(value).focus();}input?.focus();},this.mathField=()=>{if(!this.__mathField&&this.__mathFieldWrapperRef){const{locale}=this.context;this.__mathField=createMathField(this.__mathFieldWrapperRef,locale,this.props.mathInputStrings,baseConfig=>({...baseConfig,handlers:{edit:debounce(mathField=>{let value=mathField.latex();value=value.replace(/<>/g,"\\ne");if(convertDotToTimesByLocale(locale,this.props.convertDotToTimes)){value=value.replace(/\\cdot/g,"\\times");const left=mathField.cursor()[mathQuillInstance.L];if(left&&left.ctrlSeq==="\\cdot "){mathField.controller().backspace();mathField.cmd("\\times");}}else {value=value.replace(/\\times/g,"\\cdot");}if(this.props.value!==value){this.props.onChange(value);}this.setState({cursorContext:getCursorContext(mathField)});},100),enter:()=>{if(this.__mathFieldWrapperRef){this.__mathFieldWrapperRef.dispatchEvent(new Event("submit",{bubbles:true,cancelable:true}));}},upOutOf:mathField=>{mathField.typedText("^");}}}));}this.__mathField?.setAriaLabel(this.props.ariaLabel);return this.__mathField},this.focus=()=>{this.mathField()?.focus();this.setState({focused:true});},this.blur=()=>this.setState({focused:false}),this.handleKeypadPress=(key,e)=>{if(key==="DISMISS"){this.closeKeypad();return}const{locale}=this.context;const translator=getKeyTranslator(locale,this.context.strings)[key];const mathField=this.mathField();if(mathField){if(translator){translator(mathField,key);}this.setState({cursorContext:getCursorContext(mathField)});}if(e?.type==="click"){this.focus();}};}}InnerMathInput.contextType=PerseusI18nContext;InnerMathInput.defaultProps={value:"",convertDotToTimes:false};class MathInput extends React.Component{blur(){this.inputRef.current?.blur();}focus(){this.inputRef.current?.focus();}insert(value){this.inputRef.current?.insert(value);}render(){return jsxRuntimeExports.jsx(InnerMathInput,{...this.props,ref:this.inputRef,mathInputStrings:this.context.strings})}constructor(...args){super(...args),this.inputRef=React.createRef();}}MathInput.contextType=MathInputI18nContext;MathInput.defaultProps={ariaLabel:"Math input"};const MathInputIcon=({hovered,focused,pressed,active})=>{let fillColor;switch(true){case focused||pressed||active:fillColor=semanticColor.core.foreground.knockout.default;break;case hovered:fillColor=semanticColor.core.foreground.instructive.strong;break;default:fillColor=semanticColor.core.foreground.neutral.default;break}let dynamicClass;switch(true){case focused||pressed:dynamicClass=styles$C.iconActive;break;case active:dynamicClass=styles$C.iconExpanded;break;case hovered:dynamicClass=styles$C.iconHovered;break;default:dynamicClass=styles$C.iconInactive;}return jsxRuntimeExports.jsx(View,{style:[styles$C.iconContainer,dynamicClass],children:jsxRuntimeExports.jsx("svg",{xmlns:"http://www.w3.org/2000/svg",width:"16",height:"16",fill:fillColor,viewBox:"0 0 256 256",children:jsxRuntimeExports.jsx("path",{d:"M112,72a8,8,0,0,1-8,8H40a8,8,0,0,1,0-16h64A8,8,0,0,1,112,72Zm-8,104H80V152a8,8,0,0,0-16,0v24H40a8,8,0,0,0,0,16H64v24a8,8,0,0,0,16,0V192h24a8,8,0,0,0,0-16Zm48,0h64a8,8,0,0,0,0-16H152a8,8,0,0,0,0,16Zm64,16H152a8,8,0,0,0,0,16h64a8,8,0,0,0,0-16Zm-61.66-90.34a8,8,0,0,0,11.32,0L184,83.31l18.34,18.35a8,8,0,0,0,11.32-11.32L195.31,72l18.35-18.34a8,8,0,0,0-11.32-11.32L184,60.69,165.66,42.34a8,8,0,0,0-11.32,11.32L172.69,72,154.34,90.34A8,8,0,0,0,154.34,101.66Z"})})})};const mapButtonSets=buttonSets=>{const keypadButtonSets={};if(!buttonSets){return keypadButtonSets}buttonSets.forEach(buttonSet=>{switch(buttonSet){case "advanced relations":keypadButtonSets.advancedRelations=true;break;case "basic relations":keypadButtonSets.basicRelations=true;break;case "basic+div":keypadButtonSets.divisionKey=true;break;case "logarithms":keypadButtonSets.logarithms=true;break;case "prealgebra":keypadButtonSets.preAlgebra=true;break;case "trig":keypadButtonSets.trigonometry=true;break;case "scientific":keypadButtonSets.scientific=true;break;}});return keypadButtonSets};const inputFocused={borderColor:semanticColor.core.border.instructive.default};const styles$C=StyleSheet.create({iconContainer:{display:"flex",justifyContent:"center",height:"100%",padding:sizing.size_060,borderRadius:border$1.radius.radius_040,borderWidth:border$1.width.medium,borderStyle:"solid"},iconInactive:{borderColor:semanticColor.core.transparent,backgroundColor:semanticColor.core.background.neutral.subtle},iconActive:{borderColor:semanticColor.focus.outer,boxShadow:`inset 0 0 0 ${border$1.width.medium} ${semanticColor.focus.inner}`,backgroundColor:semanticColor.core.background.neutral.default},iconExpanded:{borderColor:semanticColor.core.transparent,backgroundColor:semanticColor.core.background.neutral.default},iconHovered:{borderColor:semanticColor.core.transparent,backgroundColor:semanticColor.core.background.neutral.subtle},outerWrapper:{display:"inline-block",borderStyle:"solid",borderWidth:border$1.width.thin,borderColor:semanticColor.core.border.neutral.default,borderRadius:border$1.radius.radius_040,background:semanticColor.core.background.base.default,":hover":inputFocused},wrapperFocused:inputFocused,popoverContent:{padding:0,paddingBlockEnd:sizing.size_060,maxInlineSize:"initial"}});
1419
1421
 
1420
1422
  let _dependencies=null;const setDependencies=dependencies=>{_dependencies=dependencies;};const getDependencies=()=>{if(_dependencies){return _dependencies}throw new Error(["Perseus has not been provided required dependencies.","setDependencies(dependencies) must be called first.","Make sure Perseus is being imported from javascript/perseus/perseus.js."].join("\n"))};const DependenciesContext=React.createContext({analytics:{onAnalyticsEvent:async()=>{}},generateUrl:()=>{throw new Error("generateUrl dependency not provided in Perseus dependencies")},useVideo:()=>{throw new Error("useVideo dependency not provided in Perseus dependencies")}});const useDependencies=()=>{const deps=React.useContext(DependenciesContext);return deps};
1421
1423
 
@@ -1431,7 +1433,7 @@ const ApiOptions={propTypes:PropTypes.shape({isArticle:PropTypes.bool.isRequired
1431
1433
 
1432
1434
  const getPromptJSON$q=(label,userInput)=>{return {type:"expression",label,userInput:{value:userInput}}};
1433
1435
 
1434
- const englishOperators={arctg:"arctan",cosec:"csc",cossec:"csc",cotg:"cot",ctg:"cot",sen:"sin",tg:"tan"};const anglicizeOperators=tex=>{return tex.replace(/\\operatorname{([a-z]+)}/g,(_,op)=>`\\${englishOperators[op]??op} `)};const normalizeTex=tex=>{return anglicizeOperators(tex)};const defaultOnFocus=()=>{};const defaultOnBlur=()=>{};const KeypadInputWithInterface=React.forwardRef(function KeypadInputWithInterface(props,ref){const keypadInputRef=React.useRef(null);const noopKeypadActivation=_keypadActive=>{};React.useImperativeHandle(ref,()=>({focus:(cb=noopKeypadActivation)=>keypadInputRef.current?.focus(cb),blur:()=>keypadInputRef.current?.blur(),insert:val=>{keypadInputRef.current?.mathField?.pressKey?.(val);}}));return jsxRuntimeExports.jsx(KeypadInput,{ref:keypadInputRef,...props})});const Expression=forwardRef(function Expression(props,ref){const{apiOptions=ApiOptions.defaults,onFocus=defaultOnFocus,onBlur=defaultOnBlur,userInput="",keypadElement,handleUserInput,trackInteraction,widgetId,options:{buttonSets,times,extraKeys,visibleLabel,ariaLabel,buttonsVisible,functions},...rest}=props;const{strings}=usePerseusI18n();const{analytics}=useDependencies();const{setKeypadActive}=React.useContext(KeypadContext);const textareaId=useId();const inputRef=useRef(null);const rootRef=useRef(null);useEffect(()=>{analytics?.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"expression",widgetId}});if(rootRef.current){const isMobile=apiOptions.customKeypad;const selector=isMobile?".mq-textarea > span":"textarea";const inputElement=rootRef.current.querySelector(selector);if(inputElement instanceof HTMLElement){inputElement.setAttribute("id",textareaId);}}},[]);const handleFocus=()=>{analytics?.onAnalyticsEvent({type:"perseus:expression-focused",payload:null});onFocus([]);};const handleBlur=()=>{onBlur([]);};const changeAndTrack=(value,cb)=>{const normalized=normalizeTex(value);handleUserInput(normalized,cb);trackInteraction();};const mobileHandleFocus=()=>{keypadElement?.configure(getKeypadConfiguration(),()=>{if(rootRef.current){handleFocus();}});};const getKeypadConfiguration=useCallback(()=>{return {keypadType:"EXPRESSION",extraKeys:extraKeys,times:times}},[extraKeys,times]);const getFocusTarget=useCallback(()=>{if(!rootRef.current){return null}const isMobile=apiOptions.customKeypad;const selector=isMobile?".mq-textarea > span":"textarea";const element=rootRef.current.querySelector(selector);return element instanceof HTMLElement?element:null},[apiOptions.customKeypad]);useImperativeHandle(ref,()=>({focus:()=>{const targetBefore=getFocusTarget();inputRef.current?.focus?.(setKeypadActive);const targetAfter=getFocusTarget()??targetBefore??(document.activeElement instanceof HTMLElement?document.activeElement:null);if(!targetAfter){return false}if(document.activeElement!==targetAfter){targetAfter.focus();}return document.activeElement===targetAfter},focusInputPath:path=>{inputRef.current?.focus?.(setKeypadActive);},blurInputPath:path=>{if(typeof inputRef.current?.blur==="function"){inputRef.current?.blur();}},insert:keyPressed=>{inputRef.current?.insert?.(keyPressed);},getInputPaths:()=>[[]],getUserInput:()=>{return normalizeTex(userInput)},getKeypadConfiguration,getPromptJSON:()=>{return getPromptJSON$q(visibleLabel,normalizeTex(userInput))},getSerializedState:()=>{return {...rest,value:userInput,keypadConfiguration:getKeypadConfiguration(),times,buttonSets,buttonsVisible,functions}}}));const keypadConfiguration=getKeypadConfiguration();if(apiOptions.customKeypad){return jsxRuntimeExports.jsxs(View,{ref:rootRef,style:styles$A.mobileLabelInputWrapper,children:[!!visibleLabel&&jsxRuntimeExports.jsx(BodyText,{size:"small",htmlFor:textareaId,tag:"label",children:visibleLabel}),jsxRuntimeExports.jsx(KeypadInputWithInterface,{ref:inputRef,ariaLabel:ariaLabel||strings.mathInputBox,value:userInput,keypadElement:keypadElement,onChange:changeAndTrack,onFocus:mobileHandleFocus,onBlur:handleBlur})]})}return jsxRuntimeExports.jsxs(View,{ref:rootRef,style:styles$A.desktopLabelInputWrapper,children:[!!visibleLabel&&jsxRuntimeExports.jsx(BodyText,{size:"small",htmlFor:textareaId,tag:"label",children:visibleLabel}),jsxRuntimeExports.jsx("div",{className:"perseus-widget-expression",children:jsxRuntimeExports.jsx(MathInput,{ref:inputRef,value:userInput,onChange:changeAndTrack,convertDotToTimes:times,buttonSets:buttonSets,onFocus:handleFocus,onBlur:handleBlur,ariaLabel:ariaLabel||strings.mathInputBox,extraKeys:keypadConfiguration.extraKeys,onAnalyticsEvent:analytics?.onAnalyticsEvent??(async()=>{})})})]})});const styles$A=StyleSheet.create({mobileLabelInputWrapper:{paddingBlockStart:"15px",paddingInline:"4px",paddingBlockEnd:"0"},desktopLabelInputWrapper:{marginBlockStart:"5px",marginInline:"5px",marginBlockEnd:"0"}});function getUserInputFromSerializedState$i(serializedState){return normalizeTex(serializedState.value)}function getStartUserInput$i(){return ""}function getOneCorrectAnswerFromRubric$1(rubric){const correctAnswers=(rubric.answerForms||[]).filter(answerForm=>answerForm.considered==="correct");if(correctAnswers.length===0){return}return correctAnswers[0].value}function getCorrectUserInput$9(options){for(const form of options.answerForms){if(form.considered==="correct"){return form.value}}return ""}var Expression$1 = {name:"expression",displayName:"Expression / Equation",widget:Expression,isLintable:true,getOneCorrectAnswerFromRubric: getOneCorrectAnswerFromRubric$1,getStartUserInput: getStartUserInput$i,getCorrectUserInput: getCorrectUserInput$9,getUserInputFromSerializedState: getUserInputFromSerializedState$i};
1436
+ const englishOperators={arctg:"arctan",cosec:"csc",cossec:"csc",cotg:"cot",ctg:"cot",sen:"sin",tg:"tan"};const anglicizeOperators=tex=>{return tex.replace(/\\operatorname{([a-z]+)}/g,(_,op)=>`\\${englishOperators[op]??op} `)};const normalizeTex=tex=>{return anglicizeOperators(tex)};const defaultOnFocus=()=>{};const defaultOnBlur=()=>{};const KeypadInputWithInterface=React.forwardRef(function KeypadInputWithInterface(props,ref){const keypadInputRef=React.useRef(null);const noopKeypadActivation=_keypadActive=>{};React.useImperativeHandle(ref,()=>({focus:(cb=noopKeypadActivation)=>keypadInputRef.current?.focus(cb),blur:()=>keypadInputRef.current?.blur(),insert:val=>{keypadInputRef.current?.mathField?.pressKey?.(val);}}));return jsxRuntimeExports.jsx(KeypadInput,{ref:keypadInputRef,...props})});const Expression=forwardRef(function Expression(props,ref){const{apiOptions=ApiOptions.defaults,onFocus=defaultOnFocus,onBlur=defaultOnBlur,userInput="",keypadElement,handleUserInput,trackInteraction,widgetId,options:{buttonSets,times,extraKeys,visibleLabel,ariaLabel,buttonsVisible,functions},...rest}=props;const{strings}=usePerseusI18n();const{analytics}=useDependencies();const{setKeypadActive}=React.useContext(KeypadContext);const textareaId=useId();const inputRef=useRef(null);const rootRef=useRef(null);useEffect(()=>{analytics?.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"expression",widgetId}});if(rootRef.current){const isMobile=apiOptions.customKeypad;const selector=isMobile?".mq-textarea > span":"textarea";const inputElement=rootRef.current.querySelector(selector);if(inputElement instanceof HTMLElement){inputElement.setAttribute("id",textareaId);}}},[]);const handleFocus=()=>{analytics?.onAnalyticsEvent({type:"perseus:expression-focused",payload:null});onFocus([]);};const handleBlur=()=>{onBlur([]);};const changeAndTrack=(value,cb)=>{const normalized=normalizeTex(value);handleUserInput(normalized,cb);trackInteraction();};const mobileHandleFocus=()=>{keypadElement?.configure(getKeypadConfiguration(),()=>{if(rootRef.current){handleFocus();}});};const getKeypadConfiguration=useCallback(()=>{return {keypadType:"EXPRESSION",extraKeys:extraKeys,times:times}},[extraKeys,times]);const getFocusTarget=useCallback(()=>{if(!rootRef.current){return null}const isMobile=apiOptions.customKeypad;const selector=isMobile?".mq-textarea > span":"textarea";const element=rootRef.current.querySelector(selector);return element instanceof HTMLElement?element:null},[apiOptions.customKeypad]);useImperativeHandle(ref,()=>({focus:()=>{const targetBefore=getFocusTarget();inputRef.current?.focus?.(setKeypadActive);const targetAfter=getFocusTarget()??targetBefore??(document.activeElement instanceof HTMLElement?document.activeElement:null);if(!targetAfter){return false}if(document.activeElement!==targetAfter){targetAfter.focus();}return document.activeElement===targetAfter},focusInputPath:path=>{inputRef.current?.focus?.(setKeypadActive);},blurInputPath:path=>{if(typeof inputRef.current?.blur==="function"){inputRef.current?.blur();}},insert:keyPressed=>{inputRef.current?.insert?.(keyPressed);},getInputPaths:()=>[[]],getUserInput:()=>{return normalizeTex(userInput)},getKeypadConfiguration,getPromptJSON:()=>{return getPromptJSON$q(visibleLabel,normalizeTex(userInput))},getSerializedState:()=>{return {...rest,value:userInput,keypadConfiguration:getKeypadConfiguration(),times,buttonSets,buttonsVisible,functions}}}));const keypadConfiguration=getKeypadConfiguration();if(apiOptions.customKeypad){return jsxRuntimeExports.jsxs(View,{ref:rootRef,style:styles$B.mobileLabelInputWrapper,children:[!!visibleLabel&&jsxRuntimeExports.jsx(BodyText,{size:"small",htmlFor:textareaId,tag:"label",children:visibleLabel}),jsxRuntimeExports.jsx(KeypadInputWithInterface,{ref:inputRef,ariaLabel:ariaLabel||strings.mathInputBox,value:userInput,keypadElement:keypadElement,onChange:changeAndTrack,onFocus:mobileHandleFocus,onBlur:handleBlur})]})}return jsxRuntimeExports.jsxs(View,{ref:rootRef,style:styles$B.desktopLabelInputWrapper,children:[!!visibleLabel&&jsxRuntimeExports.jsx(BodyText,{size:"small",htmlFor:textareaId,tag:"label",children:visibleLabel}),jsxRuntimeExports.jsx("div",{className:"perseus-widget-expression",children:jsxRuntimeExports.jsx(MathInput,{ref:inputRef,value:userInput,onChange:changeAndTrack,convertDotToTimes:times,buttonSets:buttonSets,onFocus:handleFocus,onBlur:handleBlur,ariaLabel:ariaLabel||strings.mathInputBox,extraKeys:keypadConfiguration.extraKeys,onAnalyticsEvent:analytics?.onAnalyticsEvent??(async()=>{})})})]})});const styles$B=StyleSheet.create({mobileLabelInputWrapper:{paddingBlockStart:"15px",paddingInline:"4px",paddingBlockEnd:"0"},desktopLabelInputWrapper:{marginBlockStart:"5px",marginInline:"5px",marginBlockEnd:"0"}});function getUserInputFromSerializedState$i(serializedState){return normalizeTex(serializedState.value)}function getStartUserInput$i(){return ""}function getOneCorrectAnswerFromRubric$1(rubric){const correctAnswers=(rubric.answerForms||[]).filter(answerForm=>answerForm.considered==="correct");if(correctAnswers.length===0){return}return correctAnswers[0].value}function getCorrectUserInput$9(options){for(const form of options.answerForms){if(form.considered==="correct"){return form.value}}return ""}var Expression$1 = {name:"expression",displayName:"Expression / Equation",widget:Expression,isLintable:true,getOneCorrectAnswerFromRubric: getOneCorrectAnswerFromRubric$1,getStartUserInput: getStartUserInput$i,getCorrectUserInput: getCorrectUserInput$9,getUserInputFromSerializedState: getUserInputFromSerializedState$i};
1435
1437
 
1436
1438
  class SimpleKeypadInput extends React.Component{componentDidMount(){this._isMounted=true;}componentWillUnmount(){this._isMounted=false;}focus(){this.inputRef.current?.focus(this.context.setKeypadActive);}blur(){this.inputRef.current?.blur();}getValue(){return this.props.value}render(){const _this=this;const{keypadElement,onFocus,value,...rest}=_this.props;return jsxRuntimeExports.jsx(KeypadInput,{ref:this.inputRef,keypadElement:keypadElement,onFocus:()=>{if(keypadElement){keypadElement.configure({keypadType:"FRACTION"},()=>{if(_this._isMounted){onFocus?.();}});}else {onFocus?.();}},value:value==null?"":""+value,...rest})}constructor(...args){super(...args),this._isMounted=false,this.inputRef=React.createRef();}}SimpleKeypadInput.contextType=KeypadContext;SimpleKeypadInput.propTypes={keypadElement:keypadElementPropType,onFocus:PropTypes.func,value:PropTypes.oneOfType([PropTypes.string,PropTypes.number])};
1437
1439
 
@@ -1449,16 +1451,14 @@ const Log={log:(message,extra)=>{getDependencies().Log.log(message,extra);},erro
1449
1451
 
1450
1452
  const svgLocalLabelsRegex=/^file\+graphie:/;const hashRegex=/\/([^/]+)$/;function getLocale(){const{JIPT,kaLocale}=getDependencies();return JIPT.useJIPT?"en-pt":kaLocale}function shouldUseLocalizedData(){return getLocale()!=="en"}const splitHashRegex=/\/(?=[^/]+$)/;function getLocalizedDataUrl(url){if(svgLocalLabelsRegex.test(url)){return Util.getDataUrl(url)}const[base,hash]=Util.getBaseUrl(url).split(splitHashRegex);return `${base}/${getLocale()}/${hash}-data.json`}function getUrlHash(url){const match=url.match(hashRegex);if(match==null){throw new PerseusError("not a valid URL",Errors.InvalidInput)}return match&&match[1]}function parseDataFromJSONP(graphieHash,graphieJSONP,errorCallback){const match=graphieJSONP.match(new RegExp(`^(?:svgData|svgOtherData)${graphieHash}\\((.+)\\);$`));const jsonToParse=match?match[1]:graphieJSONP;try{return JSON.parse(jsonToParse)}catch(error){errorCallback(error);return null}}let labelDataCache={};function loadGraphie(url,onDataLoaded){const hash=getUrlHash(url);const entry=labelDataCache[hash];if(entry!=null){if(entry.loaded){const{data,localized}=entry;onDataLoaded(data,localized);}else {entry.dataCallbacks.push(onDataLoaded);}}else {const cacheData={loaded:false,dataCallbacks:[onDataLoaded],localized:shouldUseLocalizedData()};labelDataCache[hash]=cacheData;const retrieveData=async(url,errorCallback)=>{const response=await fetch(url);if(!response?.ok){errorCallback();return}const jsonp=await response.text();const data=parseDataFromJSONP(hash,jsonp,error=>{Log.error("Failed to parse JSONP for svg-image",Errors.Service,{cause:error,loggedMetadata:{dataUrl:Util.getDataUrl(url),jsonp}});});if(!data){return}const newCacheEntry=labelDataCache[hash]={...labelDataCache[hash],loaded:true,data};newCacheEntry.dataCallbacks.forEach(callback=>{callback(newCacheEntry.data,cacheData.localized);});};const dataLoadErrorHandler=(x,status,error)=>{Log.error("Data load failed for svg-image",Errors.Service,{cause:error,loggedMetadata:{dataUrl:Util.getDataUrl(url),status}});};if(shouldUseLocalizedData()){retrieveData(getLocalizedDataUrl(url),()=>{cacheData.localized=false;retrieveData(Util.getDataUrl(url),dataLoadErrorHandler);});}else {retrieveData(Util.getDataUrl(url),dataLoadErrorHandler);}}}
1451
1453
 
1452
- const baseFontFamily="'Lato', sans-serif";const boldFontFamily="'Lato-Bold', 'Lato', sans-serif";const white="#FFFFFF";const gray76="#BABEC2";const gray68="#888D93";const gray17="#21242c";const pureSmMin="568px";const pureMdMin$1="768px";const pureLgMin$1="1024px";const pureXlMin="1280px";const pureXsMax$1="567px";const pureSmMax$1="767px";const pureMdMax$1="1023px";const pureLgMax$1="1279px";const phoneMargin=16;const negativePhoneMargin=-16;const hintBorderWidth=sizing.size_040;const hintPaddingInlineStart=sizing.size_160;const interactiveSizes$1={defaultBoxSize:400,defaultBoxSizeSmall:288};const circleSize$1=24;const radioMarginWidth$1=2;const warningColor="#f86700";const warningColorHover="#df5c00";const warningColorActive="#c75300";const publishBlockingErrorColor="#be2612";const articleMaxWidthInPx=688;const articleMaxWidthTableInPx=512;
1454
+ const boldFontFamily="'Lato-Bold', 'Lato', sans-serif";const white="#FFFFFF";const gray76="#BABEC2";const gray68="#888D93";const pureSmMin="568px";const pureMdMin$1="768px";const pureLgMin$1="1024px";const pureXlMin="1280px";const pureXsMax$1="567px";const pureSmMax$1="767px";const pureMdMax$1="1023px";const pureLgMax$1="1279px";const phoneMargin=16;const negativePhoneMargin=-16;const hintBorderWidth=sizing.size_040;const hintPaddingInlineStart=sizing.size_160;const interactiveSizes$1={defaultBoxSize:400,defaultBoxSizeSmall:288};const circleSize$1=24;const radioMarginWidth$1=2;const warningColor="#f86700";const warningColorHover="#df5c00";const warningColorActive="#c75300";const articleMaxWidthInPx=688;const articleMaxWidthTableInPx=512;
1453
1455
 
1454
1456
  var constants = /*#__PURE__*/Object.freeze({
1455
1457
  __proto__: null,
1456
1458
  articleMaxWidthInPx: articleMaxWidthInPx,
1457
1459
  articleMaxWidthTableInPx: articleMaxWidthTableInPx,
1458
- baseFontFamily: baseFontFamily,
1459
1460
  boldFontFamily: boldFontFamily,
1460
1461
  circleSize: circleSize$1,
1461
- gray17: gray17,
1462
1462
  gray68: gray68,
1463
1463
  gray76: gray76,
1464
1464
  hintBorderWidth: hintBorderWidth,
@@ -1466,7 +1466,6 @@ var constants = /*#__PURE__*/Object.freeze({
1466
1466
  interactiveSizes: interactiveSizes$1,
1467
1467
  negativePhoneMargin: negativePhoneMargin,
1468
1468
  phoneMargin: phoneMargin,
1469
- publishBlockingErrorColor: publishBlockingErrorColor,
1470
1469
  pureLgMax: pureLgMax$1,
1471
1470
  pureLgMin: pureLgMin$1,
1472
1471
  pureMdMax: pureMdMax$1,
@@ -1601,9 +1600,9 @@ const Status={PENDING:"pending",LOADING:"loading",LOADED:"loaded",FAILED:"failed
1601
1600
 
1602
1601
  function isGif(url){return url.endsWith(".gif")}async function decodeGifFrames(src){const res=await fetch(src);if(!res.ok){return []}const buffer=await res.arrayBuffer();const gif=parseGIF(buffer);return decompressFrames(gif,true)}function isSvg(url){const hasSvgExtension=url.endsWith(".svg");const hasGraphieUrl=isLabeledSVG(url);return hasSvgExtension||hasGraphieUrl}
1603
1602
 
1604
- var styles$z = {"contentWrapper":"perseus_NqTCZY3N","imageContainer":"perseus_Ctn3miye"};
1603
+ var styles$A = {"contentWrapper":"perseus_NqTCZY3N","imageContainer":"perseus_Ctn3miye"};
1605
1604
 
1606
- const WB_MODAL_PADDING_TOTAL=64;const ZoomedImageView=props=>{const i18n=usePerseusI18n();const{initialFocusId,onClose,...svgProps}=props;const width=props.width;const height=props.height;const contentScale=props.scale;const imageIsSvg=isSvg(props.src);const scale=imageIsSvg?Math.max(contentScale,2):Math.max(contentScale,1);const maxAvailableWidth=window.innerWidth-WB_MODAL_PADDING_TOTAL;const maxAvailableHeight=window.innerHeight-WB_MODAL_PADDING_TOTAL;const scaleWidth=maxAvailableWidth/(width*scale);const scaleHeight=maxAvailableHeight/(height*scale);const fitRatio=Math.min(scaleWidth,scaleHeight,1);const constrainedDisplayWidth=width*fitRatio*scale;return jsxRuntimeExports.jsx(ModalDialog,{"aria-labelledby":"",style:wbStyles$2.dialog,children:jsxRuntimeExports.jsx(ModalPanel,{closeButtonVisible:false,content:jsxRuntimeExports.jsxs("div",{className:styles$z.contentWrapper,children:[jsxRuntimeExports.jsx("div",{className:styles$z.imageContainer,style:{width:constrainedDisplayWidth},children:jsxRuntimeExports.jsx("div",{className:"framework-perseus",tabIndex:0,id:initialFocusId,children:jsxRuntimeExports.jsx(SvgImage,{...svgProps,allowZoom:false,width:width,height:height,scale:scale})})}),jsxRuntimeExports.jsx(IconButton,{icon:closeIcon,onClick:onClose,"aria-label":i18n.strings.imageResetZoomAriaLabel,kind:"primary",style:wbStyles$2.closeButton})]})})})};const wbStyles$2=StyleSheet.create({dialog:{width:"auto",height:"auto",margin:sizing.size_320,"@media (max-width: 767px)":{margin:0}},closeButton:{position:"absolute",insetBlockStart:border$1.width.medium,insetInlineEnd:border$1.width.medium,opacity:0,":hover":{opacity:1},":focus":{opacity:1}}});
1605
+ const WB_MODAL_PADDING_TOTAL=64;const ZoomedImageView=props=>{const i18n=usePerseusI18n();const{initialFocusId,onClose,...svgProps}=props;const width=props.width;const height=props.height;const contentScale=props.scale;const imageIsSvg=isSvg(props.src);const scale=imageIsSvg?Math.max(contentScale,2):Math.max(contentScale,1);const maxAvailableWidth=window.innerWidth-WB_MODAL_PADDING_TOTAL;const maxAvailableHeight=window.innerHeight-WB_MODAL_PADDING_TOTAL;const scaleWidth=maxAvailableWidth/(width*scale);const scaleHeight=maxAvailableHeight/(height*scale);const fitRatio=Math.min(scaleWidth,scaleHeight,1);const constrainedDisplayWidth=width*fitRatio*scale;return jsxRuntimeExports.jsx(ModalDialog,{"aria-labelledby":"",style:wbStyles$2.dialog,children:jsxRuntimeExports.jsx(ModalPanel,{closeButtonVisible:false,content:jsxRuntimeExports.jsxs("div",{className:styles$A.contentWrapper,children:[jsxRuntimeExports.jsx("div",{className:styles$A.imageContainer,style:{width:constrainedDisplayWidth},children:jsxRuntimeExports.jsx("div",{className:"framework-perseus",tabIndex:0,id:initialFocusId,children:jsxRuntimeExports.jsx(SvgImage,{...svgProps,allowZoom:false,width:width,height:height,scale:scale})})}),jsxRuntimeExports.jsx(IconButton,{icon:closeIcon,onClick:onClose,"aria-label":i18n.strings.imageResetZoomAriaLabel,kind:"primary",style:wbStyles$2.closeButton})]})})})};const wbStyles$2=StyleSheet.create({dialog:{width:"auto",height:"auto",margin:sizing.size_320,"@media (max-width: 767px)":{margin:0}},closeButton:{position:"absolute",insetBlockStart:border$1.width.medium,insetInlineEnd:border$1.width.medium,opacity:0,":hover":{opacity:1},":focus":{opacity:1}}});
1607
1606
 
1608
1607
  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,{})}})})};
1609
1608
 
@@ -1625,7 +1624,9 @@ function _noop(){}class InteractionTracker{constructor(trackApi,widgetType,widge
1625
1624
 
1626
1625
  const arrayRules={fence:{match:SimpleMarkdown.defaultRules.fence.match,order:1,parse:(capture,state,parse)=>({type:"codeBlock",lang:capture[2]||undefined,content:capture[3]})},paragraph:{match:SimpleMarkdown.defaultRules.paragraph.match,order:2,parse:(capture,state,parse)=>({content:capture[1]})}};const builtArrayParser=SimpleMarkdown.parserFor(arrayRules);const parseToArray=source=>{const paragraphedSource=source.replace(/^\n\s*\n/,"")+"\n\n";return builtArrayParser(paragraphedSource,{inline:false}).map(c=>{return c["content"]})};const joinFromArray=paragraphs=>paragraphs.join("\n\n");var JiptParagraphs = {parseToArray:parseToArray,joinFromArray:joinFromArray};
1627
1626
 
1628
- const exclamationIcon={path:"M6 11a1 1 0 1 1 0-2 1 1 0 0 1 0 2zm0-9a1 1 0 0 1 1 1v4a1 1 0 1 1-2 0V3a1 1 0 0 1 1-1z",height:12,width:12};class Lint extends React.Component{componentDidMount(){this._positionTimeout=window.setTimeout(this.getPosition);}componentWillUnmount(){window.clearTimeout(this._positionTimeout);}render(){const{children,inline,blockHighlight,insideTable}=this.props;if(insideTable){if(inline){return jsxRuntimeExports.jsx("span",{"data-lint-inside-table":"true",children:children})}return jsxRuntimeExports.jsx("div",{"data-lint-inside-table":"true",children:children})}if(blockHighlight){return jsxRuntimeExports.jsxs("span",{className:css(styles$y.lintContainer,styles$y.lintContainerBlock),children:[this.renderLink(styles$y.radioWidgetHoverTarget),jsxRuntimeExports.jsx("span",{children:children})]})}if(inline){return jsxRuntimeExports.jsxs("span",{className:css(styles$y.lintContainer),children:[this.renderLink(styles$y.inlineHoverTarget),jsxRuntimeExports.jsx("span",{children:children})]})}return jsxRuntimeExports.jsxs("div",{className:css(styles$y.lintContainer),children:[this.renderLink(styles$y.hoverTarget),jsxRuntimeExports.jsx("div",{children:children})]})}constructor(...args){super(...args),this.state={tooltipAbove:true},this.getPosition=()=>{const rect=ReactDOM__default.findDOMNode(this).getBoundingClientRect();this.setState({tooltipAbove:rect.top>100});},this.renderLink=style=>{const tooltipAbove=this.state.tooltipAbove;let severityStyle;let warningText;let warningTextStyle;if(this.props.severity===1){severityStyle=styles$y.indicatorError;warningText="Error";warningTextStyle=styles$y.publishBlockingError;}else if(this.props.severity===2){severityStyle=styles$y.indicatorWarning;warningText="Warning";warningTextStyle=styles$y.warning;}else {severityStyle=styles$y.indicatorGuideline;warningText="Recommendation";warningTextStyle=styles$y.warning;}return jsxRuntimeExports.jsxs("a",{href:`https://khanacademy.org/r/linter-rules#${this.props.ruleName}`,target:"lint-help-window",className:css(style),children:[jsxRuntimeExports.jsx("span",{className:css(styles$y.indicator,severityStyle),children:this.props.severity===1&&jsxRuntimeExports.jsx(InlineIcon,{...exclamationIcon})}),jsxRuntimeExports.jsxs("div",{className:css(styles$y.tooltip,tooltipAbove&&styles$y.tooltipAbove),children:[this.props.message.split("\n\n").map((m,i)=>jsxRuntimeExports.jsxs("p",{className:css(styles$y.tooltipParagraph),children:[jsxRuntimeExports.jsxs("span",{className:css(warningTextStyle),children:[warningText,":"," "]}),m]},i)),jsxRuntimeExports.jsx("div",{className:css(styles$y.tail,tooltipAbove&&styles$y.tailAbove)})]})]})};}}const styles$y=StyleSheet.create({lintContainer:{position:"relative"},lintContainerBlock:{display:"block"},hoverTarget:{position:"absolute",insetBlockStart:0,insetInlineEnd:-40,display:"block",width:24,height:24,":hover > span":{backgroundColor:warningColorHover},":hover div":{display:"block"},":hover ~ div":{outline:"1px solid "+warningColor},":hover ~ div div[data-lint-inside-table]":{outline:"1px solid "+warningColor},":hover ~ div span[data-lint-inside-table]":{backgroundColor:warningColor,color:white}},inlineHoverTarget:{float:"inline-end",position:"relative",marginInlineEnd:-40,display:"block",width:24,height:24,":hover > span":{backgroundColor:warningColorHover},":hover div":{display:"block"},":hover ~ span":{backgroundColor:warningColor,color:white}},radioWidgetHoverTarget:{position:"absolute",insetInlineStart:-40,display:"block",width:24,height:24,minInlineSize:264,":hover > span":{backgroundColor:warningColorHover},":hover > div":{display:"block",padding:8,width:280},":hover > div > div":{insetInlineStart:8},":hover ~ span":{backgroundColor:warningColor,color:white}},indicator:{alignItems:"center",borderRadius:4,color:"white",display:"flex",fontSize:12,height:8,justifyContent:"center",margin:8,width:8},indicatorError:{backgroundColor:"#be2612",borderRadius:8,height:16,width:16},indicatorWarning:{backgroundColor:"#f86700"},indicatorGuideline:{backgroundColor:"#ffbe26"},tooltip:{position:"absolute",insetInlineEnd:-12,display:"none",zIndex:1e3,color:white,backgroundColor:gray17,opacity:.9,fontFamily:baseFontFamily,fontSize:"12px",lineHeight:"15px",width:"320px",borderRadius:"4px"},tooltipAbove:{insetBlockEnd:32},tail:{position:"absolute",insetBlockStart:-12,insetInlineEnd:16,width:0,height:0,borderInlineStart:"8px solid transparent",borderInlineEnd:"8px solid transparent",borderBlockEnd:"12px solid "+gray17},tailAbove:{insetBlockEnd:-12,borderBlockEnd:"none",borderBlockStart:"12px solid "+gray17,insetBlockStart:"auto"},tooltipParagraph:{margin:12},warning:{color:warningColor,fontFamily:boldFontFamily},publishBlockingError:{color:publishBlockingErrorColor}});
1627
+ function LinterLink({message,ruleName,severity,style}){let severityLabel;let severityLabelStyle;if(severity===1){severityLabel="Error";severityLabelStyle=styles$z.publishBlockingError;}else if(severity===2){severityLabel="Warning";severityLabelStyle=styles$z.warning;}else {severityLabel="Recommendation";severityLabelStyle=styles$z.warning;}return jsxRuntimeExports.jsx(Tooltip,{forceAnchorFocusivity:false,variant:"strong",content:jsxRuntimeExports.jsx(TooltipContent,{title:jsxRuntimeExports.jsx(BodyText,{weight:"bold",style:severityLabelStyle,children:severityLabel}),children:message.split("\n\n").map((paragraph,i)=>jsxRuntimeExports.jsx(BodyText,{style:styles$z.tooltipParagraph,children:paragraph},i))}),children:jsxRuntimeExports.jsx(IconButton,{size:"small",kind:"tertiary",actionType:"destructive","aria-label":`${severityLabel}: ${ruleName}`,href:`https://khanacademy.org/r/linter-rules#${ruleName}`,icon:severity===1?warningCircleIcon:dotOutlineIcon,style:style})})}const styles$z=StyleSheet.create({tooltipParagraph:{":not(:first-child)":{marginBlockStart:sizing.size_080}},warning:{color:semanticColor.status.warning.foreground},publishBlockingError:{color:semanticColor.status.critical.foreground}});
1628
+
1629
+ function Lint({children,inline,message,ruleName,insideTable,blockHighlight,severity}){const linterLinkProps={message,ruleName,severity};if(insideTable){if(inline){return jsxRuntimeExports.jsx("span",{"data-lint-inside-table":"true",children:children})}return jsxRuntimeExports.jsx("div",{"data-lint-inside-table":"true",children:children})}if(blockHighlight){return jsxRuntimeExports.jsxs("span",{className:css(styles$y.lintContainer,styles$y.lintContainerBlock),children:[jsxRuntimeExports.jsx(LinterLink,{...linterLinkProps,style:styles$y.radioWidgetHoverTarget}),jsxRuntimeExports.jsx("span",{children:children})]})}if(inline){return jsxRuntimeExports.jsxs("span",{className:css(styles$y.lintContainer),children:[jsxRuntimeExports.jsx(LinterLink,{...linterLinkProps,style:styles$y.inlineHoverTarget}),jsxRuntimeExports.jsx("span",{children:children})]})}return jsxRuntimeExports.jsxs("div",{className:css(styles$y.lintContainer),children:[jsxRuntimeExports.jsx(LinterLink,{...linterLinkProps,style:styles$y.hoverTarget}),jsxRuntimeExports.jsx("div",{children:children})]})}const highlightBlockContent={":hover ~ div":{outline:`1px solid ${semanticColor.status.warning.foreground}`},":hover ~ div div[data-lint-inside-table]":{outline:`1px solid ${semanticColor.status.warning.foreground}`},":hover ~ div span[data-lint-inside-table]":{backgroundColor:semanticColor.status.warning.background,color:semanticColor.status.warning.foreground}};const highlightInlineContent={":hover ~ span":{backgroundColor:semanticColor.status.warning.background,color:semanticColor.status.warning.foreground}};const styles$y=StyleSheet.create({lintContainer:{position:"relative"},lintContainerBlock:{display:"block"},hoverTarget:{position:"absolute",insetInlineEnd:-60,...highlightBlockContent},inlineHoverTarget:{float:"inline-end",position:"relative",marginInlineEnd:-60,...highlightInlineContent},radioWidgetHoverTarget:{position:"absolute",insetInlineStart:-60}});
1629
1630
 
1630
1631
  const rules$1={...pureMarkdownRules,columns:{...pureMarkdownRules.columns,react:(node,output,state)=>{return jsxRuntimeExports.jsxs("div",{className:"perseus-two-columns",children:[jsxRuntimeExports.jsx("div",{className:"perseus-column",children:jsxRuntimeExports.jsx("div",{className:"perseus-column-content",children:output(node.col1,state)})}),jsxRuntimeExports.jsx("div",{className:"perseus-column",children:jsxRuntimeExports.jsx("div",{className:"perseus-column-content",children:output(node.col2,state)})})]},state.key)}},crowdinId:{...pureMarkdownRules.crowdinId,react:(node,output,state)=>node.id},table:{...pureMarkdownRules.table,react:function(node,output,state){const table=SimpleMarkdown.defaultRules.table.react(node,output,state);if(!state.isMobile){return table}return React.cloneElement(table,{tabIndex:0})}},titledTable:{...pureMarkdownRules.titledTable,react:(node,output,state)=>{let contents;if(!node.table){contents="//invalid table//";}else if(node.table.type==="lint"){if(node.table.content?.type==="table"){contents=rules$1.lint.react({...node.table,content:{...node,table:node.table.content}},output,{...state,unwrapTitledTableContents:true});}else {contents=rules$1.lint.react(node.table,output,state);}}else {const tableOutput=SimpleMarkdown.defaultRules.table.react(node.table,output,state);const caption=jsxRuntimeExports.jsx("caption",{className:"perseus-table-title",children:output(node.title,state)},"caption");contents=React.cloneElement(tableOutput,null,[caption,...tableOutput.props.children]);}if(state.unwrapTitledTableContents){return contents}return jsxRuntimeExports.jsx("div",{className:"perseus-titled-table",children:contents},state.key)}},widget:{...pureMarkdownRules.widget,react:(node,output,state)=>{return jsxRuntimeExports.jsx("em",{children:`[Widget: ${node.id}]`},state.key)}},blockMath:{...pureMarkdownRules.blockMath,react:(node,output,state)=>{const{TeX}=getDependencies();return jsxRuntimeExports.jsx(TeX,{children:node.content},state.key)}},math:{...pureMarkdownRules.math,react:(node,output,state)=>{const{TeX}=getDependencies();return jsxRuntimeExports.jsx(TeX,{children:node.content},state.key)}},unescapedDollar:{...pureMarkdownRules.unescapedDollar,react:(node,output,state)=>{return "$"}},link:{...pureMarkdownRules.link,react:function(node,output,state){const link=SimpleMarkdown.defaultRules.link.react(node,output,state);const href=link.props.href;let rel=null;const isKAUrl=href?href.match(/https?:\/\/[^/]*khanacademy.org|^\//):false;if(!isKAUrl){rel="noopener noreferrer";}const newProps={...link.props,target:"_blank",href,rel};if(state.baseElements&&state.baseElements.Link){return state.baseElements.Link(newProps)}return React.cloneElement(link,newProps)}},codeBlock:{...pureMarkdownRules.codeBlock,react:(node,output,state)=>{if(node.lang==="alt"){return jsxRuntimeExports.jsx("div",{className:"perseus-markdown-alt perseus-sr-only",children:output(node.content,state)},state.key)}return SimpleMarkdown.defaultRules.codeBlock.react(node,output,state)}},lint:{...pureMarkdownRules.lint,react:(node,output,state)=>{return jsxRuntimeExports.jsx(Lint,{message:node.message,ruleName:node.ruleName,inline:isInline$1(node.content),blockHighlight:node.blockHighlight,insideTable:node.insideTable,severity:node.severity,children:output(node.content,state)})}},paragraph:{...pureMarkdownRules.paragraph,react:(node,output,state)=>{if(noParagraphForInlineWidget(node)){return output(node.content,state)}else {return jsxRuntimeExports.jsx("p",{children:output(node.content,state)})}}}};function isInline$1(node){return !!(node&&node.type&&inlineNodeTypes$1.hasOwnProperty(node.type))}const inlineNodeTypes$1={text:true,math:true,unescapedDollar:true,link:true,img:true,strong:true,u:true,em:true,del:true,code:true};const builtParser$1=SimpleMarkdown.parserFor(rules$1);const parse$1=(source,state)=>{const paragraphedSource=source+"\n\n";return builtParser$1(paragraphedSource,{...state,inline:false})};const inlineParser$1=(source,state)=>{return builtParser$1(source,{...state,inline:true})};const getContent$1=ast=>{if(Array.isArray(ast)){return _.flatten(ast.map(getContent$1))}if(ast.content&&typeof ast.content==="string"){if(ast.type.toLowerCase().indexOf("code")!==-1){return ["",ast.content,""]}return [ast.content.replace(/\s+/g," ")]}const children=_.chain(ast).values().flatten().filter(object=>object!=null&&_.has(object,"type")).value();if(!children.length){return []}const nestedContent=getContent$1(children);if(ast.type==="paragraph"&&nestedContent.length){nestedContent[0]=nestedContent[0].replace(/^\s+/,"");const last=nestedContent.length-1;nestedContent[last]=nestedContent[last].replace(/\s+$/,"");}return nestedContent};const characterCount$1=source=>{const ast=parse$1(source);const content=getContent$1(ast).join("");return content.length};var PerseusMarkdown$1 = {characterCount:characterCount$1,traverseContent:traverseContent,parse:parse$1,parseInline:inlineParser$1,reactFor:SimpleMarkdown.reactFor,ruleOutput:SimpleMarkdown.ruleOutput(rules$1,"react"),basicOutput:SimpleMarkdown.reactFor(SimpleMarkdown.ruleOutput(rules$1,"react")),sanitizeUrl:SimpleMarkdown.sanitizeUrl};
1631
1632
 
@@ -1771,7 +1772,7 @@ const styles$j={buttonStyleOverrides:{height:"auto",lineHeight:"inherit",marginL
1771
1772
 
1772
1773
  function mediaQueryIsMatched(mediaQuery){if(typeof window.matchMedia!=="function"){return false}return window.matchMedia(mediaQuery).matches}const Explanation=forwardRef(function Explanation(props,ref){const{strings}=usePerseusI18n();const dependencies=useDependencies();const[expanded,setExpanded]=useState(false);const contentId=useId();const{hidePrompt,showPrompt,explanation,widgets}=props.options;useOnMountEffect(()=>{dependencies.analytics.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"explanation",widgetId:props.widgetId}});});useImperativeHandle(ref,()=>({getPromptJSON(){return getPromptJSON$j(props)}}));function onClick(){setExpanded(!expanded);props.trackInteraction();}const legacyContentStyling=[styles$j.content,expanded?styles$j.contentExpanded:styles$j.contentCollapsed,mediaQueryIsMatched("(prefers-reduced-motion: no-preference)")&&(expanded?styles$j.transitionExpanded:styles$j.transitionCollapsed)];const contentClasses=[styles$k.content,expanded?styles$k.contentExpanded:styles$k.contentCollapsed,expanded?styles$k.transitionExpanded:styles$k.transitionCollapsed];return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(Button,{"aria-expanded":expanded,"aria-controls":contentId,endIcon:expanded?caretUp:caretDown,kind:"tertiary",labelStyle:styles$j.labelStyle,onClick:onClick,size:"small",style:styles$j.buttonStyleOverrides,children:expanded?hidePrompt:showPrompt}),jsxRuntimeExports.jsx(View,{id:contentId,style:legacyContentStyling,className:contentClasses.join(" "),"aria-hidden":!expanded,testId:"content-container",children:jsxRuntimeExports.jsx(View,{className:styles$k.contentWrapper,style:styles$j.contentWrapper,children:jsxRuntimeExports.jsx(UserInputManager,{widgets:widgets,problemNum:0,children:({userInput,handleUserInput,initializeUserInput})=>{return jsxRuntimeExports.jsx(Renderer,{apiOptions:props.apiOptions,content:explanation,widgets:widgets,linterContext:props.linterContext,strings:strings,userInput:userInput,handleUserInput:handleUserInput,initializeUserInput:initializeUserInput})}})})})]})});var Explanation$1 = {name:"explanation",displayName:"Explanation",widget:Explanation,isLintable:true};
1773
1774
 
1774
- class FreeResponse extends React.Component{isOverLimit(){return !this.props.options.allowUnlimitedCharacters&&this.characterCount()>this.props.options.characterLimit}render(){const{allowUnlimitedCharacters,characterLimit,question,placeholder}=this.props.options;const isOverLimit=this.isOverLimit();const characterCountText=allowUnlimitedCharacters?undefined:this.context.strings.characterCount({used:this.characterCount(),num:characterLimit});if(characterCountText){this.announceCharacterCount(characterCountText,isOverLimit);}return jsxRuntimeExports.jsx(View,{style:styles$i.container,className:"free-response",children:jsxRuntimeExports.jsx(LabeledField,{label:jsxRuntimeExports.jsx(View,{className:"free-response-question",children:jsxRuntimeExports.jsx(Renderer,{content:question,strings:this.context.strings})}),field:jsxRuntimeExports.jsx(TextArea,{error:isOverLimit,onChange:this._handleUserInput,placeholder:placeholder,style:styles$i.textarea,value:this.props.userInput.currentValue}),additionalHelperMessage:jsxRuntimeExports.jsxs("p",{className:css(styles$i.characterCountText,isOverLimit?styles$i.overCharacterLimit:undefined),children:[isOverLimit&&jsxRuntimeExports.jsx(PhosphorIcon,{"aria-label":"Error:",icon:warningCircleIcon,size:"small",style:styles$i.warningCircleIcon}),characterCountText]})})})}constructor(...args){super(...args),this.isWidget=true,this.announceCharacterCount=(message,isOverLimit)=>{const level=isOverLimit?"assertive":"polite";announceMessage({message,level,debounceThreshold:750});},this.characterCount=()=>{return this.props.userInput.currentValue.replace(/\n/g,"").length},this._handleUserInput=newValue=>{this.props.handleUserInput({currentValue:newValue});};}}FreeResponse.contextType=PerseusI18nContext;function getStartUserInput$c(){return {currentValue:""}}var FreeResponse$1 = {name:"free-response",accessible:true,displayName:"Free Response (Assessments only)",widget:FreeResponse,hidden:false,getUserInputFromSerializedState:getStartUserInput$c,getStartUserInput: getStartUserInput$c};const styles$i=StyleSheet.create({container:{gap:spacing.xSmall_8},characterCountText:{color:semanticColor.core.foreground.neutral.default,fontSize:font.body.size.small,margin:0},overCharacterLimit:{color:semanticColor.core.foreground.critical.default},textarea:{padding:spacing.medium_16},warningCircleIcon:{marginInlineEnd:spacing.xSmall_8}});
1775
+ class FreeResponse extends React.Component{isOverLimit(){return !this.props.options.allowUnlimitedCharacters&&this.characterCount()>this.props.options.characterLimit}render(){const{allowUnlimitedCharacters,characterLimit,question,placeholder}=this.props.options;const isOverLimit=this.isOverLimit();const characterCountText=allowUnlimitedCharacters?undefined:this.context.strings.characterCount({used:this.characterCount(),num:characterLimit});if(characterCountText){this.announceCharacterCount(characterCountText,isOverLimit);}return jsxRuntimeExports.jsx(View,{style:styles$i.container,className:"free-response",children:jsxRuntimeExports.jsx(LabeledField,{label:jsxRuntimeExports.jsx(View,{className:"free-response-question",children:jsxRuntimeExports.jsx(Renderer,{content:question,strings:this.context.strings})}),field:jsxRuntimeExports.jsx(TextArea,{error:isOverLimit,onChange:this._handleUserInput,placeholder:placeholder,style:styles$i.textarea,value:this.props.userInput.currentValue}),additionalHelperMessage:jsxRuntimeExports.jsxs("p",{className:css(styles$i.characterCountText,isOverLimit?styles$i.overCharacterLimit:undefined),children:[isOverLimit&&jsxRuntimeExports.jsx(PhosphorIcon,{"aria-label":"Error:",icon:warningCircleIcon$1,size:"small",style:styles$i.warningCircleIcon}),characterCountText]})})})}constructor(...args){super(...args),this.isWidget=true,this.announceCharacterCount=(message,isOverLimit)=>{const level=isOverLimit?"assertive":"polite";announceMessage({message,level,debounceThreshold:750});},this.characterCount=()=>{return this.props.userInput.currentValue.replace(/\n/g,"").length},this._handleUserInput=newValue=>{this.props.handleUserInput({currentValue:newValue});};}}FreeResponse.contextType=PerseusI18nContext;function getStartUserInput$c(){return {currentValue:""}}var FreeResponse$1 = {name:"free-response",accessible:true,displayName:"Free Response (Assessments only)",widget:FreeResponse,hidden:false,getUserInputFromSerializedState:getStartUserInput$c,getStartUserInput: getStartUserInput$c};const styles$i=StyleSheet.create({container:{gap:spacing.xSmall_8},characterCountText:{color:semanticColor.core.foreground.neutral.default,fontSize:font.body.size.small,margin:0},overCharacterLimit:{color:semanticColor.core.foreground.critical.default},textarea:{padding:spacing.medium_16},warningCircleIcon:{marginInlineEnd:spacing.xSmall_8}});
1775
1776
 
1776
1777
  const getPromptJSON$i=(title,rendererJSON,hintRendererJSON)=>{if(!rendererJSON){return {type:"graded-group",title,content:"",widgets:{},hint:hintRendererJSON}}return {...rendererJSON,title,type:"graded-group",hint:hintRendererJSON}};
1777
1778
 
@@ -1986,7 +1987,7 @@ const{magnitude: magnitude$1,vector: vector$1}=geometry;function initializeGraph
1986
1987
 
1987
1988
  const{getAngleFromVertex,getClockwiseAngle,polar}=angles;const{angleMeasures,ccw,lawOfCosines,magnitude,polygonSidesIntersect,reverseVector,sign,vector}=geometry;const{getAbsoluteValueCoefficients,getLogarithmCoefficients,getExponentialCoefficients,getQuadraticCoefficients}=coefficients;const minDistanceBetweenAngleVertexAndSidePoint=2;function interactiveGraphReducer(state,action){switch(action.type){case REINITIALIZE:return initializeGraphState(action.params);case MOVE_POINT_IN_FIGURE:return doMovePointInFigure(state,action);case MOVE_LINE:return doMoveLine(state,action);case MOVE_ALL:return doMoveAll(state,action);case MOVE_POINT:return doMovePoint(state,action);case MOVE_CENTER:return doMoveCenter(state,action);case MOVE_RADIUS_POINT:return doMoveRadiusPoint(state,action);case CHANGE_SNAP_STEP:return doChangeSnapStep(state,action);case CHANGE_RANGE:return doChangeRange(state,action);case ADD_POINT:return doAddPoint(state,action);case REMOVE_POINT:return doRemovePoint(state,action);case FOCUS_POINT:return doFocusPoint(state,action);case BLUR_POINT:return doBlurPoint(state);case DELETE_INTENT:return doDeleteIntent(state);case CLICK_POINT:return doClickPoint(state,action);case CLOSE_POLYGON:return doClosePolygon(state);case OPEN_POLYGON:return doOpenPolygon(state);case CHANGE_INTERACTION_MODE:return doChangeInteractionMode(state,action);case CHANGE_KEYBOARD_INVITATION_VISIBILITY:return doChangeKeyboardInvitationVisibility(state,action);default:throw new UnreachableCaseError(action)}}function doDeleteIntent(state,action){if(isUnlimitedGraphState(state)){if(state.focusedPointIndex!==null){return doRemovePoint(state,actions.pointGraph.removePoint(state.focusedPointIndex))}}return state}function doFocusPoint(state,action){switch(state.type){case "polygon":case "point":return {...state,focusedPointIndex:action.index};default:return state}}function doBlurPoint(state,action){switch(state.type){case "polygon":case "point":const nextState={...state,showRemovePointButton:false};if(state.interactionMode==="mouse"){nextState.focusedPointIndex=null;}return nextState;default:return state}}function doClickPoint(state,action){if(isUnlimitedGraphState(state)){return {...state,focusedPointIndex:action.index,showRemovePointButton:true}}return state}function doClosePolygon(state){if(isUnlimitedGraphState(state)&&state.type==="polygon"){const noDupedPoints=getArrayWithoutDuplicates(state.coords);return {...state,coords:noDupedPoints,closedPolygon:true}}return state}function doOpenPolygon(state){if(isUnlimitedGraphState(state)&&state.type==="polygon"){return {...state,closedPolygon:false}}return state}function doChangeInteractionMode(state,action){if(isUnlimitedGraphState(state)){const nextKeyboardInvitation=action.mode==="keyboard"?false:state.showKeyboardInteractionInvitation;return {...state,interactionMode:action.mode,showKeyboardInteractionInvitation:nextKeyboardInvitation}}return state}function doChangeKeyboardInvitationVisibility(state,action){if(isUnlimitedGraphState(state)){return {...state,showKeyboardInteractionInvitation:action.shouldShow,hasBeenInteractedWith:true}}return state}function doMovePointInFigure(state,action){switch(state.type){case "segment":case "linear-system":{const newValue=boundToEdgeAndSnapToGrid(action.destination,state);const newCoords=updateAtIndex({array:state.coords,index:action.figureIndex,update:tuple=>setAtIndex({array:tuple,index:action.pointIndex,newValue})});const coordsToCheck=newCoords[action.figureIndex];if(coordsOverlap(coordsToCheck)){return state}const sharedAnnouncement={pointIndex:action.pointIndex,pointLabel:getCustomPointLabel(state.pointLabels,action.figureIndex*2+action.pointIndex),x:newValue[X],y:newValue[Y]};const stateAnnouncement=state.type==="segment"?{type:"move-segment-point",segmentIndex:action.figureIndex,totalSegments:state.coords.length,...sharedAnnouncement}:{type:"move-linear-system-point",lineIndex:action.figureIndex,...sharedAnnouncement};return {...state,hasBeenInteractedWith:true,coords:newCoords,stateAnnouncement}}case "ray":case "linear":case "vector":{const newValue=boundToEdgeAndSnapToGrid(action.destination,state);const newCoords=setAtIndex({array:state.coords,index:action.pointIndex,newValue});if(coordsOverlap(newCoords)){return state}let stateAnnouncement;if(state.type==="ray"){stateAnnouncement={type:"move-ray-point",pointIndex:action.pointIndex,pointLabel:getCustomPointLabel(state.pointLabels,action.pointIndex),x:newValue[X],y:newValue[Y]};}else if(state.type==="vector"){stateAnnouncement={type:"move-vector-point",pointIndex:action.pointIndex,x:newValue[X],y:newValue[Y]};}else {stateAnnouncement={type:"move-point",pointLabel:resolvePointLabel(state.pointLabels,action.pointIndex),x:newValue[X],y:newValue[Y]};}return {...state,hasBeenInteractedWith:true,coords:newCoords,stateAnnouncement}}case "circle":throw new Error(`Don't use movePointInFigure for circle graphs. Use moveCenter or moveRadiusPoint.`);case "angle":case "none":case "point":case "polygon":case "quadratic":case "sinusoid":case "absolute-value":case "tangent":case "exponential":case "logarithm":throw new Error(`Don't use movePointInFigure for ${state.type} graphs. Use movePoint instead!`);default:throw new UnreachableCaseError(state)}}function doMoveLine(state,action){const{snapStep,range}=state;const{newStart}=action;switch(state.type){case "segment":case "linear-system":{if(action.itemIndex===undefined){throw new Error("Please provide index of line to move")}const currentLine=state.coords[action.itemIndex];const constrainedLine=constrainShapePreservingMove(currentLine,newStart,{snapStep,range});const newCoords=setAtIndex({array:state.coords,index:action.itemIndex,newValue:constrainedLine});const stateAnnouncement=state.type==="segment"?{type:"move-segment-line",coords:constrainedLine}:{type:"move-linear-system-line",lineIndex:action.itemIndex,coords:constrainedLine};return {...state,type:state.type,hasBeenInteractedWith:true,coords:newCoords,stateAnnouncement}}case "ray":case "linear":case "vector":{const constrainedLine=constrainShapePreservingMove(state.coords,newStart,{snapStep,range});let type;if(state.type==="ray"){type="move-ray-line";}else if(state.type==="vector"){type="move-vector-line";}else {type="move-linear-line";}const stateAnnouncement={type,coords:constrainedLine};return {...state,type:state.type,hasBeenInteractedWith:true,coords:constrainedLine,stateAnnouncement}}default:return state}}function doMoveAll(state,action){const{snapStep,range}=state;const{newStart}=action;switch(state.type){case "polygon":{const useGridSnap=state.snapTo!=="sides"&&state.snapTo!=="angles";const desiredDelta=vec.sub(newStart,state.coords[0]);const change=getChange(state.coords,desiredDelta,{snapStep:useGridSnap?snapStep:[0,0],range});const newCoords=state.coords.map(point=>useGridSnap?snap(snapStep,vec.add(point,change)):vec.add(point,change));return {...state,hasBeenInteractedWith:true,coords:newCoords,stateAnnouncement:{type:"move-polygon",coords:newCoords,pointLabels:state.pointLabels}}}default:return state}}function doMovePoint(state,action){switch(state.type){case "angle":const newState=(()=>{if(action.index===1){const updatedCoords=boundAndSnapAngleVertex(state,action);return {...state,hasBeenInteractedWith:true,coords:updatedCoords}}return {...state,hasBeenInteractedWith:true,coords:setAtIndex({array:state.coords,index:action.index,newValue:boundAndSnapAngleEndPoints(action.destination,state,action.index)})}})();if(angleSidePointsTooCloseToVertex(newState)){return state}return {...newState,stateAnnouncement:{type:"move-angle-point",pointIndex:action.index,pointLabel:getCustomPointLabel(state.pointLabels,action.index),x:newState.coords[action.index][X],y:newState.coords[action.index][Y],angleMeasure:getClockwiseAngle(newState.coords,newState.allowReflexAngles??false)}};case "polygon":let newValue;if(state.snapTo==="sides"){newValue=boundAndSnapToSides(action.destination,state,action.index);}else if(state.snapTo==="angles"){newValue=boundAndSnapToPolygonAngle(action.destination,state,action.index);}else {newValue=boundToEdgeAndSnapToGrid(action.destination,state);}const newCoords=setAtIndex({array:state.coords,index:action.index,newValue:newValue});const polygonSidesCanIntersect=state.numSides==="unlimited"&&!state.closedPolygon;if(!polygonSidesCanIntersect&&polygonSidesIntersect(newCoords)){return state}return {...state,hasBeenInteractedWith:true,coords:newCoords,stateAnnouncement:{type:"move-point",pointLabel:resolvePointLabel(state.pointLabels,action.index),x:newValue[X],y:newValue[Y]}};case "point":{const newCoord=boundToEdgeAndSnapToGrid(action.destination,state);return {...state,hasBeenInteractedWith:true,focusedPointIndex:action.index,coords:setAtIndex({array:state.coords,index:action.index,newValue:newCoord}),stateAnnouncement:{type:"move-point",pointLabel:resolvePointLabel(state.pointLabels,action.index),x:newCoord[X],y:newCoord[Y]}}}case "sinusoid":{const destination=action.destination;const boundDestination=boundToEdgeAndSnapToGrid(destination,state);const newCoords=[...state.coords];newCoords[action.index]=boundDestination;if(newCoords[0][X]===newCoords[1][X]){return state}return {...state,hasBeenInteractedWith:true,coords:setAtIndex({array:state.coords,index:action.index,newValue:boundDestination}),stateAnnouncement:{type:"move-sinusoid-point",pointIndex:action.index,pointLabel:getCustomPointLabel(state.pointLabels,action.index),x:newCoords[action.index][X],y:newCoords[action.index][Y],otherY:newCoords[1-action.index][Y]}}}case "exponential":{const boundDestination=boundToEdgeAndSnapToGrid(action.destination,state);const newCoords=[...state.coords];newCoords[action.index]=boundDestination;if(newCoords[0][X]===newCoords[1][X]){return state}const expHandle=getAsymptoteHandleCoord("horizontal",state.range,state.asymptote);if(boundDestination[X]===expHandle[X]&&boundDestination[Y]===expHandle[Y]){return state}return {...state,hasBeenInteractedWith:true,coords:setAtIndex({array:state.coords,index:action.index,newValue:boundDestination}),stateAnnouncement:{type:"move-exponential-point",pointIndex:action.index,pointLabel:getCustomPointLabel(state.pointLabels,action.index),x:boundDestination[X],y:boundDestination[Y],hasCurve:getExponentialCoefficients(newCoords,state.asymptote)!==undefined}}}case "logarithm":{const boundDestination=boundToEdgeAndSnapToGrid(action.destination,state);const newCoords=[...state.coords];newCoords[action.index]=boundDestination;if(newCoords[0][X]===newCoords[1][X]){return state}if(newCoords[0][Y]===newCoords[1][Y]){return state}const logHandle=getAsymptoteHandleCoord("vertical",state.range,state.asymptote);if(boundDestination[X]===logHandle[X]&&boundDestination[Y]===logHandle[Y]){return state}return {...state,hasBeenInteractedWith:true,coords:setAtIndex({array:state.coords,index:action.index,newValue:boundDestination}),stateAnnouncement:{type:"move-logarithm-point",pointIndex:action.index,pointLabel:getCustomPointLabel(state.pointLabels,action.index),x:boundDestination[X],y:boundDestination[Y],hasCurve:getLogarithmCoefficients(newCoords,state.asymptote)!==undefined}}}case "absolute-value":{const boundDestination=boundToEdgeAndSnapToGrid(action.destination,state);const newCoords=[...state.coords];newCoords[action.index]=boundDestination;if(newCoords[0][X]===newCoords[1][X]){return state}return {...state,hasBeenInteractedWith:true,coords:setAtIndex({array:state.coords,index:action.index,newValue:boundDestination}),stateAnnouncement:{type:"move-absolute-value-point",pointIndex:action.index,pointLabel:getCustomPointLabel(state.pointLabels,action.index),x:boundDestination[X],y:boundDestination[Y],slope:getAbsoluteValueCoefficients(newCoords)[0]}}}case "tangent":{const boundDestination=boundToEdgeAndSnapToGrid(action.destination,state);const newCoords=[...state.coords];newCoords[action.index]=boundDestination;if(newCoords[0][X]===newCoords[1][X]||newCoords[0][Y]===newCoords[1][Y]){return state}return {...state,hasBeenInteractedWith:true,coords:setAtIndex({array:state.coords,index:action.index,newValue:boundDestination}),stateAnnouncement:{type:"move-tangent-point",pointIndex:action.index,pointLabel:getCustomPointLabel(state.pointLabels,action.index),x:boundDestination[X],y:boundDestination[Y]}}}case "quadratic":{const newCoords=[...state.coords];const boundDestination=boundToEdgeAndSnapToGrid(action.destination,state);newCoords[action.index]=boundDestination;const QuadraticCoefficients=getQuadraticCoefficients(newCoords);if(QuadraticCoefficients===undefined){return state}const vertex=getQuadraticVertex(QuadraticCoefficients);return {...state,hasBeenInteractedWith:true,coords:setAtIndex({array:state.coords,index:action.index,newValue:boundDestination}),stateAnnouncement:{type:"move-quadratic-point",pointIndex:action.index,pointLabel:getCustomPointLabel(state.pointLabels,action.index),x:boundDestination[X],y:boundDestination[Y],vertex}}}default:throw new Error("The movePoint action is only for point, quadratic, and polygon graphs")}}function doMoveCenter(state,action){switch(state.type){case "circle":{const constrainedCenter=boundToEdgeAndSnapToGrid(action.destination,state);const newRadiusPoint=[...vec.add(state.radiusPoint,vec.sub(constrainedCenter,state.center))];const[xMin,xMax]=state.range[X];const[radX]=newRadiusPoint;if(radX<xMin||radX>xMax){const xJumpDist=(radX-constrainedCenter[X])*2;const possibleNewX=radX-xJumpDist;if(possibleNewX>=xMin&&possibleNewX<=xMax){newRadiusPoint[X]=possibleNewX;}else {return state}}return {...state,hasBeenInteractedWith:true,center:constrainedCenter,radiusPoint:newRadiusPoint,stateAnnouncement:{type:"move-center",x:constrainedCenter[X],y:constrainedCenter[Y]}}}case "exponential":{const newY=boundAndSnapToGrid(action.destination,state)[Y];if(newY===state.asymptote){return state}const expFutureHandle=getAsymptoteHandleCoord("horizontal",state.range,newY);if(state.coords.some(c=>c[X]===expFutureHandle[X]&&c[Y]===expFutureHandle[Y])){return state}return {...state,hasBeenInteractedWith:true,asymptote:newY,stateAnnouncement:{type:"move-exponential-asymptote",asymptoteY:newY}}}case "logarithm":{const newX=boundAndSnapToGrid(action.destination,state)[X];if(newX===state.asymptote){return state}const logFutureHandle=getAsymptoteHandleCoord("vertical",state.range,newX);if(state.coords.some(c=>c[X]===logFutureHandle[X]&&c[Y]===logFutureHandle[Y])){return state}return {...state,hasBeenInteractedWith:true,asymptote:newX,stateAnnouncement:{type:"move-logarithm-asymptote",asymptoteX:newX}}}default:throw new Error("The doMoveCenter action is only for circle, exponential, or logarithm graphs")}}function doMoveRadiusPoint(state,action){switch(state.type){case "circle":{const nextRadiusPoint=boundToEdgeAndSnapToGrid([action.destination[X],state.center[1]],state);if(vector$3.equal(nextRadiusPoint,state.center)){return state}return {...state,hasBeenInteractedWith:true,radiusPoint:nextRadiusPoint,stateAnnouncement:{type:"move-radius-point",x:nextRadiusPoint[X],y:nextRadiusPoint[Y],centerX:state.center[X],radius:vec.dist(state.center,nextRadiusPoint),pointLabel:getCustomPointLabel(state.pointLabels,0)}}}default:throw new Error("The doMoveRadiusPoint action is only for circle graphs")}}function doChangeSnapStep(state,action){if(_.isEqual(state.snapStep,action.snapStep)){return state}return {...state,snapStep:action.snapStep}}function doChangeRange(state,action){if(_.isEqual(state.range,action.range)){return state}return {...state,range:action.range}}function doAddPoint(state,action){if(!isUnlimitedGraphState(state)){return state}const{snapStep}=state;const snappedPoint=snap(snapStep,action.location);for(const point of state.coords){if(point[X]===snappedPoint[X]&&point[Y]===snappedPoint[Y]){return state}}const newCoords=[...state.coords,snappedPoint];return {...state,hasBeenInteractedWith:true,coords:newCoords,showRemovePointButton:true,focusedPointIndex:newCoords.length-1}}function doRemovePoint(state,action){if(!isUnlimitedGraphState(state)){return state}const nextFocusedPointIndex=state.coords.length>1?state.coords.length-2:null;return {...state,coords:state.coords.filter((_,i)=>i!==action.index),focusedPointIndex:nextFocusedPointIndex,showRemovePointButton:nextFocusedPointIndex!==null?true:false}}const getDeltaVertex=(maxMoves,minMoves,delta)=>{const[deltaX,deltaY]=delta;const maxXMove=Math.min(...maxMoves.map(move=>move[X]));const maxYMove=Math.min(...maxMoves.map(move=>move[Y]));const minXMove=Math.max(...minMoves.map(move=>move[X]));const minYMove=Math.max(...minMoves.map(move=>move[Y]));const dx=clamp(deltaX,minXMove,maxXMove);const dy=clamp(deltaY,minYMove,maxYMove);return [dx,dy]};const getChange=(coords,delta,constraintOpts)=>{const maxMoves=coords.map(point=>maxMove({...constraintOpts,point}));const minMoves=coords.map(point=>minMove({...constraintOpts,point}));const[dx,dy]=getDeltaVertex(maxMoves,minMoves,delta);return [dx,dy]};const constrainShapePreservingMove=(currentLine,newStart,constraintOpts)=>{const desiredDelta=vec.sub(newStart,currentLine[0]);const change=getChange(currentLine,desiredDelta,constraintOpts);const{snapStep}=constraintOpts;return [snap(snapStep,vec.add(currentLine[0],change)),snap(snapStep,vec.add(currentLine[1],change))]};function leq(a,b){return a<b||approximateEqual(a,b)}function boundAndSnapToGrid(point,{snapStep,range}){return snap(snapStep,bound$1({snapStep,range,point}))}function boundAndSnapAngleVertex({range,coords,snapStep},{destination}){const coordsCopy=[...coords];const startingVertex=coordsCopy[1];const insetAmount=vec.add(snapStep,[minDistanceBetweenAngleVertexAndSidePoint,minDistanceBetweenAngleVertexAndSidePoint]);const newVertex=clampToBox(inset(insetAmount,range),snap(snapStep,destination));const delta=vec.add(newVertex,reverseVector(startingVertex));const newPoints={};for(const i of [0,2]){const oldPoint=coordsCopy[i];let newPoint=vec.add(oldPoint,delta);let angle=getAngleFromVertex(newVertex,newPoint);angle*=Math.PI/180;newPoint=constrainToBoundsOnAngle(newPoint,angle,range,snapStep);newPoints[i]=newPoint;}newPoints[1]=newVertex;Object.entries(newPoints).forEach(([i,newPoint])=>{coordsCopy[i]=newPoint;});return coordsCopy}function tooClose(point1,point2,range){const safeDistance=2;const distance=vec.dist(point1,point2);return distance<safeDistance}function constrainToBoundsOnAngle(point,angle,range,snapStep){const lower=[range[0][0]+snapStep[0],range[1][0]+snapStep[0]];const upper=[range[0][1]-snapStep[1],range[1][1]-snapStep[1]];let result=point;if(result[0]<lower[0]){result=[lower[0],result[1]+(lower[0]-result[0])*Math.tan(angle)];}else if(result[0]>upper[0]){result=[upper[0],result[1]-(result[0]-upper[0])*Math.tan(angle)];}if(result[1]<lower[1]){result=[result[0]+(lower[1]-result[1])/Math.tan(angle),lower[1]];}else if(result[1]>upper[1]){result=[result[0]-(result[1]-upper[1])/Math.tan(angle),upper[1]];}return result}function boundAndSnapAngleEndPoints(destinationPoint,{range,coords,snapDegrees,angleOffsetDeg,snapStep},index){const snap=snapDegrees||1;const offsetDegrees=angleOffsetDeg||0;const coordsCopy=[...coords];const angleRange=[[range[0][0]+snapStep[0],range[0][1]-snapStep[0]],[range[1][0]+snapStep[1],range[1][1]-snapStep[1]]];const boundPoint=bound$1({snapStep:[0,0],range:angleRange,point:destinationPoint});coordsCopy[index]=boundPoint;const vertex=coords[1];let angle=getAngleFromVertex(coordsCopy[index],vertex);angle=Math.round((angle-offsetDegrees)/snap)*snap+offsetDegrees;const minDistance=minDistanceBetweenAngleVertexAndSidePoint+.01;const distance=Math.max(vec.dist(coordsCopy[index],vertex),minDistance);const snappedValue=vec.add(vertex,polar(distance,angle));return snappedValue}function angleSidePointsTooCloseToVertex(state){return tooClose(state.coords[0],state.coords[1],state.range)||tooClose(state.coords[2],state.coords[1],state.range)}function boundAndSnapToPolygonAngle(destinationPoint,{range,coords},index){const startingPoint=coords[index];return calculateAngleSnap(destinationPoint,range,coords,index,startingPoint)}function calculateAngleSnap(destinationPoint,range,coords,index,startingPoint){const coordsCopy=[...coords];coordsCopy[index]=bound$1({snapStep:[0,0],range,point:destinationPoint});const angles=angleMeasures(coordsCopy).map(angle=>angle*180/Math.PI);const rel=j=>{return (index+j+coordsCopy.length)%coordsCopy.length};_.each([-1,1],function(j){angles[rel(j)]=Math.round(angles[rel(j)]);});const getAngle=function(a,vertex,b){const angle=getClockwiseAngle([coordsCopy[rel(a)],coordsCopy[rel(vertex)],coordsCopy[rel(b)]]);return angle};const innerAngles=[angles[rel(-1)]-getAngle(-2,-1,1),angles[rel(1)]-getAngle(-1,1,2)];innerAngles[2]=180-(innerAngles[0]+innerAngles[1]);if(innerAngles.some(function(angle){return leq(angle,1)})){return startingPoint}const knownSide=magnitude(vector(coordsCopy[rel(-1)],coordsCopy[rel(1)]));const onLeft=sign(ccw(coordsCopy[rel(-1)],coordsCopy[rel(1)],coordsCopy[index]))===1;const side=Math.sin(innerAngles[1]*Math.PI/180)/Math.sin(innerAngles[2]*Math.PI/180)*knownSide;const outerAngle=getAngleFromVertex(coordsCopy[rel(1)],coordsCopy[rel(-1)]);const offset=polar(side,outerAngle+(onLeft?1:-1)*innerAngles[0]);return vector$3.add(coordsCopy[rel(-1)],offset)}function boundAndSnapToSides(destinationPoint,{range,coords},index){const startingPoint=coords[index];return calculateSideSnap(destinationPoint,range,coords,index,startingPoint)}function calculateSideSnap(destinationPoint,range,coords,index,startingPoint){const boundedDestinationPoint=bound$1({snapStep:[0,0],range,point:destinationPoint});const rel=j=>{return (index+j+coords.length)%coords.length};const sides=[[coords[rel(-1)],boundedDestinationPoint],[boundedDestinationPoint,coords[rel(1)]],[coords[rel(-1)],coords[rel(1)]]].map(function(coords){return magnitude(vector(...coords))});_.each([0,1],function(j){sides[j]=Math.round(sides[j]);});if(leq(sides[1]+sides[2],sides[0])||leq(sides[0]+sides[2],sides[1])||leq(sides[0]+sides[1],sides[2])){return startingPoint}const innerAngle=lawOfCosines(sides[0],sides[2],sides[1]);const outerAngle=getAngleFromVertex(coords[rel(1)],coords[rel(-1)]);const onLeft=sign(ccw(coords[rel(-1)],coords[rel(1)],boundedDestinationPoint))===1;const offset=polar(sides[0],outerAngle+(onLeft?1:-1)*innerAngle);return vector$3.add(coords[rel(-1)],offset)}function maxMove({snapStep,range,point}){const topRight=boundToEdgeAndSnapToGrid([Infinity,Infinity],{snapStep,range});return vec.sub(topRight,point)}function minMove({snapStep,range,point}){const bottomLeft=boundToEdgeAndSnapToGrid([-Infinity,-Infinity],{snapStep,range});return vec.sub(bottomLeft,point)}const coordsOverlap=coords=>coords.some((coord,i)=>coords.some((c,j)=>i!==j&&vector$3.equal(coord,c)));function updateAtIndex(args){const{array,index,update}=args;const newValue=update(array[index]);return setAtIndex({array,index,newValue})}function setAtIndex(args){const{array,index,newValue}=args;const copy=[...array];copy[index]=newValue;return copy}
1988
1989
 
1989
- const{clockwise}=geometry;const{convertRadiansToDegrees}=angles;function renderPolygonGraph(state,dispatch,i18n,markings){return {graph:jsxRuntimeExports.jsx(PolygonGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:describePolygonGraph(state,i18n,markings).srPolygonInteractiveElements}}const PolygonGraph=props=>{const{dispatch}=props;const{numSides,coords,snapStep,snapTo="grid"}=props.graphState;const graphConfig=useGraphConfig();const polygonRef=React.useRef(null);const polygonHitboxRef=React.useRef(null);const pointsRef=React.useRef([]);const lastMoveTimeRef=React.useRef(0);const{range:[x,y]}=graphConfig;const[[left,top]]=useTransformVectorsToPixels([x[0],y[1]]);const dragReferencePoint=coords[0];const constrain=getKeyboardMovementConstraintForPolygon(snapStep,snapTo);const moveAll=newStart=>{dispatch(actions.polygon.moveAll(newStart));};useDraggable({gestureTarget:polygonRef,point:dragReferencePoint,onMove:moveAll,constrainKeyboardMovement:constrain});const{dragging}=useDraggable({gestureTarget:polygonHitboxRef,point:dragReferencePoint,onMove:moveAll,constrainKeyboardMovement:constrain});const[hovered,setHovered]=React.useState(false);const[focusVisible,setFocusVisible]=React.useState(false);const polygonHitbox=useHitbox({shape:{kind:"polygon",vertices:coords},hitboxRef:polygonHitboxRef,layer:"body",dragging,onHoverChange:setHovered,testId:"movable-polygon__hitbox"});React.useEffect(()=>{const focusedIndex=props.graphState.focusedPointIndex;if(focusedIndex!=null){pointsRef.current[focusedIndex]?.focus();}},[props.graphState.focusedPointIndex,props.graphState.coords.length,pointsRef]);React.useEffect(()=>{if(numSides==="unlimited"&&props.graphState.coords.length>2){dispatch(actions.polygon.closePolygon());}},[]);const statefulProps={...props,graphConfig,polygonRef,pointsRef,lastMoveTimeRef,left,top,dragging,points:coords,hovered,setHovered,focusVisible,setFocusVisible};return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[polygonHitbox,numSides==="unlimited"?jsxRuntimeExports.jsx(UnlimitedPolygonGraph,{...statefulProps}):jsxRuntimeExports.jsx(LimitedPolygonGraph,{...statefulProps})]})};const LimitedPolygonGraph=statefulProps=>{const{dispatch,hovered,setHovered,focusVisible,setFocusVisible,graphConfig,polygonRef,lastMoveTimeRef,dragging,points}=statefulProps;const{showAngles,showSides,range,snapTo="grid",snapStep,pointLabels}=statefulProps.graphState;const{disableKeyboardInteraction,interactiveColor}=graphConfig;const{strings,locale}=usePerseusI18n();const buildLabel=usePointAriaLabel(pointLabels);const id=React.useId();const lines=getLines(points);const polygonPointsNumId=id+"-points-num";const polygonPointsId=id+"-points";const{srPolygonGraph,srPolygonGraphPointsNum,srPolygonGraphPoints,srPolygonElementsNum}=describePolygonGraph(statefulProps.graphState,{strings,locale},statefulProps.graphConfig.markings);return jsxRuntimeExports.jsxs("g",{"aria-label":srPolygonGraph,"aria-describedby":`${polygonPointsNumId} ${polygonPointsId}`,children:[jsxRuntimeExports.jsx(Polygon,{points:[...points],color:interactiveColor,svgPolygonProps:{strokeWidth:focusVisible?"var(--movable-line-stroke-weight-active)":"var(--movable-line-stroke-weight)",style:{fill:"transparent"},"aria-hidden":true}}),points.map((point,i)=>{const pt1=points.at(i-1);const pt2=points[(i+1)%points.length];if(!pt1||!pt2){return null}return jsxRuntimeExports.jsx(PolygonAngle,{centerPoint:point,endPoints:[pt1,pt2],areEndPointsClockwise:clockwise(points),showAngles:!!showAngles,snapTo:snapTo},"angle-"+i)}),showSides&&lines.map(([start,end],i)=>{const[x,y]=vec.midpoint(start,end);const length=vec.dist(start,end);const isApprox=!Number.isInteger(length);return jsxRuntimeExports.jsx(TextLabel,{x:x,y:y,children:isApprox?`≈ ${length.toFixed(snapTo==="sides"?0:1)}`:length},"side-"+i)}),jsxRuntimeExports.jsx(Polygon,{points:[...points],color:"transparent",svgPolygonProps:{ref:polygonRef,tabIndex:disableKeyboardInteraction?-1:0,strokeWidth:TARGET_SIZE,style:{cursor:dragging?"grabbing":"grab",fill:hovered?"var(--mafs-blue)":"transparent"},onMouseEnter:()=>setHovered(true),onMouseLeave:()=>setHovered(false),onKeyDownCapture:()=>{setFocusVisible(hasFocusVisible(polygonRef.current));},onFocus:()=>{setFocusVisible(hasFocusVisible(polygonRef.current));},onBlur:()=>setFocusVisible(hasFocusVisible(polygonRef.current)),className:"movable-polygon",role:"button","aria-label":srPolygonGraphPoints?`${srPolygonElementsNum} ${srPolygonGraphPoints}`:srPolygonElementsNum,"aria-disabled":disableKeyboardInteraction}}),points.map((point,i)=>{const angleId=`${id}-angle-${i}`;const side1Id=`${id}-point-${i}-side-1`;const side2Id=`${id}-point-${i}-side-2`;const angle=getAngleFromPoints(points,i);const angleDegree=angle?convertRadiansToDegrees(angle):null;const sidesArray=getSideLengthsFromPoints(points,i);const{pointIndex:point1Index,sideLength:side1Length}=sidesArray[0];const{pointIndex:point2Index,sideLength:side2Length}=sidesArray[1];return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx(MovablePoint$3,{ariaDescribedBy:`${angleId} ${side1Id} ${side2Id}`,ariaLabel:buildLabel(i,point),constrain:getKeyboardMovementConstraintForPoint(points,i,range,snapStep,snapTo),point:point,sequenceNumber:i+1,onMove:destination=>{const now=Date.now();const targetFPS=40;const moveThresholdTime=1e3/targetFPS;if(now-lastMoveTimeRef.current>moveThresholdTime){dispatch(actions.polygon.movePoint(i,destination));lastMoveTimeRef.current=now;}}}),angleDegree&&jsxRuntimeExports.jsx(SRDescInSVG,{id:angleId,children:Number.isInteger(angleDegree)?strings.srPolygonPointAngle({angle:angleDegree}):strings.srPolygonPointAngleApprox({angle:srFormatNumber(angleDegree,locale,1)})}),jsxRuntimeExports.jsx(SRDescInSVG,{id:side1Id,children:getPolygonSideString(side1Length,point1Index,strings,locale)}),jsxRuntimeExports.jsx(SRDescInSVG,{id:side2Id,children:getPolygonSideString(side2Length,point2Index,strings,locale)})]},"point-"+i)}),jsxRuntimeExports.jsx(SRDescInSVG,{id:polygonPointsNumId,children:srPolygonGraphPointsNum}),srPolygonGraphPoints&&jsxRuntimeExports.jsx(SRDescInSVG,{id:polygonPointsId,children:srPolygonGraphPoints})]})};const UnlimitedPolygonGraph=statefulProps=>{const{dispatch,graphConfig,left,top,pointsRef,points}=statefulProps;const{coords,closedPolygon,pointLabels}=statefulProps.graphState;const{strings,locale}=usePerseusI18n();const buildLabel=usePointAriaLabel(pointLabels);const{interactiveColor}=useGraphConfig();const[isCurrentlyDragging,setIsCurrentlyDragging]=useState(false);const dragEndCallbackTimer=useTimeout(()=>setIsCurrentlyDragging(false),400);const id=React.useId();const polygonPointsNumId=id+"-points-num";const polygonPointsId=id+"-points";if(closedPolygon){const closedPolygonProps={...statefulProps,numSides:coords.length};return jsxRuntimeExports.jsx(LimitedPolygonGraph,{...closedPolygonProps})}const{graphDimensionsInPixels}=graphConfig;const widthPx=graphDimensionsInPixels[0];const heightPx=graphDimensionsInPixels[1];const emptyGraph=coords.length===0;const{srPolygonGraph,srPolygonGraphPointsNum,srPolygonGraphPoints}=describePolygonGraph(statefulProps.graphState,{strings,locale},statefulProps.graphConfig.markings);return jsxRuntimeExports.jsxs("g",{"aria-label":emptyGraph?strings.srUnlimitedPolygonEmpty:srPolygonGraph,"aria-describedby":`${polygonPointsNumId} ${polygonPointsId}`,children:[jsxRuntimeExports.jsx(Polyline,{points:[...points],color:interactiveColor,svgPolylineProps:{strokeWidth:"var(--movable-line-stroke-weight)",style:{fill:"transparent"},"aria-hidden":true}}),jsxRuntimeExports.jsx("rect",{"aria-hidden":true,style:{fill:"none",pointerEvents:"all",cursor:"crosshair"},width:widthPx,height:heightPx,x:left,y:top,onClick:event=>{if(isCurrentlyDragging){return}const elementRect=event.currentTarget.getBoundingClientRect();const zoomFactor=getCSSZoomFactor(event.currentTarget);const x=(event.clientX-elementRect.x)/zoomFactor;const y=(event.clientY-elementRect.y)/zoomFactor;const graphCoordinates=pixelsToVectors([[x,y]],graphConfig);dispatch(actions.polygon.addPoint(graphCoordinates[0]));}}),coords.map((point,i)=>{const angleId=`${id}-angle-${i}`;let sideIds="";const hasAngle=i>0&&i<coords.length-1;const angle=hasAngle?getAngleFromPoints(points,i):null;const angleDegree=angle?convertRadiansToDegrees(angle):null;const sidesArray=getSideLengthsFromPoints(points,i,true);for(let sideIndex=0;sideIndex<sidesArray.length;sideIndex++){sideIds+=`${id}-point-${i}-side-${sideIndex} `;}return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx(MovablePoint$3,{ariaDescribedBy:`${angleId} ${sideIds}`,ariaLabel:buildLabel(i,point),point:point,sequenceNumber:i+1,onDragStart:()=>{dragEndCallbackTimer.clear();setIsCurrentlyDragging(true);},onMove:destination=>{dispatch(actions.polygon.movePoint(i,destination));},onDragEnd:()=>{dragEndCallbackTimer.set();},ref:ref=>{pointsRef.current[i]=ref;},onFocus:()=>{dispatch(actions.polygon.focusPoint(i));},onClick:()=>{if(i===0&&getArrayWithoutDuplicates(coords).length>=3){dispatch(actions.polygon.closePolygon());}dispatch(actions.polygon.clickPoint(i));}}),angleDegree&&jsxRuntimeExports.jsx(SRDescInSVG,{id:angleId,children:Number.isInteger(angleDegree)?strings.srPolygonPointAngle({angle:angleDegree}):strings.srPolygonPointAngleApprox({angle:srFormatNumber(angleDegree,locale,1)})}),sidesArray.map(({pointIndex,sideLength},j)=>jsxRuntimeExports.jsx(SRDescInSVG,{id:`${id}-point-${i}-side-${j}`,children:getPolygonSideString(sideLength,pointIndex,strings,locale)},`${id}-point-${i}-side-${j}`))]},"point-"+i)}),coords.length>0&&jsxRuntimeExports.jsx(SRDescInSVG,{id:polygonPointsNumId,children:srPolygonGraphPointsNum}),srPolygonGraphPoints&&jsxRuntimeExports.jsx(SRDescInSVG,{id:polygonPointsId,children:srPolygonGraphPoints})]})};function getLines(points){return points.map((point,i)=>{const next=points[(i+1)%points.length];return [point,next]})}const hasFocusVisible=element=>{const matches=selector=>element?.matches(selector)??false;try{return matches(":focus-visible")}catch{return matches(":focus")}};function getKeyboardMovementConstraintForPoint(points,index,range,snapStep,snapTo){switch(snapTo){case "grid":return p=>snap(snapStep,p);case "sides":return getSideSnapConstraint(points,index,range);case "angles":return getAngleSnapConstraint(points,index,range);default:throw new UnreachableCaseError(snapTo)}}function getKeyboardMovementConstraintForPolygon(snapStep,snapTo){switch(snapTo){case "grid":return p=>snap(snapStep,p);case "sides":case "angles":return p=>p;default:throw new UnreachableCaseError(snapTo)}}function getSideSnapConstraint(points,index,range){const newPoints=[...points];const pointToBeMoved=newPoints[index];const movePointWithConstraint=moveFunc=>{let destinationAttempt=moveFunc(pointToBeMoved);let newPoint=pointToBeMoved;while(newPoint[0]===pointToBeMoved[0]&&newPoint[1]===pointToBeMoved[1]&&isInBound({range,point:destinationAttempt})){newPoint=calculateSideSnap(destinationAttempt,range,newPoints,index,pointToBeMoved);destinationAttempt=moveFunc(destinationAttempt);}return newPoint};return {up:movePointWithConstraint(coord=>vec.add(coord,[0,1])),down:movePointWithConstraint(coord=>vec.sub(coord,[0,1])),left:movePointWithConstraint(coord=>vec.sub(coord,[1,0])),right:movePointWithConstraint(coord=>vec.add(coord,[1,0]))}}function getAngleSnapConstraint(points,index,range){const newPoints=[...points];const pointToBeMoved=newPoints[index];const movePointWithConstraint=moveFunc=>{let destinationAttempt=bound$1({snapStep:[0,0],range,point:moveFunc(pointToBeMoved)});let newPoint=pointToBeMoved;while(newPoint[0]===pointToBeMoved[0]&&newPoint[1]===pointToBeMoved[1]&&isInBound({range,point:destinationAttempt})){newPoint=calculateAngleSnap(destinationAttempt,range,newPoints,index,pointToBeMoved);destinationAttempt=moveFunc(destinationAttempt);}return newPoint};return {up:movePointWithConstraint(coord=>vec.add(coord,[0,.1])),down:movePointWithConstraint(coord=>vec.sub(coord,[0,.1])),left:movePointWithConstraint(coord=>vec.sub(coord,[.1,0])),right:movePointWithConstraint(coord=>vec.add(coord,[.1,0]))}}
1990
+ const{clockwise}=geometry;const{convertRadiansToDegrees}=angles;function renderPolygonGraph(state,dispatch,i18n,markings){return {graph:jsxRuntimeExports.jsx(PolygonGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:describePolygonGraph(state,i18n,markings).srPolygonInteractiveElements}}const PolygonGraph=props=>{const{dispatch}=props;const{numSides,coords,snapStep,snapTo="grid"}=props.graphState;const graphConfig=useGraphConfig();const polygonRef=React.useRef(null);const polygonHitboxRef=React.useRef(null);const pointsRef=React.useRef([]);const lastMoveTimeRef=React.useRef(0);const{range:[x,y]}=graphConfig;const[[left,top]]=useTransformVectorsToPixels([x[0],y[1]]);const dragReferencePoint=coords[0];const constrain=getKeyboardMovementConstraintForPolygon(snapStep,snapTo);const moveAll=newStart=>{dispatch(actions.polygon.moveAll(newStart));};useDraggable({gestureTarget:polygonRef,point:dragReferencePoint,onMove:moveAll,constrainKeyboardMovement:constrain});const{dragging}=useDraggable({gestureTarget:polygonHitboxRef,point:dragReferencePoint,onMove:moveAll,constrainKeyboardMovement:constrain});const[hovered,setHovered]=React.useState(false);const[focusVisible,setFocusVisible]=React.useState(false);const polygonHitbox=useHitbox({shape:{kind:"polygon",vertices:coords},hitboxRef:polygonHitboxRef,layer:"body",dragging,onHoverChange:setHovered,testId:"movable-polygon__hitbox"});React.useEffect(()=>{const focusedIndex=props.graphState.focusedPointIndex;if(focusedIndex!=null){pointsRef.current[focusedIndex]?.focus();}},[props.graphState.focusedPointIndex,props.graphState.coords.length,pointsRef]);React.useEffect(()=>{if(numSides==="unlimited"&&props.graphState.coords.length>2){dispatch(actions.polygon.closePolygon());}},[]);const statefulProps={...props,graphConfig,polygonRef,pointsRef,lastMoveTimeRef,left,top,dragging,points:coords,hovered,setHovered,focusVisible,setFocusVisible};return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[polygonHitbox,numSides==="unlimited"?jsxRuntimeExports.jsx(UnlimitedPolygonGraph,{...statefulProps}):jsxRuntimeExports.jsx(LimitedPolygonGraph,{...statefulProps})]})};const LimitedPolygonGraph=statefulProps=>{const{dispatch,hovered,setHovered,focusVisible,setFocusVisible,graphConfig,polygonRef,lastMoveTimeRef,dragging,points}=statefulProps;const{showAngles,showSides,range,snapTo="grid",snapStep,pointLabels}=statefulProps.graphState;const{disableKeyboardInteraction,interactiveColor}=graphConfig;const{strings,locale}=usePerseusI18n();const buildLabel=usePointAriaLabel(pointLabels);const id=React.useId();const lines=getLines(points);const polygonPointsNumId=id+"-points-num";const polygonPointsId=id+"-points";const{srPolygonGraph,srPolygonGraphPointsNum,srPolygonGraphPoints,srPolygonElementsNum}=describePolygonGraph(statefulProps.graphState,{strings,locale},statefulProps.graphConfig.markings);return jsxRuntimeExports.jsxs("g",{"aria-label":srPolygonGraph,"aria-describedby":`${polygonPointsNumId} ${polygonPointsId}`,children:[jsxRuntimeExports.jsx(Polygon,{points:[...points],color:interactiveColor,svgPolygonProps:{strokeWidth:focusVisible?"var(--movable-line-stroke-weight-active)":"var(--movable-line-stroke-weight)",style:{fill:"transparent"},"aria-hidden":true}}),points.map((point,i)=>{const pt1=points.at(i-1);const pt2=points[(i+1)%points.length];if(!pt1||!pt2){return null}return jsxRuntimeExports.jsx(PolygonAngle,{centerPoint:point,endPoints:[pt1,pt2],areEndPointsClockwise:clockwise(points),showAngles:!!showAngles,snapTo:snapTo},"angle-"+i)}),showSides&&lines.map(([start,end],i)=>{const[x,y]=line.midpoint([start,end]);const length=vec.dist(start,end);const isApprox=!Number.isInteger(length);return jsxRuntimeExports.jsx(TextLabel,{x:x,y:y,children:isApprox?`≈ ${length.toFixed(snapTo==="sides"?0:1)}`:length},"side-"+i)}),jsxRuntimeExports.jsx(Polygon,{points:[...points],color:"transparent",svgPolygonProps:{ref:polygonRef,tabIndex:disableKeyboardInteraction?-1:0,strokeWidth:TARGET_SIZE,style:{cursor:dragging?"grabbing":"grab",fill:hovered?"var(--mafs-blue)":"transparent"},onMouseEnter:()=>setHovered(true),onMouseLeave:()=>setHovered(false),onKeyDownCapture:()=>{setFocusVisible(hasFocusVisible(polygonRef.current));},onFocus:()=>{setFocusVisible(hasFocusVisible(polygonRef.current));},onBlur:()=>setFocusVisible(hasFocusVisible(polygonRef.current)),className:"movable-polygon",role:"button","aria-label":srPolygonGraphPoints?`${srPolygonElementsNum} ${srPolygonGraphPoints}`:srPolygonElementsNum,"aria-disabled":disableKeyboardInteraction}}),points.map((point,i)=>{const angleId=`${id}-angle-${i}`;const side1Id=`${id}-point-${i}-side-1`;const side2Id=`${id}-point-${i}-side-2`;const angle=getAngleFromPoints(points,i);const angleDegree=angle?convertRadiansToDegrees(angle):null;const sidesArray=getSideLengthsFromPoints(points,i);const{pointIndex:point1Index,sideLength:side1Length}=sidesArray[0];const{pointIndex:point2Index,sideLength:side2Length}=sidesArray[1];return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx(MovablePoint$3,{ariaDescribedBy:`${angleId} ${side1Id} ${side2Id}`,ariaLabel:buildLabel(i,point),constrain:getKeyboardMovementConstraintForPoint(points,i,range,snapStep,snapTo),point:point,sequenceNumber:i+1,onMove:destination=>{const now=Date.now();const targetFPS=40;const moveThresholdTime=1e3/targetFPS;if(now-lastMoveTimeRef.current>moveThresholdTime){dispatch(actions.polygon.movePoint(i,destination));lastMoveTimeRef.current=now;}}}),angleDegree&&jsxRuntimeExports.jsx(SRDescInSVG,{id:angleId,children:Number.isInteger(angleDegree)?strings.srPolygonPointAngle({angle:angleDegree}):strings.srPolygonPointAngleApprox({angle:srFormatNumber(angleDegree,locale,1)})}),jsxRuntimeExports.jsx(SRDescInSVG,{id:side1Id,children:getPolygonSideString(side1Length,point1Index,strings,locale)}),jsxRuntimeExports.jsx(SRDescInSVG,{id:side2Id,children:getPolygonSideString(side2Length,point2Index,strings,locale)})]},"point-"+i)}),jsxRuntimeExports.jsx(SRDescInSVG,{id:polygonPointsNumId,children:srPolygonGraphPointsNum}),srPolygonGraphPoints&&jsxRuntimeExports.jsx(SRDescInSVG,{id:polygonPointsId,children:srPolygonGraphPoints})]})};const UnlimitedPolygonGraph=statefulProps=>{const{dispatch,graphConfig,left,top,pointsRef,points}=statefulProps;const{coords,closedPolygon,pointLabels}=statefulProps.graphState;const{strings,locale}=usePerseusI18n();const buildLabel=usePointAriaLabel(pointLabels);const{interactiveColor}=useGraphConfig();const[isCurrentlyDragging,setIsCurrentlyDragging]=useState(false);const dragEndCallbackTimer=useTimeout(()=>setIsCurrentlyDragging(false),400);const id=React.useId();const polygonPointsNumId=id+"-points-num";const polygonPointsId=id+"-points";if(closedPolygon){const closedPolygonProps={...statefulProps,numSides:coords.length};return jsxRuntimeExports.jsx(LimitedPolygonGraph,{...closedPolygonProps})}const{graphDimensionsInPixels}=graphConfig;const widthPx=graphDimensionsInPixels[0];const heightPx=graphDimensionsInPixels[1];const emptyGraph=coords.length===0;const{srPolygonGraph,srPolygonGraphPointsNum,srPolygonGraphPoints}=describePolygonGraph(statefulProps.graphState,{strings,locale},statefulProps.graphConfig.markings);return jsxRuntimeExports.jsxs("g",{"aria-label":emptyGraph?strings.srUnlimitedPolygonEmpty:srPolygonGraph,"aria-describedby":`${polygonPointsNumId} ${polygonPointsId}`,children:[jsxRuntimeExports.jsx(Polyline,{points:[...points],color:interactiveColor,svgPolylineProps:{strokeWidth:"var(--movable-line-stroke-weight)",style:{fill:"transparent"},"aria-hidden":true}}),jsxRuntimeExports.jsx("rect",{"aria-hidden":true,style:{fill:"none",pointerEvents:"all",cursor:"crosshair"},width:widthPx,height:heightPx,x:left,y:top,onClick:event=>{if(isCurrentlyDragging){return}const elementRect=event.currentTarget.getBoundingClientRect();const zoomFactor=getCSSZoomFactor(event.currentTarget);const x=(event.clientX-elementRect.x)/zoomFactor;const y=(event.clientY-elementRect.y)/zoomFactor;const graphCoordinates=pixelsToVectors([[x,y]],graphConfig);dispatch(actions.polygon.addPoint(graphCoordinates[0]));}}),coords.map((point,i)=>{const angleId=`${id}-angle-${i}`;let sideIds="";const hasAngle=i>0&&i<coords.length-1;const angle=hasAngle?getAngleFromPoints(points,i):null;const angleDegree=angle?convertRadiansToDegrees(angle):null;const sidesArray=getSideLengthsFromPoints(points,i,true);for(let sideIndex=0;sideIndex<sidesArray.length;sideIndex++){sideIds+=`${id}-point-${i}-side-${sideIndex} `;}return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx(MovablePoint$3,{ariaDescribedBy:`${angleId} ${sideIds}`,ariaLabel:buildLabel(i,point),point:point,sequenceNumber:i+1,onDragStart:()=>{dragEndCallbackTimer.clear();setIsCurrentlyDragging(true);},onMove:destination=>{dispatch(actions.polygon.movePoint(i,destination));},onDragEnd:()=>{dragEndCallbackTimer.set();},ref:ref=>{pointsRef.current[i]=ref;},onFocus:()=>{dispatch(actions.polygon.focusPoint(i));},onClick:()=>{if(i===0&&getArrayWithoutDuplicates(coords).length>=3){dispatch(actions.polygon.closePolygon());}dispatch(actions.polygon.clickPoint(i));}}),angleDegree&&jsxRuntimeExports.jsx(SRDescInSVG,{id:angleId,children:Number.isInteger(angleDegree)?strings.srPolygonPointAngle({angle:angleDegree}):strings.srPolygonPointAngleApprox({angle:srFormatNumber(angleDegree,locale,1)})}),sidesArray.map(({pointIndex,sideLength},j)=>jsxRuntimeExports.jsx(SRDescInSVG,{id:`${id}-point-${i}-side-${j}`,children:getPolygonSideString(sideLength,pointIndex,strings,locale)},`${id}-point-${i}-side-${j}`))]},"point-"+i)}),coords.length>0&&jsxRuntimeExports.jsx(SRDescInSVG,{id:polygonPointsNumId,children:srPolygonGraphPointsNum}),srPolygonGraphPoints&&jsxRuntimeExports.jsx(SRDescInSVG,{id:polygonPointsId,children:srPolygonGraphPoints})]})};function getLines(points){return points.map((point,i)=>{const next=points[(i+1)%points.length];return [point,next]})}const hasFocusVisible=element=>{const matches=selector=>element?.matches(selector)??false;try{return matches(":focus-visible")}catch{return matches(":focus")}};function getKeyboardMovementConstraintForPoint(points,index,range,snapStep,snapTo){switch(snapTo){case "grid":return p=>snap(snapStep,p);case "sides":return getSideSnapConstraint(points,index,range);case "angles":return getAngleSnapConstraint(points,index,range);default:throw new UnreachableCaseError(snapTo)}}function getKeyboardMovementConstraintForPolygon(snapStep,snapTo){switch(snapTo){case "grid":return p=>snap(snapStep,p);case "sides":case "angles":return p=>p;default:throw new UnreachableCaseError(snapTo)}}function getSideSnapConstraint(points,index,range){const newPoints=[...points];const pointToBeMoved=newPoints[index];const movePointWithConstraint=moveFunc=>{let destinationAttempt=moveFunc(pointToBeMoved);let newPoint=pointToBeMoved;while(newPoint[0]===pointToBeMoved[0]&&newPoint[1]===pointToBeMoved[1]&&isInBound({range,point:destinationAttempt})){newPoint=calculateSideSnap(destinationAttempt,range,newPoints,index,pointToBeMoved);destinationAttempt=moveFunc(destinationAttempt);}return newPoint};return {up:movePointWithConstraint(coord=>vec.add(coord,[0,1])),down:movePointWithConstraint(coord=>vec.sub(coord,[0,1])),left:movePointWithConstraint(coord=>vec.sub(coord,[1,0])),right:movePointWithConstraint(coord=>vec.add(coord,[1,0]))}}function getAngleSnapConstraint(points,index,range){const newPoints=[...points];const pointToBeMoved=newPoints[index];const movePointWithConstraint=moveFunc=>{let destinationAttempt=bound$1({snapStep:[0,0],range,point:moveFunc(pointToBeMoved)});let newPoint=pointToBeMoved;while(newPoint[0]===pointToBeMoved[0]&&newPoint[1]===pointToBeMoved[1]&&isInBound({range,point:destinationAttempt})){newPoint=calculateAngleSnap(destinationAttempt,range,newPoints,index,pointToBeMoved);destinationAttempt=moveFunc(destinationAttempt);}return newPoint};return {up:movePointWithConstraint(coord=>vec.add(coord,[0,.1])),down:movePointWithConstraint(coord=>vec.sub(coord,[0,.1])),left:movePointWithConstraint(coord=>vec.sub(coord,[.1,0])),right:movePointWithConstraint(coord=>vec.add(coord,[.1,0]))}}
1990
1991
 
1991
1992
  function renderQuadraticGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(QuadraticGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:describeQuadraticGraph(state,i18n).srQuadraticInteractiveElements}}function QuadraticGraph(props){const{dispatch,graphState}=props;const{coords,pointLabels,snapStep}=graphState;const{interactiveColor}=useGraphConfig();const{strings,locale}=usePerseusI18n();const id=React.useId();const quadraticDirectionId=id+"-direction";const quadraticVertexId=id+"-vertex";const quadraticInterceptsId=id+"-intercepts";const coeffRef=React.useRef([0,0,0]);const coeffs=getQuadraticCoefficients$2(coords);if(coeffs!==undefined){coeffRef.current=coeffs;}const[a,b,c]=coeffRef.current;const y=x=>(a*x+b)*x+c;const{srQuadraticGraph,srQuadraticDirection,srQuadraticVertex,srQuadraticXIntercepts,srQuadraticYIntercept}=describeQuadraticGraph(graphState,{strings,locale});return jsxRuntimeExports.jsxs("g",{"aria-label":srQuadraticGraph,"aria-describedby":`${quadraticDirectionId} ${quadraticVertexId} ${quadraticInterceptsId}`,children:[jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(Plot$2.OfX,{y:y,color:interactiveColor,svgPathProps:{"aria-hidden":true}})}),coords.map((coord,i)=>{const srQuadraticPoint=getQuadraticPointString(resolvePointLabel(pointLabels,i),coord,strings,locale);const srVertex=srQuadraticVertex?` ${srQuadraticVertex}`:"";return jsxRuntimeExports.jsx(MovablePoint$3,{ariaLabel:`${srQuadraticPoint}${srVertex}`,point:coord,sequenceNumber:i+1,constrain:getQuadraticKeyboardConstraint(coords,snapStep,i),onMove:destination=>dispatch(actions.quadratic.movePoint(i,destination))},"point-"+i)}),srQuadraticDirection&&jsxRuntimeExports.jsx(SRDescInSVG,{id:quadraticDirectionId,children:srQuadraticDirection}),srQuadraticVertex&&jsxRuntimeExports.jsx(SRDescInSVG,{id:quadraticVertexId,children:srQuadraticVertex}),jsxRuntimeExports.jsx(SRDescInSVG,{id:quadraticInterceptsId,children:srQuadraticXIntercepts?`${srQuadraticXIntercepts} ${srQuadraticYIntercept}`:`${srQuadraticYIntercept}`})]})}const getQuadraticKeyboardConstraint=(coords,snapStep,pointMoved)=>{const newCoords=[coords[0],coords[1],coords[2]];const coordToBeMoved=newCoords[pointMoved];const movePointWithConstraint=moveFunc=>{let movedCoord=moveFunc(coordToBeMoved);newCoords[pointMoved]=movedCoord;if(areCoordsValid(newCoords)){return movedCoord}movedCoord=moveFunc(movedCoord);newCoords[pointMoved]=movedCoord;if(areCoordsValid(newCoords)){return movedCoord}return moveFunc(movedCoord)};return {up:vec.add(coordToBeMoved,[0,snapStep[1]]),down:vec.sub(coordToBeMoved,[0,snapStep[1]]),left:movePointWithConstraint(coord=>vec.sub(coord,[snapStep[0],0])),right:movePointWithConstraint(coord=>vec.add(coord,[snapStep[0],0]))}};const areCoordsValid=coords=>{const p1=coords[0];const p2=coords[1];const p3=coords[2];if(p1[0]===p2[0]||p2[0]===p3[0]||p1[0]===p3[0]){return false}return true};
1992
1993
 
@@ -2122,7 +2123,7 @@ var extraWidgets = [Blank,CSProgram$1,Categorizer$1,Definition$1,DeprecatedStand
2122
2123
 
2123
2124
  const init=function(){registerWidgets(basicWidgets);registerWidgets(extraWidgets);replaceDeprecatedWidgets();};
2124
2125
 
2125
- const libName="@khanacademy/perseus";const libVersion="87.1.0";addLibraryVersionToPerseusDebug(libName,libVersion);
2126
+ const libName="@khanacademy/perseus";const libVersion="87.1.2";addLibraryVersionToPerseusDebug(libName,libVersion);
2126
2127
 
2127
2128
  const apiVersion={major:12,minor:0};
2128
2129