@khanacademy/perseus 77.10.1 → 77.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/es/index.css +1 -1
  2. package/dist/es/index.css.map +1 -1
  3. package/dist/es/index.js +21 -17
  4. package/dist/es/index.js.map +1 -1
  5. package/dist/es/strings.js +1 -1
  6. package/dist/es/strings.js.map +1 -1
  7. package/dist/index.css +1 -1
  8. package/dist/index.css.map +1 -1
  9. package/dist/index.js +21 -17
  10. package/dist/index.js.map +1 -1
  11. package/dist/server-item-renderer.d.ts +1 -1
  12. package/dist/strings.js +1 -1
  13. package/dist/strings.js.map +1 -1
  14. package/dist/testing/feature-flags-util.d.ts +0 -2
  15. package/dist/testing/item-flipbook/item-flipbook-model.d.ts +22 -0
  16. package/dist/testing/item-flipbook/item-flipbook-view-model.d.ts +57 -0
  17. package/dist/testing/item-flipbook/item-flipbook-view.d.ts +7 -0
  18. package/dist/testing/item-flipbook/item-flipbook.d.ts +2 -0
  19. package/dist/testing/item-flipbook/safe-parse-perseus-item.d.ts +2 -0
  20. package/dist/testing/item-renderer-hooks.d.ts +1 -1
  21. package/dist/widgets/dropdown/dropdown.d.ts +1 -1
  22. package/dist/widgets/expression/expression.d.ts +2 -2
  23. package/dist/widgets/interactive-graphs/graphs/components/movable-asymptote.d.ts +8 -0
  24. package/dist/widgets/interactive-graphs/graphs/components/movable-line/index.d.ts +2 -0
  25. package/dist/widgets/interactive-graphs/graphs/components/movable-line/line.d.ts +16 -0
  26. package/dist/widgets/interactive-graphs/graphs/components/movable-line/movable-line.d.ts +20 -0
  27. package/dist/widgets/interactive-graphs/graphs/components/{movable-line.d.ts → movable-line/util.d.ts} +0 -20
  28. package/dist/widgets/interactive-graphs/graphs/screenreader-text.d.ts +6 -0
  29. package/dist/widgets/interactive-graphs/graphs/utils.d.ts +8 -2
  30. package/dist/widgets/interactive-graphs/interactive-graph.d.ts +1 -1
  31. package/dist/widgets/interactive-graphs/point-labels.d.ts +2 -0
  32. package/dist/widgets/interactive-graphs/types.d.ts +46 -1
  33. package/dist/widgets/label-image/label-image.d.ts +1 -1
  34. package/dist/widgets/mock-widgets/mock-widget.d.ts +1 -1
  35. package/dist/widgets/numeric-input/numeric-input.class.d.ts +1 -1
  36. package/dist/widgets/numeric-input/numeric-input.d.ts +2 -2
  37. package/dist/widgets/table/table.d.ts +1 -1
  38. package/package.json +7 -7
package/dist/es/index.js CHANGED
@@ -1838,15 +1838,15 @@ var components = /*#__PURE__*/Object.freeze({
1838
1838
 
1839
1839
  const GifControlsButton=({isPlaying,onToggle})=>{const strings=usePerseusI18n().strings;return jsxRuntimeExports.jsx(Button,{"aria-label":isPlaying?strings.gifPauseButtonLabel:strings.gifPlayButtonLabel,kind:"secondary",startIcon:isPlaying?pauseIcon:playIcon,onClick:onToggle,style:{width:"fit-content"},children:isPlaying?strings.gifPauseButtonLabel:strings.gifPlayButtonLabel})};
1840
1840
 
1841
- const MODAL_HEIGHT=568;function ExploreImageModalContent({backgroundImage,scale:contentScale,caption,alt,longDescription,linterContext,apiOptions,box,labels,range,captionId,longDescId}){const[isGifPlaying,setIsGifPlaying]=React.useState(false);const context=React.useContext(PerseusI18nContext);if(!backgroundImage.url){return null}const gifControlsFF=isFeatureOn({apiOptions},"image-widget-upgrade-gif-controls");const imageIsGif=isGif(backgroundImage.url);const imageIsSvg=isSvg(backgroundImage.url);let scale=1;if(backgroundImage.width&&backgroundImage.height){scale=imageIsSvg?Math.max(contentScale,2):Math.max(contentScale,1);}let height=backgroundImage.height;let width=backgroundImage.width;if(backgroundImage.height&&backgroundImage.width){width=backgroundImage.width;height=backgroundImage.height;const maxScale=MODAL_HEIGHT/backgroundImage.height;scale=Math.min(scale,maxScale);}return jsxRuntimeExports.jsxs("div",{className:styles$g.modalPanelContainer,children:[jsxRuntimeExports.jsx("div",{className:styles$g.modalImageContainer,children:jsxRuntimeExports.jsx(context$1.Consumer,{children:({setAssetStatus})=>jsxRuntimeExports.jsx(SvgImage,{src:backgroundImage.url,allowZoom:false,alt:caption===alt?"":alt,width:width,height:height,scale:scale,preloader:apiOptions.imagePreloader,extraGraphie:{box:box,range:range,labels:labels??[]},zoomToFullSizeOnMobile:apiOptions.isMobile,constrainHeight:apiOptions.isMobile,allowFullBleed:apiOptions.isMobile,setAssetStatus:setAssetStatus,isGifPlaying:gifControlsFF&&imageIsGif?isGifPlaying:undefined,onGifLoop:gifControlsFF&&imageIsGif?()=>setIsGifPlaying(false):undefined})})}),jsxRuntimeExports.jsxs("div",{className:`perseus-image-modal-description ${styles$g.modalDescriptionContainer}`,children:[gifControlsFF&&imageIsGif&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(GifControlsButton,{isPlaying:isGifPlaying,onToggle:()=>setIsGifPlaying(!isGifPlaying)}),jsxRuntimeExports.jsx("div",{className:styles$g.spacerVertical})]}),caption&&jsxRuntimeExports.jsx("div",{id:captionId,className:styles$g.modalCaptionContainer,children:jsxRuntimeExports.jsx(Renderer,{content:caption,apiOptions:apiOptions,linterContext:linterContext,strings:context.strings})}),jsxRuntimeExports.jsx(Heading,{size:"large",tag:"h2",style:wbStyles$1.descriptionHeading,children:context.strings.imageDescriptionLabel}),jsxRuntimeExports.jsx("div",{id:longDescId,children:jsxRuntimeExports.jsx(Renderer,{content:longDescription,apiOptions:apiOptions,linterContext:linterContext,strings:context.strings})})]})]})}const wbStyles$1={descriptionHeading:{marginBlockEnd:sizing.size_160}};
1841
+ const MODAL_HEIGHT=568;function ExploreImageModalContent({backgroundImage,scale:contentScale,caption,alt,longDescription,linterContext,apiOptions,box,labels,range,captionId,longDescId}){const[isGifPlaying,setIsGifPlaying]=React.useState(false);const context=React.useContext(PerseusI18nContext);if(!backgroundImage.url){return null}const imageIsGif=isGif(backgroundImage.url);const imageIsSvg=isSvg(backgroundImage.url);let scale=1;if(backgroundImage.width&&backgroundImage.height){scale=imageIsSvg?Math.max(contentScale,2):Math.max(contentScale,1);}let height=backgroundImage.height;let width=backgroundImage.width;if(backgroundImage.height&&backgroundImage.width){width=backgroundImage.width;height=backgroundImage.height;const maxScale=MODAL_HEIGHT/backgroundImage.height;scale=Math.min(scale,maxScale);}return jsxRuntimeExports.jsxs("div",{className:styles$g.modalPanelContainer,children:[jsxRuntimeExports.jsx("div",{className:styles$g.modalImageContainer,children:jsxRuntimeExports.jsx(context$1.Consumer,{children:({setAssetStatus})=>jsxRuntimeExports.jsx(SvgImage,{src:backgroundImage.url,allowZoom:false,alt:caption===alt?"":alt,width:width,height:height,scale:scale,preloader:apiOptions.imagePreloader,extraGraphie:{box:box,range:range,labels:labels??[]},zoomToFullSizeOnMobile:apiOptions.isMobile,constrainHeight:apiOptions.isMobile,allowFullBleed:apiOptions.isMobile,setAssetStatus:setAssetStatus,isGifPlaying:imageIsGif?isGifPlaying:undefined,onGifLoop:imageIsGif?()=>setIsGifPlaying(false):undefined})})}),jsxRuntimeExports.jsxs("div",{className:`perseus-image-modal-description ${styles$g.modalDescriptionContainer}`,children:[imageIsGif&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(GifControlsButton,{isPlaying:isGifPlaying,onToggle:()=>setIsGifPlaying(!isGifPlaying)}),jsxRuntimeExports.jsx("div",{className:styles$g.spacerVertical})]}),caption&&jsxRuntimeExports.jsx("div",{id:captionId,className:styles$g.modalCaptionContainer,children:jsxRuntimeExports.jsx(Renderer,{content:caption,apiOptions:apiOptions,linterContext:linterContext,strings:context.strings})}),jsxRuntimeExports.jsx(Heading,{size:"large",tag:"h2",style:wbStyles$1.descriptionHeading,children:context.strings.imageDescriptionLabel}),jsxRuntimeExports.jsx("div",{id:longDescId,children:jsxRuntimeExports.jsx(Renderer,{content:longDescription,apiOptions:apiOptions,linterContext:linterContext,strings:context.strings})})]})]})}const wbStyles$1={descriptionHeading:{marginBlockEnd:sizing.size_160}};
1842
1842
 
1843
1843
  const ExploreImageModal=props=>{const context=React__default.useContext(PerseusI18nContext);const uniqueId=React__default.useId();const captionId=`${uniqueId}-caption`;const longDescId=`${uniqueId}-long-desc`;const titleText=props.title||context.strings.imageAlternativeTitle;const title=jsxRuntimeExports.jsx("h1",{className:`perseus-image-modal-title ${styles$g.modalTitleContainer}`,children:jsxRuntimeExports.jsx(Renderer,{content:titleText,apiOptions:props.apiOptions,linterContext:props.linterContext,strings:context.strings})});return jsxRuntimeExports.jsx("div",{className:`framework-perseus ${styles$g.modalContainer}`,children:jsxRuntimeExports.jsx(FlexibleDialog,{title:title,content:jsxRuntimeExports.jsx(ExploreImageModalContent,{...props,captionId:captionId,longDescId:longDescId}),"aria-describedby":props.caption?`${captionId} ${longDescId}`:longDescId,styles:{root:wbStyles.root}})})};const wbStyles={root:{borderRadius:sizing.size_120,maxWidth:"100%"}};
1844
1844
 
1845
1845
  const GifControlsIcon=({isPlaying,onToggle})=>{const strings=usePerseusI18n().strings;return jsxRuntimeExports.jsx(IconButton,{icon:isPlaying?pauseIcon:playIcon,kind:"secondary","aria-label":isPlaying?strings.gifPauseButtonLabel:strings.gifPlayButtonLabel,onClick:onToggle,style:{flexShrink:0}})};
1846
1846
 
1847
- const ImageInfoArea=props=>{const{backgroundImage,caption,longDescription,apiOptions,linterContext,isGifPlaying,setIsGifPlaying}=props;const context=React.useContext(PerseusI18nContext);const gifControlsFF=isFeatureOn({apiOptions},"image-widget-upgrade-gif-controls");if(!backgroundImage.url){return null}const imageIsGif=isGif(backgroundImage.url);return jsxRuntimeExports.jsxs("div",{className:styles$g.infoAreaContainer,children:[gifControlsFF&&imageIsGif&&jsxRuntimeExports.jsx(GifControlsIcon,{isPlaying:isGifPlaying,onToggle:()=>setIsGifPlaying(!isGifPlaying)}),gifControlsFF&&imageIsGif&&longDescription&&jsxRuntimeExports.jsx("div",{className:styles$g.spacerHorizontal}),longDescription&&jsxRuntimeExports.jsx(ModalLauncher,{modal:jsxRuntimeExports.jsx(ExploreImageModal,{...props}),children:({openModal})=>jsxRuntimeExports.jsx(ExploreImageButton,{hasCaption:!!caption,onClick:openModal})}),caption&&jsxRuntimeExports.jsx("figcaption",{className:"perseus-image-caption",children:jsxRuntimeExports.jsx(Renderer,{content:caption,apiOptions:apiOptions,linterContext:linterContext,strings:context.strings})})]})};
1847
+ const ImageInfoArea=props=>{const{backgroundImage,caption,longDescription,apiOptions,linterContext,isGifPlaying,setIsGifPlaying}=props;const context=React.useContext(PerseusI18nContext);if(!backgroundImage.url){return null}const imageIsGif=isGif(backgroundImage.url);return jsxRuntimeExports.jsxs("div",{className:styles$g.infoAreaContainer,children:[imageIsGif&&jsxRuntimeExports.jsx(GifControlsIcon,{isPlaying:isGifPlaying,onToggle:()=>setIsGifPlaying(!isGifPlaying)}),imageIsGif&&longDescription&&jsxRuntimeExports.jsx("div",{className:styles$g.spacerHorizontal}),longDescription&&jsxRuntimeExports.jsx(ModalLauncher,{modal:jsxRuntimeExports.jsx(ExploreImageModal,{...props}),children:({openModal})=>jsxRuntimeExports.jsx(ExploreImageButton,{hasCaption:!!caption,onClick:openModal})}),caption&&jsxRuntimeExports.jsx("figcaption",{className:"perseus-image-caption",children:jsxRuntimeExports.jsx(Renderer,{content:caption,apiOptions:apiOptions,linterContext:linterContext,strings:context.strings})})]})};
1848
1848
 
1849
- const ImageComponent=props=>{const{apiOptions,alt,backgroundImage,box,caption,longDescription,decorative,linterContext,labels,range,title,trackInteraction,widgetId}=props;const context=React.useContext(PerseusI18nContext);const{analytics}=useDependencies();const gifControlsFF=isFeatureOn({apiOptions},"image-widget-upgrade-gif-controls");const[isGifPlaying,setIsGifPlaying]=React.useState(false);useOnMountEffect(()=>{analytics.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"image",widgetId:widgetId}});});React.useEffect(()=>{setIsGifPlaying(false);},[backgroundImage.url]);if(!backgroundImage.url){return null}const imageIsGif=isGif(backgroundImage.url);let scale=props.scale;if(scale<=0||scale===Infinity||scale===-Infinity){scale=1;}const svgImage=jsxRuntimeExports.jsx(context$1.Consumer,{children:({setAssetStatus})=>jsxRuntimeExports.jsx(SvgImage,{src:backgroundImage.url,apiOptions:apiOptions,width:backgroundImage.width,height:backgroundImage.height,scale:scale,preloader:apiOptions.imagePreloader,extraGraphie:{box:box,range:range,labels:labels},trackInteraction:trackInteraction,zoomToFullSizeOnMobile:apiOptions.isMobile,constrainHeight:apiOptions.isMobile,allowFullBleed:apiOptions.isMobile,allowZoom:!decorative&&!imageIsGif,alt:decorative||caption===alt?"":alt,setAssetStatus:setAssetStatus,isGifPlaying:gifControlsFF&&imageIsGif?isGifPlaying:undefined,onGifLoop:gifControlsFF&&imageIsGif?()=>setIsGifPlaying(false):undefined})});const maxWidth=backgroundImage.width?backgroundImage.width*scale:undefined;const width=maxWidth?undefined:"fit-content";if(decorative){return jsxRuntimeExports.jsx("figure",{className:"perseus-image-widget",style:{maxWidth:maxWidth,width:width},children:svgImage})}return jsxRuntimeExports.jsxs("figure",{className:"perseus-image-widget",style:{maxWidth:maxWidth,width:width},children:[title&&jsxRuntimeExports.jsx("div",{className:`perseus-image-title ${styles$g.titleContainer}`,children:jsxRuntimeExports.jsx(Renderer,{content:title,apiOptions:apiOptions,linterContext:linterContext,strings:context.strings})}),svgImage,(gifControlsFF&&imageIsGif||caption||longDescription)&&jsxRuntimeExports.jsx(ImageInfoArea,{isGifPlaying:isGifPlaying,setIsGifPlaying:setIsGifPlaying,...props})]})};
1849
+ const ImageComponent=props=>{const{apiOptions,alt,backgroundImage,box,caption,longDescription,decorative,linterContext,labels,range,title,trackInteraction,widgetId}=props;const context=React.useContext(PerseusI18nContext);const{analytics}=useDependencies();const[isGifPlaying,setIsGifPlaying]=React.useState(false);useOnMountEffect(()=>{analytics.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"image",widgetId:widgetId}});});React.useEffect(()=>{setIsGifPlaying(false);},[backgroundImage.url]);if(!backgroundImage.url){return null}const imageIsGif=isGif(backgroundImage.url);let scale=props.scale;if(scale<=0||scale===Infinity||scale===-Infinity){scale=1;}const svgImage=jsxRuntimeExports.jsx(context$1.Consumer,{children:({setAssetStatus})=>jsxRuntimeExports.jsx(SvgImage,{src:backgroundImage.url,apiOptions:apiOptions,width:backgroundImage.width,height:backgroundImage.height,scale:scale,preloader:apiOptions.imagePreloader,extraGraphie:{box:box,range:range,labels:labels},trackInteraction:trackInteraction,zoomToFullSizeOnMobile:apiOptions.isMobile,constrainHeight:apiOptions.isMobile,allowFullBleed:apiOptions.isMobile,allowZoom:!decorative&&!imageIsGif,alt:decorative||caption===alt?"":alt,setAssetStatus:setAssetStatus,isGifPlaying:imageIsGif?isGifPlaying:undefined,onGifLoop:imageIsGif?()=>setIsGifPlaying(false):undefined})});const maxWidth=backgroundImage.width?backgroundImage.width*scale:undefined;const width=maxWidth?undefined:"fit-content";if(decorative){return jsxRuntimeExports.jsx("figure",{className:"perseus-image-widget",style:{maxWidth:maxWidth,width:width},children:svgImage})}return jsxRuntimeExports.jsxs("figure",{className:"perseus-image-widget",style:{maxWidth:maxWidth,width:width},children:[title&&jsxRuntimeExports.jsx("div",{className:`perseus-image-title ${styles$g.titleContainer}`,children:jsxRuntimeExports.jsx(Renderer,{content:title,apiOptions:apiOptions,linterContext:linterContext,strings:context.strings})}),svgImage,(imageIsGif||caption||longDescription)&&jsxRuntimeExports.jsx(ImageInfoArea,{isGifPlaying:isGifPlaying,setIsGifPlaying:setIsGifPlaying,...props})]})};
1850
1850
 
1851
1851
  const defaultBoxSize=400;const defaultRange=[0,10];const defaultBackgroundImage$1={url:null,width:0,height:0};class ImageWidget extends React.Component{getPromptJSON(){return getPromptJSON$d(this.props)}render(){return jsxRuntimeExports.jsx(ImageComponent,{...this.props})}constructor(...args){super(...args),this.isWidget=true;}}ImageWidget.contextType=PerseusI18nContext;ImageWidget.defaultProps={alignment:"block",title:"",range:[defaultRange,defaultRange],box:[defaultBoxSize,defaultBoxSize],backgroundImage:defaultBackgroundImage$1,scale:1,labels:[],alt:"",longDescription:"",decorative:false,caption:"",linterContext:linterContextDefault};var Image$1 = {name:"image",displayName:"Image",widget:ImageWidget,isLintable:true};
1852
1852
 
@@ -1862,7 +1862,7 @@ const actions={global:{deleteIntent,changeInteractionMode,changeKeyboardInvitati
1862
1862
 
1863
1863
  const defaultGraphConfig={range:[[0,1],[0,1]],tickStep:[1,1],gridStep:[1,1],snapStep:[1,1],markings:"none",showTooltips:false,graphDimensionsInPixels:[1,1],width:0,height:0,labels:[],labelLocation:"onAxis",disableKeyboardInteraction:false,interactiveColor:"var(--mafs-blue)",showAxisArrows:{xMin:true,xMax:true,yMin:true,yMax:true},showAxisTicks:{x:true,y:true}};const GraphConfigContext=createContext(defaultGraphConfig);function useGraphConfig(){return React__default.useContext(GraphConfigContext)}
1864
1864
 
1865
- function getAnnouncementText(state,strings,locale){switch(state.type){case "move-point":return strings.srPointAtCoordinates({num:state.pointLabel,x:srFormatNumber(state.x,locale),y:srFormatNumber(state.y,locale)});case "move-radius-point":return `${srCircleRadiusPointLabel(state.x,state.y,state.centerX,strings,locale)} ${strings.srCircleRadius({radius:state.radius})}`;case "move-center":return srCircleCenterLabel(state.x,state.y,strings,locale);case "move-vector-point":return srVectorPointLabel(state,strings,locale);case "move-vector-line":return strings.srVectorGrabHandle({tailX:srFormatNumber(state.coords[0][0],locale),tailY:srFormatNumber(state.coords[0][1],locale),tipX:srFormatNumber(state.coords[1][0],locale),tipY:srFormatNumber(state.coords[1][1],locale)});case "move-segment-point":return srSegmentPointLabel(state,strings,locale);case "move-segment-line":return strings.srSegmentGrabHandle({point1X:srFormatNumber(state.coords[0][0],locale),point1Y:srFormatNumber(state.coords[0][1],locale),point2X:srFormatNumber(state.coords[1][0],locale),point2Y:srFormatNumber(state.coords[1][1],locale)});case "move-linear-system-point":return srLinearSystemPointLabel(state,strings,locale);case "move-linear-system-line":return strings.srLinearSystemGrabHandle({lineNumber:state.lineIndex+1,point1X:srFormatNumber(state.coords[0][0],locale),point1Y:srFormatNumber(state.coords[0][1],locale),point2X:srFormatNumber(state.coords[1][0],locale),point2Y:srFormatNumber(state.coords[1][1],locale)});case "move-ray-point":return srRayPointLabel(state,strings,locale);case "move-ray-line":return strings.srRayGrabHandle(formatLineEndpoints(state.coords,locale));case "move-linear-line":return strings.srLinearGrabHandle(formatLineEndpoints(state.coords,locale));case "move-sinusoid-point":return srSinusoidPointLabel(state,strings,locale);case "move-angle-point":return srAnglePointLabel(state,strings,locale);case "move-polygon":return srPolygonLabel(state.coords,state.pointLabels,strings,locale);default:throw new UnreachableCaseError(state)}}function formatLineEndpoints(coords,locale){return {point1X:srFormatNumber(coords[0][0],locale),point1Y:srFormatNumber(coords[0][1],locale),point2X:srFormatNumber(coords[1][0],locale),point2Y:srFormatNumber(coords[1][1],locale)}}function srSinusoidPointLabel(state,strings,locale){const formatted={x:srFormatNumber(state.x,locale),y:srFormatNumber(state.y,locale)};if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,...formatted})}if(state.pointIndex===0){return strings.srSinusoidRootPoint(formatted)}if(state.y===state.otherY){return strings.srSinusoidFlatPoint(formatted)}return state.y>state.otherY?strings.srSinusoidMaxPoint(formatted):strings.srSinusoidMinPoint(formatted)}function srAnglePointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,x,y})}switch(state.pointIndex){case 0:return strings.srAngleEndingSide({x,y});case 1:return strings.srAngleVertexWithAngleMeasure({x,y,angleMeasure:srFormatNumber(state.angleMeasure,locale)});default:return strings.srAngleStartingSide({x,y})}}function srSegmentPointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,x,y})}return state.totalSegments===1?strings.srSingleSegmentGraphEndpointAriaLabel({endpointNumber:state.pointIndex+1,x,y}):strings.srMultipleSegmentGraphEndpointAriaLabel({endpointNumber:state.pointIndex+1,indexOfSegment:state.segmentIndex+1,x,y})}function srLinearSystemPointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,x,y})}return strings.srLinearSystemPoint({lineNumber:state.lineIndex+1,pointSequence:state.pointIndex+1,x,y})}function srRayPointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,x,y})}return state.pointIndex===0?strings.srRayEndpoint({x,y}):strings.srRayTerminalPoint({x,y})}function srVectorPointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);return state.pointIndex===0?strings.srPointAtCoordinates({num:1,x,y}):strings.srVectorTipPoint({x,y})}function srPolygonLabel(coords,pointLabels,strings,locale){const pointsString=coords.map(([x,y],i)=>strings.srPointAtCoordinates({num:resolvePointLabel(pointLabels,i),x:srFormatNumber(x,locale),y:srFormatNumber(y,locale)})).join(" ");const elementsLabel=coords.length===1?strings.srPolygonElementsOne:strings.srPolygonElementsNum({num:coords.length});return `${elementsLabel} ${pointsString}`}function srCircleRadiusPointLabel(x,y,centerX,strings,locale){return x>=centerX?strings.srCircleRadiusPointRight({radiusPointX:srFormatNumber(x,locale),radiusPointY:srFormatNumber(y,locale)}):strings.srCircleRadiusPointLeft({radiusPointX:srFormatNumber(x,locale),radiusPointY:srFormatNumber(y,locale)})}function srCircleCenterLabel(x,y,strings,locale){return strings.srCircleShape({centerX:srFormatNumber(x,locale),centerY:srFormatNumber(y,locale)})}function srFormatNumber(a,locale,maximumFractionDigits){const piBasedNumber=getPiMultiple(a);if(piBasedNumber){return piBasedNumber}return (0+a).toLocaleString(locale,{maximumFractionDigits:maximumFractionDigits??3,useGrouping:false})}function getPiMultiple(a){if(Number.isInteger(a)||a===0||a>1e12){return null}const piCoefficient=a/Math.PI;const truncatedCoefficient=parseFloat(piCoefficient.toFixed(12));if(Number.isInteger(truncatedCoefficient)){return truncatedCoefficient+"π"}const acceptableDivisors=[2,3,4,6];for(const divisor of acceptableDivisors){const coefficientNumerator=parseFloat((piCoefficient*divisor).toFixed(12));if(Number.isInteger(coefficientNumerator)){return coefficientNumerator+"π/"+divisor}}return null}
1865
+ function getAnnouncementText(state,strings,locale){switch(state.type){case "move-point":return strings.srPointAtCoordinates({num:state.pointLabel,x:srFormatNumber(state.x,locale),y:srFormatNumber(state.y,locale)});case "move-radius-point":return `${srCircleRadiusPointLabel(state.x,state.y,state.centerX,strings,locale)} ${strings.srCircleRadius({radius:state.radius})}`;case "move-center":return srCircleCenterLabel(state.x,state.y,strings,locale);case "move-quadratic-point":return srQuadraticPointLabel(state,strings,locale);case "move-vector-point":return srVectorPointLabel(state,strings,locale);case "move-vector-line":return strings.srVectorGrabHandle({tailX:srFormatNumber(state.coords[0][0],locale),tailY:srFormatNumber(state.coords[0][1],locale),tipX:srFormatNumber(state.coords[1][0],locale),tipY:srFormatNumber(state.coords[1][1],locale)});case "move-segment-point":return srSegmentPointLabel(state,strings,locale);case "move-segment-line":return strings.srSegmentGrabHandle({point1X:srFormatNumber(state.coords[0][0],locale),point1Y:srFormatNumber(state.coords[0][1],locale),point2X:srFormatNumber(state.coords[1][0],locale),point2Y:srFormatNumber(state.coords[1][1],locale)});case "move-linear-system-point":return srLinearSystemPointLabel(state,strings,locale);case "move-linear-system-line":return strings.srLinearSystemGrabHandle({lineNumber:state.lineIndex+1,point1X:srFormatNumber(state.coords[0][0],locale),point1Y:srFormatNumber(state.coords[0][1],locale),point2X:srFormatNumber(state.coords[1][0],locale),point2Y:srFormatNumber(state.coords[1][1],locale)});case "move-ray-point":return srRayPointLabel(state,strings,locale);case "move-ray-line":return strings.srRayGrabHandle(formatLineEndpoints(state.coords,locale));case "move-linear-line":return strings.srLinearGrabHandle(formatLineEndpoints(state.coords,locale));case "move-sinusoid-point":return srSinusoidPointLabel(state,strings,locale);case "move-exponential-point":return srExponentialPointLabel(state,strings,locale);case "move-exponential-asymptote":return strings.srExponentialAsymptote({asymptoteY:srFormatNumber(state.asymptoteY,locale)});case "move-logarithm-point":return srLogarithmPointLabel(state,strings,locale);case "move-logarithm-asymptote":return strings.srLogarithmAsymptote({asymptoteX:srFormatNumber(state.asymptoteX,locale)});case "move-tangent-point":return srTangentPointLabel(state,strings,locale);case "move-absolute-value-point":return srAbsoluteValuePointLabel(state,strings,locale);case "move-angle-point":return srAnglePointLabel(state,strings,locale);case "move-polygon":return srPolygonLabel(state.coords,state.pointLabels,strings,locale);default:throw new UnreachableCaseError(state)}}function srQuadraticPointLabel(state,strings,locale){if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,x:srFormatNumber(state.x,locale),y:srFormatNumber(state.y,locale)})}const pointString=getQuadraticPointString(state.pointIndex+1,[state.x,state.y],strings,locale);if(state.vertex===undefined){return pointString}return `${pointString} ${getQuadraticVertexString(state.vertex,strings)}`}function formatLineEndpoints(coords,locale){return {point1X:srFormatNumber(coords[0][0],locale),point1Y:srFormatNumber(coords[0][1],locale),point2X:srFormatNumber(coords[1][0],locale),point2Y:srFormatNumber(coords[1][1],locale)}}function srSinusoidPointLabel(state,strings,locale){const formatted={x:srFormatNumber(state.x,locale),y:srFormatNumber(state.y,locale)};if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,...formatted})}if(state.pointIndex===0){return strings.srSinusoidRootPoint(formatted)}if(state.y===state.otherY){return strings.srSinusoidFlatPoint(formatted)}return state.y>state.otherY?strings.srSinusoidMaxPoint(formatted):strings.srSinusoidMinPoint(formatted)}function srExponentialPointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,x,y})}return state.pointIndex===0?strings.srExponentialPoint1({x,y}):strings.srExponentialPoint2({x,y})}function srLogarithmPointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,x,y})}return state.pointIndex===0?strings.srLogarithmPoint1({x,y}):strings.srLogarithmPoint2({x,y})}function srTangentPointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,x,y})}return state.pointIndex===0?strings.srTangentInflectionPoint({x,y}):strings.srTangentSecondPoint({x,y})}function srAbsoluteValuePointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,x,y})}return state.pointIndex===0?strings.srAbsoluteValueVertexPoint({x,y}):strings.srAbsoluteValueSecondPoint({x,y})}function srAnglePointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,x,y})}switch(state.pointIndex){case 0:return strings.srAngleEndingSide({x,y});case 1:return strings.srAngleVertexWithAngleMeasure({x,y,angleMeasure:srFormatNumber(state.angleMeasure,locale)});default:return strings.srAngleStartingSide({x,y})}}function srSegmentPointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,x,y})}return state.totalSegments===1?strings.srSingleSegmentGraphEndpointAriaLabel({endpointNumber:state.pointIndex+1,x,y}):strings.srMultipleSegmentGraphEndpointAriaLabel({endpointNumber:state.pointIndex+1,indexOfSegment:state.segmentIndex+1,x,y})}function srLinearSystemPointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,x,y})}return strings.srLinearSystemPoint({lineNumber:state.lineIndex+1,pointSequence:state.pointIndex+1,x,y})}function srRayPointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);if(typeof state.pointLabel==="string"){return strings.srPointAtCoordinates({num:state.pointLabel,x,y})}return state.pointIndex===0?strings.srRayEndpoint({x,y}):strings.srRayTerminalPoint({x,y})}function srVectorPointLabel(state,strings,locale){const x=srFormatNumber(state.x,locale);const y=srFormatNumber(state.y,locale);return state.pointIndex===0?strings.srPointAtCoordinates({num:1,x,y}):strings.srVectorTipPoint({x,y})}function srPolygonLabel(coords,pointLabels,strings,locale){const pointsString=coords.map(([x,y],i)=>strings.srPointAtCoordinates({num:resolvePointLabel(pointLabels,i),x:srFormatNumber(x,locale),y:srFormatNumber(y,locale)})).join(" ");const elementsLabel=coords.length===1?strings.srPolygonElementsOne:strings.srPolygonElementsNum({num:coords.length});return `${elementsLabel} ${pointsString}`}function srCircleRadiusPointLabel(x,y,centerX,strings,locale){return x>=centerX?strings.srCircleRadiusPointRight({radiusPointX:srFormatNumber(x,locale),radiusPointY:srFormatNumber(y,locale)}):strings.srCircleRadiusPointLeft({radiusPointX:srFormatNumber(x,locale),radiusPointY:srFormatNumber(y,locale)})}function srCircleCenterLabel(x,y,strings,locale){return strings.srCircleShape({centerX:srFormatNumber(x,locale),centerY:srFormatNumber(y,locale)})}function getCoordQuadrant(coord){const[unroundedX,unroundedY]=coord;const x=Number(unroundedX.toFixed(3));const y=Number(unroundedY.toFixed(3));if(x===0&&y===0){return "origin"}if(y===0){return "x-axis"}if(x===0){return "y-axis"}if(x>0&&y>0){return 1}if(x<0&&y>0){return 2}if(x<0&&y<0){return 3}return 4}function getQuadraticVertexString(vertex,strings){const location=getCoordQuadrant(vertex);switch(location){case "origin":return strings.srQuadraticGraphVertexOrigin;case "x-axis":return strings.srQuadraticGraphVertexXAxis;case "y-axis":return strings.srQuadraticGraphVertexYAxis;default:return strings.srQuadraticGraphVertexQuadrant({quadrant:location})}}function getQuadraticPointString(pointNumber,coord,strings,locale){const location=getCoordQuadrant(coord);const[x,y]=coord;switch(location){case "origin":return strings.srQuadraticPointOrigin({pointNumber:pointNumber});case "x-axis":case "y-axis":return strings.srQuadraticPointAxis({pointNumber:pointNumber,x:srFormatNumber(x,locale),y:srFormatNumber(y,locale)});default:return strings.srQuadraticPointQuadrant({pointNumber:pointNumber,quadrant:location,x:srFormatNumber(x,locale),y:srFormatNumber(y,locale)})}}function srFormatNumber(a,locale,maximumFractionDigits){const piBasedNumber=getPiMultiple(a);if(piBasedNumber){return piBasedNumber}return (0+a).toLocaleString(locale,{maximumFractionDigits:maximumFractionDigits??3,useGrouping:false})}function getPiMultiple(a){if(Number.isInteger(a)||a===0||a>1e12){return null}const piCoefficient=a/Math.PI;const truncatedCoefficient=parseFloat(piCoefficient.toFixed(12));if(Number.isInteger(truncatedCoefficient)){return truncatedCoefficient+"π"}const acceptableDivisors=[2,3,4,6];for(const divisor of acceptableDivisors){const coefficientNumerator=parseFloat((piCoefficient*divisor).toFixed(12));if(Number.isInteger(coefficientNumerator)){return coefficientNumerator+"π/"+divisor}}return null}
1866
1866
 
1867
1867
  function resolvePointLabel(pointLabels,index){const customLabel=pointLabels?.[index];if(typeof customLabel!=="string"||!customLabel){return index+1}return customLabel}function buildPointAriaLabel(pointLabels,index,point,strings,locale){const label=resolvePointLabel(pointLabels,index);if(typeof label==="number"){return undefined}return strings.srPointAtCoordinates({num:label,x:srFormatNumber(point[0],locale),y:srFormatNumber(point[1],locale)})}function usePointAriaLabel(pointLabels){const{strings,locale}=usePerseusI18n();return (index,point)=>buildPointAriaLabel(pointLabels,index,point,strings,locale)}
1868
1868
 
@@ -1898,7 +1898,7 @@ const MovablePoint$1=React.forwardRef(function MovablePointWithRef(props,pointRe
1898
1898
 
1899
1899
  function SRDescInSVG(props){const{id,children}=props;return jsxRuntimeExports.jsx("foreignObject",{children:jsxRuntimeExports.jsx("span",{id:id,style:a11y.srOnly,"aria-hidden":true,children:children})})}
1900
1900
 
1901
- function renderAbsoluteValueGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(AbsoluteValueGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getAbsoluteValueDescription(state,i18n)}}function AbsoluteValueGraph(props){const{dispatch,graphState}=props;const{interactiveColor}=useGraphConfig();const i18n=usePerseusI18n();const id=React.useId();const descriptionId=id+"-description";const{coords,pointLabels,snapStep}=graphState;const buildLabel=usePointAriaLabel(pointLabels);const coeffRef=React.useRef({m:1,h:0,v:0});const coeffs=getAbsoluteValueCoefficients(coords);if(coeffs!==undefined){coeffRef.current=coeffs;}const{m,h,v}=coeffRef.current;const{srAbsoluteValueGraph,srAbsoluteValueVertexPoint,srAbsoluteValueSecondPoint,srAbsoluteValueDescription:srDescription}=describeAbsoluteValueGraph(graphState,i18n);return jsxRuntimeExports.jsxs("g",{"aria-label":srAbsoluteValueGraph,"aria-describedby":descriptionId,children:[jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(Plot$2.OfX,{y:x=>m*Math.abs(x-h)+v,color:interactiveColor,svgPathProps:{"aria-hidden":true}})}),coords.map((coord,i)=>jsxRuntimeExports.jsx(MovablePoint$1,{ariaLabel:buildLabel(i,coord)??(i===0?srAbsoluteValueVertexPoint:srAbsoluteValueSecondPoint),point:coord,sequenceNumber:i+1,constrain:getAbsoluteValueKeyboardConstraint(coords,snapStep,i),onMove:destination=>dispatch(actions.absoluteValue.movePoint(i,destination))},"point-"+i)),jsxRuntimeExports.jsx(SRDescInSVG,{id:descriptionId,children:srDescription})]})}function getAbsoluteValueCoefficients(coords){const p1=coords[0];const p2=coords[1];const denom=p2[X]-p1[X];if(denom===0){return undefined}const num=p2[Y]-p1[Y];let m=Math.abs(num/denom);if(p2[Y]<p1[Y]){m=-m;}return {m,h:p1[X],v:p1[Y]}}const getAbsoluteValueKeyboardConstraint=(coords,snapStep,pointIndex)=>{const coordToBeMoved=coords[pointIndex];const otherPoint=coords[1-pointIndex];const moveWithConstraint=moveFunc=>{let movedCoord=moveFunc(coordToBeMoved);if(movedCoord[X]===otherPoint[X]){movedCoord=moveFunc(movedCoord);}return movedCoord};return {up:vec.add(coordToBeMoved,[0,snapStep[Y]]),down:vec.sub(coordToBeMoved,[0,snapStep[Y]]),left:moveWithConstraint(coord=>vec.sub(coord,[snapStep[X],0])),right:moveWithConstraint(coord=>vec.add(coord,[snapStep[X],0]))}};function getAbsoluteValueDescription(state,i18n){const{strings}=i18n;const{coords}=state;const{locale}=i18n;const[p1,p2]=coords;return strings.srInteractiveElements({elements:strings.srAbsoluteValueInteractiveElements({point1X:srFormatNumber(p1[X],locale),point1Y:srFormatNumber(p1[Y],locale),point2X:srFormatNumber(p2[X],locale),point2Y:srFormatNumber(p2[Y],locale)})})}function describeAbsoluteValueGraph(state,i18n){const{strings,locale}=i18n;const{coords}=state;const[vertex,armPoint]=coords;const coeffs=getAbsoluteValueCoefficients(coords);const m=coeffs?.m??1;const srAbsoluteValueGraph=strings.srAbsoluteValueGraph;const srAbsoluteValueVertexPoint=strings.srAbsoluteValueVertexPoint({x:srFormatNumber(vertex[X],locale),y:srFormatNumber(vertex[Y],locale)});const srAbsoluteValueSecondPoint=strings.srAbsoluteValueSecondPoint({x:srFormatNumber(armPoint[X],locale),y:srFormatNumber(armPoint[Y],locale)});const srAbsoluteValueDescription=strings.srAbsoluteValueDescription({x:srFormatNumber(vertex[X],locale),y:srFormatNumber(vertex[Y],locale),slope:srFormatNumber(m,locale)});return {srAbsoluteValueGraph,srAbsoluteValueVertexPoint,srAbsoluteValueSecondPoint,srAbsoluteValueDescription}}
1901
+ function renderAbsoluteValueGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(AbsoluteValueGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getAbsoluteValueDescription(state,i18n)}}function AbsoluteValueGraph(props){const{dispatch,graphState}=props;const{interactiveColor}=useGraphConfig();const i18n=usePerseusI18n();const id=React.useId();const descriptionId=id+"-description";const{coords,pointLabels,snapStep}=graphState;const buildLabel=usePointAriaLabel(pointLabels);const coeffRef=React.useRef({m:1,h:0,v:0});const coeffs=getAbsoluteValueCoefficients(coords);if(coeffs!==undefined){coeffRef.current=coeffs;}const{m,h,v}=coeffRef.current;const{srAbsoluteValueGraph,srAbsoluteValueVertexPoint,srAbsoluteValueSecondPoint,srAbsoluteValueDescription:srDescription}=describeAbsoluteValueGraph(graphState,i18n);return jsxRuntimeExports.jsxs("g",{"aria-label":srAbsoluteValueGraph,"aria-describedby":descriptionId,children:[jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(Plot$2.OfX,{y:x=>m*Math.abs(x-h)+v,color:interactiveColor,svgPathProps:{"aria-hidden":true}})}),coords.map((coord,i)=>jsxRuntimeExports.jsx(MovablePoint$1,{ariaLabel:buildLabel(i,coord)??(i===0?srAbsoluteValueVertexPoint:srAbsoluteValueSecondPoint),point:coord,sequenceNumber:i+1,constrain:getAbsoluteValueKeyboardConstraint(coords,snapStep,i),onMove:destination=>dispatch(actions.absoluteValue.movePoint(i,destination)),ariaLive:"off"},"point-"+i)),jsxRuntimeExports.jsx(SRDescInSVG,{id:descriptionId,children:srDescription})]})}function getAbsoluteValueCoefficients(coords){const p1=coords[0];const p2=coords[1];const denom=p2[X]-p1[X];if(denom===0){return undefined}const num=p2[Y]-p1[Y];let m=Math.abs(num/denom);if(p2[Y]<p1[Y]){m=-m;}return {m,h:p1[X],v:p1[Y]}}const getAbsoluteValueKeyboardConstraint=(coords,snapStep,pointIndex)=>{const coordToBeMoved=coords[pointIndex];const otherPoint=coords[1-pointIndex];const moveWithConstraint=moveFunc=>{let movedCoord=moveFunc(coordToBeMoved);if(movedCoord[X]===otherPoint[X]){movedCoord=moveFunc(movedCoord);}return movedCoord};return {up:vec.add(coordToBeMoved,[0,snapStep[Y]]),down:vec.sub(coordToBeMoved,[0,snapStep[Y]]),left:moveWithConstraint(coord=>vec.sub(coord,[snapStep[X],0])),right:moveWithConstraint(coord=>vec.add(coord,[snapStep[X],0]))}};function getAbsoluteValueDescription(state,i18n){const{strings}=i18n;const{coords}=state;const{locale}=i18n;const[p1,p2]=coords;return strings.srInteractiveElements({elements:strings.srAbsoluteValueInteractiveElements({point1X:srFormatNumber(p1[X],locale),point1Y:srFormatNumber(p1[Y],locale),point2X:srFormatNumber(p2[X],locale),point2Y:srFormatNumber(p2[Y],locale)})})}function describeAbsoluteValueGraph(state,i18n){const{strings,locale}=i18n;const{coords}=state;const[vertex,armPoint]=coords;const coeffs=getAbsoluteValueCoefficients(coords);const m=coeffs?.m??1;const srAbsoluteValueGraph=strings.srAbsoluteValueGraph;const srAbsoluteValueVertexPoint=strings.srAbsoluteValueVertexPoint({x:srFormatNumber(vertex[X],locale),y:srFormatNumber(vertex[Y],locale)});const srAbsoluteValueSecondPoint=strings.srAbsoluteValueSecondPoint({x:srFormatNumber(armPoint[X],locale),y:srFormatNumber(armPoint[Y],locale)});const srAbsoluteValueDescription=strings.srAbsoluteValueDescription({x:srFormatNumber(vertex[X],locale),y:srFormatNumber(vertex[Y],locale),slope:srFormatNumber(m,locale)});return {srAbsoluteValueGraph,srAbsoluteValueVertexPoint,srAbsoluteValueSecondPoint,srAbsoluteValueDescription}}
1902
1902
 
1903
1903
  const{getClockwiseAngle: getClockwiseAngle$2}=angles;const{getSinusoidCoefficients: getSinusoidCoefficients$1,getTangentCoefficients: getTangentCoefficients$1,getQuadraticCoefficients: getQuadraticCoefficients$2,getExponentialCoefficients: getExponentialCoefficients$1,getLogarithmCoefficients: getLogarithmCoefficients$1}=coefficients;const{getLineEquation,getLineIntersectionString,magnitude: magnitude$2,vector: vector$2}=geometry;const UNLIMITED="unlimited";function numSteps(range,step){return Math.floor((range[1]-range[0])/step)}const makeInvalidTypeError=(functionName,graphType)=>{return new PerseusError(`${functionName} called but current graph type is not a '${graphType}'`,Errors.NotAllowed,{metadata:{graphType}})};function getLineCoords$1(graph,props){return graph.coords||pointsFromNormalized(props,[[.25,.75],[.75,.75]])}function getPointCoords$1(graph,props){const numPoints=graph.numPoints||1;let coords=graph.coords;if(coords){return coords}switch(numPoints){case 1:coords=[graph.coord||[0,0]];break;case 2:coords=[[-5,0],[5,0]];break;case 3:coords=[[-5,0],[0,0],[5,0]];break;case 4:coords=[[-6,0],[-2,0],[2,0],[6,0]];break;case 5:coords=[[-6,0],[-3,0],[0,0],[3,0],[6,0]];break;case 6:coords=[[-5,0],[-3,0],[-1,0],[1,0],[3,0],[5,0]];break;case UNLIMITED:coords=[];break}const range=[[-10,10],[-10,10]];const newCoords=normalizeCoords(coords,range);return pointsFromNormalized(props,newCoords)}function getLinearSystemCoords$1(graph,props){return graph.coords||_.map([[[.25,.75],[.75,.75]],[[.25,.25],[.75,.25]]],coords=>{return pointsFromNormalized(props,coords)})}function getPolygonCoords$1(graph,props){if(graph.type!=="polygon"){throw makeInvalidTypeError("toggleShowSides","polygon")}let coords=graph.coords;if(coords){return coords}const n=graph.numSides||3;if(n===UNLIMITED){coords=[];}else {const angle=2*Math.PI/n;const offset=(1/n-1/2)*Math.PI;const radius=graph.snapTo==="sides"?Math.sqrt(3)/3*7:4;coords=_.times(n,function(i){return [radius*Math.cos(i*angle+offset),radius*Math.sin(i*angle+offset)]});}const ranges=[[-10,10],[-10,10]];coords=normalizeCoords(coords,ranges);const snapToGrid=!_.contains(["angles","sides"],graph.snapTo);coords=pointsFromNormalized(props,coords,!snapToGrid);return coords}function getSegmentCoords$1(graph,props){const coords=graph.coords;if(coords){return coords}const n=graph.numSegments||1;const ys={1:[5],2:[5,-5],3:[5,0,-5],4:[6,2,-2,-6],5:[6,3,0,-3,-6],6:[5,3,1,-1,-3,-5]}[n];const range=[[-10,10],[-10,10]];return ys.map(function(y){let segment=[[-5,y],[5,y]];segment=normalizeCoords(segment,range);segment=pointsFromNormalized(props,segment);return segment})}function getAngleCoords$1(graph,props){let coords=graph.coords;if(coords){return coords}const snap=graph.snapDegrees||1;let angle=snap;while(angle<20){angle+=snap;}angle=angle*Math.PI/180;const offset=(graph.angleOffsetDeg||0)*Math.PI/180;coords=pointsFromNormalized(props,[[.85,.5],[.5,.5]]);const radius=magnitude$2(vector$2(...coords));coords[0]=[coords[1][0]+radius*Math.cos(offset),coords[1][1]+radius*Math.sin(offset)];coords[2]=[coords[1][0]+radius*Math.cos(angle+offset),coords[1][1]+radius*Math.sin(angle+offset)];return coords}function normalizeCoords(coordsList,ranges){return _.map(coordsList,function(coords){return _.map(coords,function(coord,i){const extent=ranges[i][1]-ranges[i][0];return (coord+ranges[i][1])/extent})})}function pointsFromNormalized(props,coordsList,noSnap){return _.map(coordsList,function(coords){return _.map(coords,function(coord,i){const range=props.range[i];if(noSnap){return range[0]+(range[1]-range[0])*coord}const step=props.step[i];const nSteps=numSteps(range,step);const tick=Math.round(coord*nSteps);return range[0]+step*tick})})}function getNoneEquationString(){return ""}function getLinearEquationString(props){const coords=getLineCoords$1(props.userInput,props);if(approximateEqual(coords[0][0],coords[1][0])){return "x = "+coords[0][0].toFixed(3)}const m=(coords[1][1]-coords[0][1])/(coords[1][0]-coords[0][0]);const b=coords[0][1]-m*coords[0][0];if(approximateEqual(m,0)){return "y = "+b.toFixed(3)}return "y = "+m.toFixed(3)+"x + "+b.toFixed(3)}function getCurrentQuadraticCoefficients(props){const coords=props.userInput.coords||defaultQuadraticCoords(props);return getQuadraticCoefficients$2(coords)}function defaultQuadraticCoords(props){const coords=[[.25,.75],[.5,.25],[.75,.75]];return pointsFromNormalized(props,coords)}function getQuadraticEquationString(props){const coeffs=getCurrentQuadraticCoefficients(props);return "y = "+coeffs[0].toFixed(3)+"x^2 + "+coeffs[1].toFixed(3)+"x + "+coeffs[2].toFixed(3)}function getCurrentSinusoidCoefficients(props){const coords=props.userInput.coords||defaultSinusoidCoords(props);return getSinusoidCoefficients$1(coords)}function defaultSinusoidCoords(props){const coords=[[.5,.5],[.65,.6]];return pointsFromNormalized(props,coords)}function getSinusoidEquationString(props){const coeffs=getCurrentSinusoidCoefficients(props);return "y = "+coeffs[0].toFixed(3)+"sin("+coeffs[1].toFixed(3)+"x - "+coeffs[2].toFixed(3)+") + "+coeffs[3].toFixed(3)}function defaultExponentialCoords(props){const coords=[[.5,.55],[.75,.75]];return pointsFromNormalized(props,coords,true)}function getExponentialEquationString(props){const coords=props.userInput.coords||defaultExponentialCoords(props);const asymptote=props.userInput.asymptote??0;const coeffs=getExponentialCoefficients$1(coords,asymptote);if(coeffs==null){return "y = e^x"}return "y = "+coeffs.a.toFixed(3)+"e^("+coeffs.b.toFixed(3)+"x) + "+coeffs.c.toFixed(3)}function defaultLogarithmCoords(props){const coords=[[.55,.55],[.75,.75]];return pointsFromNormalized(props,coords,true)}function getLogarithmEquationString(props){const coords=props.userInput.coords||defaultLogarithmCoords(props);const asymptote=props.userInput.asymptote??0;const coeffs=getLogarithmCoefficients$1(coords,asymptote);if(coeffs==null){return "y = ln(x)"}const cStr=coeffs.c===0?"x":coeffs.c<0?"x - "+Math.abs(coeffs.c).toFixed(3):"x + "+coeffs.c.toFixed(3);return "y = "+coeffs.a.toFixed(3)+"ln("+coeffs.b.toFixed(3)+cStr+")"}function getAbsoluteValueEquationString(props){const userInput=props.userInput;if(userInput.type!=="absolute-value"||!userInput.coords){return ""}const coeffs=getAbsoluteValueCoefficients(userInput.coords);if(coeffs===undefined){return ""}const{m,h,v}=coeffs;return "y = "+m.toFixed(3)+"|x - "+h.toFixed(3)+"| + "+v.toFixed(3)}function getCurrentTangentCoefficients(props){const coords=props.userInput.coords||defaultTangentCoords(props);return getTangentCoefficients$1(coords)}function defaultTangentCoords(props){const coords=[[.5,.5],[.75,.75]];return pointsFromNormalized(props,coords)}function getTangentEquationString(props){const coeffs=getCurrentTangentCoefficients(props);return "y = "+coeffs[0].toFixed(3)+"tan("+coeffs[1].toFixed(3)+"x - "+coeffs[2].toFixed(3)+") + "+coeffs[3].toFixed(3)}function getCircleEquationString(props){const graph=props.userInput;const center=graph.center||[0,0];const radius=graph.radius||2;return "center ("+center[0]+", "+center[1]+"), radius "+radius}function getLinearSystemEquationString(props){const coords=getLinearSystemCoords$1(props.userInput,props);return "\n"+getLineEquation(coords[0][0],coords[0][1])+"\n"+getLineEquation(coords[1][0],coords[1][1])+"\n"+getLineIntersectionString(coords[0],coords[1])}function getPointEquationString(props){if(props.userInput.type!=="point"){throw makeInvalidTypeError("getPointEquationString","point")}const coords=getPointCoords$1(props.userInput,props);return coords.map(function(coord){return "("+coord[0]+", "+coord[1]+")"}).join(", ")}function getSegmentEquationString(props){if(props.userInput.type!=="segment"){throw makeInvalidTypeError("getSegmentEquationString","segment")}const segments=getSegmentCoords$1(props.userInput,props);return _.map(segments,function(segment){return "["+_.map(segment,function(coord){return "("+coord.join(", ")+")"}).join(" ")+"]"}).join(" ")}function getRayEquationString(props){if(props.userInput.type!=="ray"){throw makeInvalidTypeError("createPointForPolygonType","ray")}const coords=getLineCoords$1(props.userInput,props);const a=coords[0];const b=coords[1];let eq=getLinearEquationString(props);if(a[0]>b[0]){eq+=" (for x <= "+a[0].toFixed(3)+")";}else if(a[0]<b[0]){eq+=" (for x >= "+a[0].toFixed(3)+")";}else if(a[1]>b[1]){eq+=" (for y <= "+a[1].toFixed(3)+")";}else {eq+=" (for y >= "+a[1].toFixed(3)+")";}return eq}function getPolygonEquationString(props){if(props.userInput.type!=="polygon"){throw makeInvalidTypeError("getPolygonEquationString","polygon")}const coords=getPolygonCoords$1(props.userInput,props);return _.map(coords,function(coord){return "("+coord.join(", ")+")"}).join(" ")}function getAngleEquationString(props){if(props.userInput.type!=="angle"){throw makeInvalidTypeError("getAngleEquationString","angle")}const coords=getAngleCoords$1(props.userInput,props);const allowReflexAngles=props.userInput.allowReflexAngles;const angle=getClockwiseAngle$2(coords,allowReflexAngles);return angle.toFixed(0)+"° angle"+" at ("+coords[1].join(", ")+")"}function getVectorEquationString(props){if(props.userInput.type!=="vector"){throw makeInvalidTypeError("getVectorEquationString","vector")}const coords=props.userInput.coords;if(!coords){return ""}const[tail,tip]=coords;const dx=tip[0]-tail[0];const dy=tip[1]-tail[1];return `\u27E8${dx.toFixed(3)}, ${dy.toFixed(3)}\u27E9`}function getEquationString(props){const type=props.userInput.type;switch(type){case "none":return getNoneEquationString();case "linear":return getLinearEquationString(props);case "quadratic":return getQuadraticEquationString(props);case "sinusoid":return getSinusoidEquationString(props);case "circle":return getCircleEquationString(props);case "linear-system":return getLinearSystemEquationString(props);case "point":return getPointEquationString(props);case "segment":return getSegmentEquationString(props);case "ray":return getRayEquationString(props);case "polygon":return getPolygonEquationString(props);case "angle":return getAngleEquationString(props);case "absolute-value":return getAbsoluteValueEquationString(props);case "exponential":return getExponentialEquationString(props);case "tangent":return getTangentEquationString(props);case "logarithm":return getLogarithmEquationString(props);case "vector":return getVectorEquationString(props);default:throw new UnreachableCaseError(type)}}
1904
1904
 
@@ -1930,7 +1930,7 @@ function SVGLine(props){const{start,end,style,className,testId}=props;return jsx
1930
1930
 
1931
1931
  const{calculateAngleInDegrees: calculateAngleInDegrees$4}=angles;function Vector(props){const{interactiveColor}=useGraphConfig();const{tail,tip,color=interactiveColor,strokeWidth=2,style,testId}=props;const[tailPx,tipPx]=useTransformVectorsToPixels(tail,tip);const direction=vec.sub(tipPx,tailPx);const angle=calculateAngleInDegrees$4(direction);return jsxRuntimeExports.jsxs("g",{style:{stroke:color,strokeWidth},"data-testid":testId,children:[jsxRuntimeExports.jsx(SVGLine,{start:tailPx,end:tipPx,style:style}),jsxRuntimeExports.jsx(Arrowhead,{angle:angle,tip:tip,color:color,strokeWidth:strokeWidth})]})}
1932
1932
 
1933
- const getIntersectionOfRayWithBox=(initialPoint,throughPoint,box)=>{const[[xMin,xMax],[yMin,yMax]]=box;const[aX,aY]=initialPoint;const[bX,bY]=throughPoint;const yDiff=bY-aY;const xDiff=bX-aX;const slope=yDiff/xDiff;const inverseSlope=1/slope;const xExtreme=xDiff<0?xMin:xMax;const yExtreme=yDiff<0?yMin:yMax;const yAtXExtreme=aY+(xExtreme-aX)*slope;const xAtYExtreme=aX+(yExtreme-aY)*inverseSlope;switch(true){case isBetween(yAtXExtreme,yMin,yMax):return [xExtreme,yAtXExtreme];case isBetween(xAtYExtreme,xMin,xMax):return [xAtYExtreme,yExtreme];default:return [xExtreme,yExtreme]}};function isBetween(x,low,high){return x>=low&&x<=high}function getArrayWithoutDuplicates(array){const returnArray=[];for(const coordPair of array){if(!returnArray.some(([x,y])=>x===coordPair[0]&&y===coordPair[1])){returnArray.push(coordPair);}}return returnArray}function getSlopeStringForLine(line,strings){const slope=(line[1][1]-line[0][1])/(line[1][0]-line[0][0]);if(!Number.isFinite(slope)){return strings.srLinearGraphSlopeVertical}if(slope===0){return strings.srLinearGraphSlopeHorizontal}return slope>0?strings.srLinearGraphSlopeIncreasing:strings.srLinearGraphSlopeDecreasing}function getInterceptStringForLine(line,strings,locale){const slope=(line[1][1]-line[0][1])/(line[1][0]-line[0][0]);const xIntercept=(0-line[0][1])/slope+line[0][0];const yIntercept=line[0][1]-slope*line[0][0];const overlapsXAxis=line[0][1]===0&&line[1][1]===0;const overlapsYAxis=line[0][0]===0&&line[1][0]===0;const hasXIntercept=Number.isFinite(xIntercept)&&!overlapsXAxis;const hasYIntercept=Number.isFinite(yIntercept)&&!overlapsYAxis;if(hasXIntercept&&hasYIntercept){return xIntercept===0&&yIntercept===0?strings.srLinearGraphOriginIntercept:strings.srLinearGraphBothIntercepts({xIntercept:srFormatNumber(xIntercept,locale),yIntercept:srFormatNumber(yIntercept,locale)})}return hasXIntercept?strings.srLinearGraphXOnlyIntercept({xIntercept:srFormatNumber(xIntercept,locale)}):strings.srLinearGraphYOnlyIntercept({yIntercept:srFormatNumber(yIntercept,locale)})}function getCoordQuadrant(coord){const[unroundedX,unroundedY]=coord;const x=Number(unroundedX.toFixed(3));const y=Number(unroundedY.toFixed(3));if(x===0&&y===0){return "origin"}if(y===0){return "x-axis"}if(x===0){return "y-axis"}if(x>0&&y>0){return 1}if(x<0&&y>0){return 2}if(x<0&&y<0){return 3}return 4}function getQuadraticVertexString(vertex,strings){const location=getCoordQuadrant(vertex);switch(location){case "origin":return strings.srQuadraticGraphVertexOrigin;case "x-axis":return strings.srQuadraticGraphVertexXAxis;case "y-axis":return strings.srQuadraticGraphVertexYAxis;default:return strings.srQuadraticGraphVertexQuadrant({quadrant:location})}}function getQuadraticPointString(pointNumber,coord,strings,locale){const location=getCoordQuadrant(coord);const[x,y]=coord;switch(location){case "origin":return strings.srQuadraticPointOrigin({pointNumber:pointNumber});case "x-axis":case "y-axis":return strings.srQuadraticPointAxis({pointNumber:pointNumber,x:srFormatNumber(x,locale),y:srFormatNumber(y,locale)});default:return strings.srQuadraticPointQuadrant({pointNumber:pointNumber,quadrant:location,x:srFormatNumber(x,locale),y:srFormatNumber(y,locale)})}}function getQuadraticXIntercepts(a,b,c){if(a===0){if(b===0){return []}return [-c/b]}const discriminant=b*b-4*a*c;if(discriminant<0){return []}const x1=(-b+Math.sqrt(discriminant))/(2*a);const x2=(-b-Math.sqrt(discriminant))/(2*a);if(x1===x2){return [x1]}return [x1,x2]}function getAngleFromPoints(points,i){if(i<0||i>=points.length||!Number.isInteger(i)){return null}if(points.length<3){return null}const point=points.at(i);const pt1=points.at(i-1);const pt2=points[(i+1)%points.length];if(!point||!pt1||!pt2){return null}const a=vec.dist(point,pt1);const b=vec.dist(point,pt2);const c=vec.dist(pt1,pt2);let lawOfCosinesRadicand=(a**2+b**2-c**2)/(2*a*b);if(lawOfCosinesRadicand<-1||lawOfCosinesRadicand>1){lawOfCosinesRadicand=Math.round(lawOfCosinesRadicand);}const angle=Math.acos(lawOfCosinesRadicand);return angle}function getSideLengthsFromPoints(points,i,isPolygonOpen){if(i<0||i>=points.length||!Number.isInteger(i)){return []}if(points.length<2){return []}const returnArray=[];const point=points[i];const point1Index=i===0?points.length-1:i-1;const point1=points[point1Index];const side1=i!==point1Index?vec.dist(point,point1):null;if(side1&&!(isPolygonOpen&&i===0)){returnArray.push({pointIndex:point1Index,sideLength:side1});}const point2Index=(i+1)%points.length;const point2=points[point2Index];const side2=i!==point2Index&&point2Index!==point1Index?vec.dist(point,point2):null;if(side2&&!(isPolygonOpen&&i===points.length-1)){returnArray.push({pointIndex:point2Index,sideLength:side2});}return returnArray}function getPolygonSideString(sideLength,pointIndex,strings,locale){return Number.isInteger(sideLength)?strings.srPolygonSideLength({pointNum:pointIndex+1,length:`${sideLength}`}):strings.srPolygonSideLengthApprox({pointNum:pointIndex+1,length:srFormatNumber(sideLength,locale,1)})}function calculateScaledRadius(range){const[[xMin,xMax],[yMin,yMax]]=range;const xSpan=xMax-xMin;const ySpan=yMax-yMin;const minSpan=Math.min(xSpan,ySpan);return minSpan*.06}function getAsymptoteGraphKeyboardConstraint(coords,snapStep,pointIndex,isValidPosition){const coordToBeMoved=coords[pointIndex];const movePointWithConstraint=moveFunc=>{let movedCoord=moveFunc(coordToBeMoved);for(let i=0;i<3&&!isValidPosition(movedCoord);i++){movedCoord=moveFunc(movedCoord);}if(!isValidPosition(movedCoord)){return coordToBeMoved}return movedCoord};return {up:movePointWithConstraint(coord=>vec.add(coord,[0,snapStep[1]])),down:movePointWithConstraint(coord=>vec.sub(coord,[0,snapStep[1]])),left:movePointWithConstraint(coord=>vec.sub(coord,[snapStep[0],0])),right:movePointWithConstraint(coord=>vec.add(coord,[snapStep[0],0]))}}function getAsymptoteHandleCoord(orientation,range,asymptote){if(orientation==="horizontal"){const midX=(range[0][0]+range[0][1])/2;return [midX,asymptote]}const midY=(range[1][0]+range[1][1])/2;return [asymptote,midY]}function skipAsymptoteKeyboardOverPoint(proposed,currentAsymptote,coords,handleCoord,snapStep,orientation){const snapped=snap(snapStep,proposed);const moveAxis=orientation==="horizontal"?1:0;const pinAxis=orientation==="horizontal"?0:1;const step=snapStep[moveAxis];const dir=Math.sign(snapped[moveAxis]-currentAsymptote);if(dir===0){return snapped}const pinValue=handleCoord[pinAxis];const isForbidden=v=>coords.some(c=>c[pinAxis]===pinValue&&c[moveAxis]===v);let value=snapped[moveAxis];for(let i=0;i<3&&isForbidden(value);i++){value+=dir*step;}const result=[0,0];result[moveAxis]=value;result[pinAxis]=snapped[pinAxis];return result}
1933
+ const getIntersectionOfRayWithBox=(initialPoint,throughPoint,box)=>{const[[xMin,xMax],[yMin,yMax]]=box;const[aX,aY]=initialPoint;const[bX,bY]=throughPoint;const yDiff=bY-aY;const xDiff=bX-aX;const slope=yDiff/xDiff;const inverseSlope=1/slope;const xExtreme=xDiff<0?xMin:xMax;const yExtreme=yDiff<0?yMin:yMax;const yAtXExtreme=aY+(xExtreme-aX)*slope;const xAtYExtreme=aX+(yExtreme-aY)*inverseSlope;switch(true){case isBetween(yAtXExtreme,yMin,yMax):return [xExtreme,yAtXExtreme];case isBetween(xAtYExtreme,xMin,xMax):return [xAtYExtreme,yExtreme];default:return [xExtreme,yExtreme]}};function isBetween(x,low,high){return x>=low&&x<=high}function getArrayWithoutDuplicates(array){const returnArray=[];for(const coordPair of array){if(!returnArray.some(([x,y])=>x===coordPair[0]&&y===coordPair[1])){returnArray.push(coordPair);}}return returnArray}function getSlopeStringForLine(line,strings){const slope=(line[1][1]-line[0][1])/(line[1][0]-line[0][0]);if(!Number.isFinite(slope)){return strings.srLinearGraphSlopeVertical}if(slope===0){return strings.srLinearGraphSlopeHorizontal}return slope>0?strings.srLinearGraphSlopeIncreasing:strings.srLinearGraphSlopeDecreasing}function getInterceptStringForLine(line,strings,locale){const slope=(line[1][1]-line[0][1])/(line[1][0]-line[0][0]);const xIntercept=(0-line[0][1])/slope+line[0][0];const yIntercept=line[0][1]-slope*line[0][0];const overlapsXAxis=line[0][1]===0&&line[1][1]===0;const overlapsYAxis=line[0][0]===0&&line[1][0]===0;const hasXIntercept=Number.isFinite(xIntercept)&&!overlapsXAxis;const hasYIntercept=Number.isFinite(yIntercept)&&!overlapsYAxis;if(hasXIntercept&&hasYIntercept){return xIntercept===0&&yIntercept===0?strings.srLinearGraphOriginIntercept:strings.srLinearGraphBothIntercepts({xIntercept:srFormatNumber(xIntercept,locale),yIntercept:srFormatNumber(yIntercept,locale)})}return hasXIntercept?strings.srLinearGraphXOnlyIntercept({xIntercept:srFormatNumber(xIntercept,locale)}):strings.srLinearGraphYOnlyIntercept({yIntercept:srFormatNumber(yIntercept,locale)})}function getQuadraticXIntercepts(a,b,c){if(a===0){if(b===0){return []}return [-c/b]}const discriminant=b*b-4*a*c;if(discriminant<0){return []}const x1=(-b+Math.sqrt(discriminant))/(2*a);const x2=(-b-Math.sqrt(discriminant))/(2*a);if(x1===x2){return [x1]}return [x1,x2]}function getQuadraticVertex(coeffs){const[a,b,c]=coeffs;if(a===0){return undefined}return [-b/(2*a),c-b*b/(4*a)]}function getAngleFromPoints(points,i){if(i<0||i>=points.length||!Number.isInteger(i)){return null}if(points.length<3){return null}const point=points.at(i);const pt1=points.at(i-1);const pt2=points[(i+1)%points.length];if(!point||!pt1||!pt2){return null}const a=vec.dist(point,pt1);const b=vec.dist(point,pt2);const c=vec.dist(pt1,pt2);let lawOfCosinesRadicand=(a**2+b**2-c**2)/(2*a*b);if(lawOfCosinesRadicand<-1||lawOfCosinesRadicand>1){lawOfCosinesRadicand=Math.round(lawOfCosinesRadicand);}const angle=Math.acos(lawOfCosinesRadicand);return angle}function getSideLengthsFromPoints(points,i,isPolygonOpen){if(i<0||i>=points.length||!Number.isInteger(i)){return []}if(points.length<2){return []}const returnArray=[];const point=points[i];const point1Index=i===0?points.length-1:i-1;const point1=points[point1Index];const side1=i!==point1Index?vec.dist(point,point1):null;if(side1&&!(isPolygonOpen&&i===0)){returnArray.push({pointIndex:point1Index,sideLength:side1});}const point2Index=(i+1)%points.length;const point2=points[point2Index];const side2=i!==point2Index&&point2Index!==point1Index?vec.dist(point,point2):null;if(side2&&!(isPolygonOpen&&i===points.length-1)){returnArray.push({pointIndex:point2Index,sideLength:side2});}return returnArray}function getPolygonSideString(sideLength,pointIndex,strings,locale){return Number.isInteger(sideLength)?strings.srPolygonSideLength({pointNum:pointIndex+1,length:`${sideLength}`}):strings.srPolygonSideLengthApprox({pointNum:pointIndex+1,length:srFormatNumber(sideLength,locale,1)})}function calculateScaledRadius(range){const[[xMin,xMax],[yMin,yMax]]=range;const xSpan=xMax-xMin;const ySpan=yMax-yMin;const minSpan=Math.min(xSpan,ySpan);return minSpan*.06}function getAsymptoteGraphKeyboardConstraint(coords,snapStep,pointIndex,isValidPosition){const coordToBeMoved=coords[pointIndex];const movePointWithConstraint=moveFunc=>{let movedCoord=moveFunc(coordToBeMoved);for(let i=0;i<3&&!isValidPosition(movedCoord);i++){movedCoord=moveFunc(movedCoord);}if(!isValidPosition(movedCoord)){return coordToBeMoved}return movedCoord};return {up:movePointWithConstraint(coord=>vec.add(coord,[0,snapStep[1]])),down:movePointWithConstraint(coord=>vec.sub(coord,[0,snapStep[1]])),left:movePointWithConstraint(coord=>vec.sub(coord,[snapStep[0],0])),right:movePointWithConstraint(coord=>vec.add(coord,[snapStep[0],0]))}}function getAsymptoteHandleCoord(orientation,range,asymptote){if(orientation==="horizontal"){const midX=(range[0][0]+range[0][1])/2;return [midX,asymptote]}const midY=(range[1][0]+range[1][1])/2;return [asymptote,midY]}function skipAsymptoteKeyboardOverPoint(proposed,currentAsymptote,coords,handleCoord,snapStep,orientation){const snapped=snap(snapStep,proposed);const moveAxis=orientation==="horizontal"?1:0;const pinAxis=orientation==="horizontal"?0:1;const step=snapStep[moveAxis];const dir=Math.sign(snapped[moveAxis]-currentAsymptote);if(dir===0){return snapped}const pinValue=handleCoord[pinAxis];const isForbidden=v=>coords.some(c=>c[pinAxis]===pinValue&&c[moveAxis]===v);let value=snapped[moveAxis];for(let i=0;i<3&&isForbidden(value);i++){value+=dir*step;}const result=[0,0];result[moveAxis]=value;result[pinAxis]=snapped[pinAxis];return result}
1934
1934
 
1935
1935
  const{calculateAngleInDegrees: calculateAngleInDegrees$3}=angles;const LockedLine=props=>{const{color,lineStyle,kind,points,showPoint1,showPoint2,weight,ariaLabel,range}=props;const[point1,point2]=points;const hasAria=!!ariaLabel;let line;if(kind==="ray"){const extendedPoint=getIntersectionOfRayWithBox(point1.coord,point2.coord,range);line=jsxRuntimeExports.jsx(Vector,{tail:point1.coord,tip:extendedPoint,color:lockedFigureColors[color],strokeWidth:strokeWeights[weight],style:{strokeDasharray:lineStyle==="dashed"?"var(--mafs-line-stroke-dash-style)":undefined}});}else {const LineType=kind==="segment"?Line$4.Segment:Line$4.ThroughPoints;let arrowTip=kind==="segment"?point2.coord:getIntersectionOfRayWithBox(point1.coord,point2.coord,range);const[tailPx,tipPx]=useTransformVectorsToPixels(point2.coord,point1.coord);const direction=vec.sub(tailPx,tipPx);let angle=calculateAngleInDegrees$3(direction);const startArrowHead=kind!=="segment"&&jsxRuntimeExports.jsx(Arrowhead,{angle:angle,tip:arrowTip,color:lockedFigureColors[color],strokeWidth:strokeWeights[weight]});arrowTip=kind==="segment"?point1.coord:getIntersectionOfRayWithBox(point2.coord,point1.coord,range);angle=angle>180?angle-180:angle+180;const endArrowHead=kind!=="segment"&&jsxRuntimeExports.jsx(Arrowhead,{angle:angle,tip:arrowTip,color:lockedFigureColors[color],strokeWidth:strokeWeights[weight]});line=jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[startArrowHead,jsxRuntimeExports.jsx(LineType,{point1:point1.coord,point2:point2.coord,color:lockedFigureColors[color],weight:strokeWeights[weight],style:lineStyle}),endArrowHead]});}return jsxRuntimeExports.jsxs("g",{className:kind==="ray"?"locked-ray":"locked-line","aria-label":hasAria?ariaLabel:undefined,"aria-hidden":!hasAria,role:"img",children:[line,showPoint1&&jsxRuntimeExports.jsx(Point$2,{x:point1.coord[X],y:point1.coord[Y],svgCircleProps:{style:{fill:point1.filled?lockedFigureColors[point1.color]:semanticColor.core.background.base.default,stroke:lockedFigureColors[point1.color],strokeWidth:spacing.xxxxSmall_2}}}),showPoint2&&jsxRuntimeExports.jsx(Point$2,{x:point2.coord[X],y:point2.coord[Y],svgCircleProps:{style:{fill:point2.filled?lockedFigureColors[point2.color]:semanticColor.core.background.base.default,stroke:lockedFigureColors[point2.color],strokeWidth:spacing.xxxxSmall_2}}})]})};
1936
1936
 
@@ -1948,7 +1948,11 @@ const TextLabel=({children,...rest})=>jsxRuntimeExports.jsx(Text$1,{size:16,svgT
1948
1948
 
1949
1949
  const{clockwise: clockwise$1}=geometry;const{getAngleFromVertex: getAngleFromVertex$1}=angles;const PolygonAngle=({centerPoint,endPoints,showAngles,snapTo,areEndPointsClockwise})=>{const{range}=useGraphConfig();const[centerX,centerY]=centerPoint;const[[startX,startY],[endX,endY]]=areEndPointsClockwise?endPoints:endPoints.reverse();const radius=calculateScaledRadius(range);const a=vec.dist(centerPoint,endPoints[0]);const b=vec.dist(centerPoint,endPoints[1]);const c=vec.dist(endPoints[0],endPoints[1]);let lawOfCosinesRadicand=(a**2+b**2-c**2)/(2*a*b);if(lawOfCosinesRadicand<-1||lawOfCosinesRadicand>1){lawOfCosinesRadicand=Math.round(lawOfCosinesRadicand);}const angle=Math.acos(lawOfCosinesRadicand);const y1=centerY+(startY-centerY)/a*radius;const x2=centerX+(endX-centerX)/b*radius;const x1=centerX+(startX-centerX)/a*radius;const y2=centerY+(endY-centerY)/b*radius;const[x3,y3]=vec.add(centerPoint,vec.add(vec.sub([x1,y1],centerPoint),vec.sub([x2,y2],centerPoint)));if(!showAngles){return isRightPolygonAngle(angle)?jsxRuntimeExports.jsx(RightAngleSquare,{start:[x1,y1],vertex:[x2,y2],end:[x3,y3],nonScalingStroke:true}):null}const isConcave=isConcavePolygonVertex(centerPoint,endPoints);const largeArcFlag=isConcave?1:0;const arc=`M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} 1 ${x2} ${y2}`;let angleInDegrees=angle*(180/Math.PI);if(isConcave){angleInDegrees=360-angleInDegrees;}const angleLabelNumber=parseFloat(angleInDegrees.toFixed(snapTo==="angles"?0:1));const angleLabel=Number.isInteger(angleLabelNumber)?angleLabelNumber:"≈ "+angleLabelNumber;return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("defs",{children:jsxRuntimeExports.jsxs("filter",{id:"background",x:"-5%",width:"110%",y:"0%",height:"100%",children:[jsxRuntimeExports.jsx("feFlood",{floodColor:semanticColor.core.background.base.default,floodOpacity:"0.5"}),jsxRuntimeExports.jsx("feComposite",{operator:"over",in:"SourceGraphic"})]})}),!isConcave&&isRightPolygonAngle(angle)?jsxRuntimeExports.jsx(RightAngleSquare,{start:[x1,y1],vertex:[x2,y2],end:[x3,y3],nonScalingStroke:true}):jsxRuntimeExports.jsx(Arc,{arc:arc,nonScalingStroke:true}),jsxRuntimeExports.jsxs(TextLabel,{x:x3,y:y3,attach:y3-centerY>0?"s":"n",attachDistance:Math.abs(y3-centerY)<.2||vec.dist([x3,y3],centerPoint)<.3?20:10,children:[angleLabel,"°"]})]})};const Angle=({vertex,coords,showAngles,allowReflexAngles,range})=>{const areClockwise=clockwise$1([...coords,vertex]);const shouldReverseCoords=areClockwise&&!allowReflexAngles;const clockwiseCoords=shouldReverseCoords?coords:coords.reverse();const startAngle=getAngleFromVertex$1(clockwiseCoords[0],vertex);const endAngle=getAngleFromVertex$1(clockwiseCoords[1],vertex);const angle=(startAngle+360-endAngle)%360;const isReflexive=angle>180;const[centerX,centerY]=vertex;const[point1,point2]=clockwiseCoords;const[startX,startY]=point1;const[endX,endY]=point2;const a=vec.dist(vertex,point1);const b=vec.dist(vertex,point2);const radius=2;const y1=centerY+(startY-centerY)/a*radius;const x2=centerX+(endX-centerX)/b*radius;const x1=centerX+(startX-centerX)/a*radius;const y2=centerY+(endY-centerY)/b*radius;const[x3,y3]=vec.add(vertex,vec.add(vec.sub([x1,y1],vertex),vec.sub([x2,y2],vertex)));const isOutside=shouldDrawArcOutside([x3,y3],vertex,range,[[vertex,point1],[vertex,point2]]);const largeArcFlag=isOutside||isReflexive?1:0;const sweepFlag=isOutside&&isReflexive?1:0;const arc=`M ${x1} ${y1} A ${radius} ${radius} 0 ${largeArcFlag} ${sweepFlag} ${x2} ${y2}`;const angleLabel=parseFloat(angle.toFixed(0));const[textX,textY]=calculateBisectorPoint(point1,point2,vertex,isReflexive,allowReflexAngles,radius);const{disableKeyboardInteraction}=useGraphConfig();const arcClassName=disableKeyboardInteraction?"angle-arc-static":"angle-arc-interactive";return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("defs",{children:jsxRuntimeExports.jsxs("filter",{id:"background",x:"-5%",width:"110%",y:"0%",height:"100%",children:[jsxRuntimeExports.jsx("feFlood",{floodColor:semanticColor.core.background.base.default,floodOpacity:"0.5"}),jsxRuntimeExports.jsx("feComposite",{operator:"over",in:"SourceGraphic"})]})}),isRightAngle(angle)?jsxRuntimeExports.jsx(RightAngleSquare,{start:[x1,y1],vertex:[x2,y2],end:[x3,y3],className:arcClassName}):jsxRuntimeExports.jsx(Arc,{arc:arc,className:arcClassName}),showAngles&&jsxRuntimeExports.jsxs(TextLabel,{x:textX,y:textY,color:semanticColor.core.foreground.instructive.default,children:[angleLabel,"°"]})]})};const RightAngleSquare=({start:[x1,y1],vertex:[x2,y2],end:[x3,y3],className,nonScalingStroke})=>jsxRuntimeExports.jsx(MafsCssTransformWrapper,{children:jsxRuntimeExports.jsx("path",{"aria-hidden":true,d:`M ${x1} ${y1} L ${x3} ${y3} M ${x3} ${y3} L ${x2} ${y2}`,strokeWidth:1,fill:"none",className:className,"data-testid":"angle-indicators__right-angle",vectorEffect:nonScalingStroke?"non-scaling-stroke":"none"})});const Arc=({arc,className,nonScalingStroke})=>{return jsxRuntimeExports.jsx(MafsCssTransformWrapper,{children:jsxRuntimeExports.jsx("path",{"aria-hidden":true,d:arc,strokeWidth:1,fill:"none",className:className,"data-testid":"angle-indicators__arc",vectorEffect:nonScalingStroke?"non-scaling-stroke":"none"})})};const isRightPolygonAngle=angle=>Math.abs(angle-Math.PI/2)<.01;const isRightAngle=angle=>Math.round(angle)===90;const shouldDrawArcOutside=(midpoint,vertex,range,polygonLines)=>{const rangeIntersectionPoint=getIntersectionOfRayWithBox(midpoint,vertex,range);let lineIntersectionCount=0;polygonLines.forEach(line=>segmentsIntersect([vertex,rangeIntersectionPoint],line)&&lineIntersectionCount++);return !isEven(lineIntersectionCount)};const isEven=n=>n%2===0;function isConcavePolygonVertex(centerPoint,clockwiseEndpoints){const v1=vec.sub(clockwiseEndpoints[1],centerPoint);const v2=vec.sub(clockwiseEndpoints[0],centerPoint);const crossProduct=v1[0]*v2[1]-v1[1]*v2[0];return crossProduct>0}function calculateBisectorPoint(point1,point2,vertex,isReflex,allowReflex,arcRadius){const[originX,originY]=vertex;const[x1,y1]=point1;const[x2,y2]=point2;const vectorA=[x1-originX,y1-originY];const vectorB=[x2-originX,y2-originY];const angleA=Math.atan2(vectorA[1],vectorA[0]);const angleB=Math.atan2(vectorB[1],vectorB[0]);let averageAngle=(angleA+angleB)/2;const angleRadians=Math.abs(angleA-angleB);if(allowReflex){if(angleRadians<=Math.PI&&isReflex||angleB>angleA){averageAngle+=Math.PI;}}else {if(angleRadians>Math.PI){averageAngle-=Math.PI;}}const sum=[Math.cos(averageAngle),Math.sin(averageAngle)];const sumMagnitude=Math.sqrt(sum[0]**2+sum[1]**2);const bisectorDirection=[sum[0]/sumMagnitude,sum[1]/sumMagnitude];const initialDistance=Math.sqrt(bisectorDirection[0]**2+bisectorDirection[1]**2);const requiredDistance=arcRadius*1.75;let radius=requiredDistance/initialDistance;if(initialDistance>=requiredDistance){radius=1;}const bisectorPoint=[bisectorDirection[0]*radius,bisectorDirection[1]*radius];const scaledBisector=vec.add(bisectorPoint,vertex);return scaledBisector}
1950
1950
 
1951
- const MovableLine=props=>{const{points:[start,end],ariaLabels,ariaDescribedBy,ariaLive,extend,onMoveLine=()=>{},onMovePoint=()=>{}}=props;const{snapStep}=useGraphConfig();const[ariaLives,setAriaLives]=React.useState(["off","off","off"]);const point1AriaLive=ariaLive??ariaLives[0];const point2AriaLive=ariaLive??ariaLives[1];const lineAriaLive=ariaLive??ariaLives[2];const{visiblePoint:visiblePoint1,focusableHandle:focusableHandle1}=useControlPoint({ariaLabel:ariaLabels?.point1AriaLabel,ariaDescribedBy:ariaDescribedBy,ariaLive:point1AriaLive,point:start,sequenceNumber:1,onMove:p=>{setAriaLives(["polite","off","off"]);onMovePoint(0,p);},constrain:getMovableLineKeyboardConstraint([start,end],snapStep,0)});const{visiblePoint:visiblePoint2,focusableHandle:focusableHandle2}=useControlPoint({ariaLabel:ariaLabels?.point2AriaLabel,ariaDescribedBy:ariaDescribedBy,ariaLive:point2AriaLive,point:end,sequenceNumber:2,onMove:p=>{setAriaLives(["off","polite","off"]);onMovePoint(1,p);},constrain:getMovableLineKeyboardConstraint([start,end],snapStep,1)});const line=jsxRuntimeExports.jsx(Line$1,{ariaLabel:ariaLabels?.grabHandleAriaLabel,ariaDescribedBy:ariaDescribedBy,ariaLive:lineAriaLive,start:start,end:end,extend:extend,onMove:newStart=>{setAriaLives(["off","off","polite"]);onMoveLine(newStart);}});return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[focusableHandle1,line,focusableHandle2,visiblePoint1,visiblePoint2]})};const Line$1=props=>{const{start,end,ariaLabel,ariaDescribedBy,ariaLive,extend,onMove}=props;const[startPtPx,endPtPx]=useTransformVectorsToPixels(start,end);const{range,graphDimensionsInPixels,snapStep,disableKeyboardInteraction,interactiveColor}=useGraphConfig();const computeExtensionTip=(tail,source)=>insetTipAlongRay(tail,getIntersectionOfRayWithBox(source,tail,range),range,graphDimensionsInPixels);const startExtend=extend?.start?computeExtensionTip(start,end):undefined;const endExtend=extend?.end?computeExtensionTip(end,start):undefined;const line=useRef(null);const{dragging}=useDraggable({gestureTarget:line,point:start,onMove,constrainKeyboardMovement:p=>snap(snapStep,p)});return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsxs("g",{ref:line,tabIndex:disableKeyboardInteraction?-1:0,"aria-label":ariaLabel,"aria-describedby":ariaDescribedBy,"aria-live":ariaLive,"aria-disabled":disableKeyboardInteraction,className:"movable-line","data-testid":"movable-line",style:{cursor:dragging?"grabbing":"grab"},role:"button",children:[jsxRuntimeExports.jsx(SVGLine,{start:startPtPx,end:endPtPx,style:{stroke:"transparent",strokeWidth:TARGET_SIZE}}),jsxRuntimeExports.jsx(SVGLine,{start:startPtPx,end:endPtPx,className:"movable-line-focus-outline",style:{}}),jsxRuntimeExports.jsx(SVGLine,{start:startPtPx,end:endPtPx,className:"movable-line-focus-outline-gap",style:{}}),jsxRuntimeExports.jsx(SVGLine,{start:startPtPx,end:endPtPx,style:{stroke:interactiveColor,strokeWidth:"var(--movable-line-stroke-weight)"},className:dragging?"movable-dragging":"",testId:"movable-line__line"})]}),startExtend&&jsxRuntimeExports.jsx(Vector,{tail:start,tip:startExtend,testId:"movable-line__vector"}),endExtend&&jsxRuntimeExports.jsx(Vector,{tail:end,tip:endExtend,testId:"movable-line__vector"})]})};const getMovableLineKeyboardConstraint=(line,snapStep,pointIndex)=>{const coordToBeMoved=line[pointIndex];const otherPoint=line[1-pointIndex];const movePointWithConstraint=moveFunc=>{let movedCoord=moveFunc(coordToBeMoved);if(vec.dist(movedCoord,otherPoint)===0){movedCoord=moveFunc(movedCoord);}return movedCoord};return {up:movePointWithConstraint(coord=>vec.add(coord,[0,snapStep[1]])),down:movePointWithConstraint(coord=>vec.sub(coord,[0,snapStep[1]])),left:movePointWithConstraint(coord=>vec.sub(coord,[snapStep[0],0])),right:movePointWithConstraint(coord=>vec.add(coord,[snapStep[0],0]))}};const GRAPH_EDGE_INSET_PX=4;function trimRange(range,graphDimensionsInPixels){const[xRange,yRange]=range;const[pixelsWide,pixelsTall]=graphDimensionsInPixels;const graphUnitsPerPixelX=size(xRange)/pixelsWide;const graphUnitsPerPixelY=size(yRange)/pixelsTall;const graphUnitsToTrimX=GRAPH_EDGE_INSET_PX*graphUnitsPerPixelX;const graphUnitsToTrimY=GRAPH_EDGE_INSET_PX*graphUnitsPerPixelY;return inset([graphUnitsToTrimX,graphUnitsToTrimY],range)}function insetTipAlongRay(tail,tip,range,graphDimensionsInPixels){const dx=tip[0]-tail[0];const dy=tip[1]-tail[1];const[pixelsWide,pixelsTall]=graphDimensionsInPixels;const pxPerUnitX=pixelsWide/size(range[0]);const pxPerUnitY=pixelsTall/size(range[1]);const lenPx=Math.hypot(dx*pxPerUnitX,dy*pxPerUnitY);if(lenPx===0){return tip}const scale=GRAPH_EDGE_INSET_PX/lenPx;return [tip[0]-dx*scale,tip[1]-dy*scale]}
1951
+ const getMovableLineKeyboardConstraint=(line,snapStep,pointIndex)=>{const coordToBeMoved=line[pointIndex];const otherPoint=line[1-pointIndex];const movePointWithConstraint=moveFunc=>{let movedCoord=moveFunc(coordToBeMoved);if(vec.dist(movedCoord,otherPoint)===0){movedCoord=moveFunc(movedCoord);}return movedCoord};return {up:movePointWithConstraint(coord=>vec.add(coord,[0,snapStep[1]])),down:movePointWithConstraint(coord=>vec.sub(coord,[0,snapStep[1]])),left:movePointWithConstraint(coord=>vec.sub(coord,[snapStep[0],0])),right:movePointWithConstraint(coord=>vec.add(coord,[snapStep[0],0]))}};const GRAPH_EDGE_INSET_PX=4;function trimRange(range,graphDimensionsInPixels){const[xRange,yRange]=range;const[pixelsWide,pixelsTall]=graphDimensionsInPixels;const graphUnitsPerPixelX=size(xRange)/pixelsWide;const graphUnitsPerPixelY=size(yRange)/pixelsTall;const graphUnitsToTrimX=GRAPH_EDGE_INSET_PX*graphUnitsPerPixelX;const graphUnitsToTrimY=GRAPH_EDGE_INSET_PX*graphUnitsPerPixelY;return inset([graphUnitsToTrimX,graphUnitsToTrimY],range)}function insetTipAlongRay(tail,tip,range,graphDimensionsInPixels){const dx=tip[0]-tail[0];const dy=tip[1]-tail[1];const[pixelsWide,pixelsTall]=graphDimensionsInPixels;const pxPerUnitX=pixelsWide/size(range[0]);const pxPerUnitY=pixelsTall/size(range[1]);const lenPx=Math.hypot(dx*pxPerUnitX,dy*pxPerUnitY);if(lenPx===0){return tip}const scale=GRAPH_EDGE_INSET_PX/lenPx;return [tip[0]-dx*scale,tip[1]-dy*scale]}
1952
+
1953
+ const Line$1=props=>{const{start,end,ariaLabel,ariaDescribedBy,ariaLive,extend,onMove}=props;const[startPtPx,endPtPx]=useTransformVectorsToPixels(start,end);const{range,graphDimensionsInPixels,snapStep,disableKeyboardInteraction,interactiveColor}=useGraphConfig();const computeExtensionTip=(tail,source)=>insetTipAlongRay(tail,getIntersectionOfRayWithBox(source,tail,range),range,graphDimensionsInPixels);const startExtend=extend?.start?computeExtensionTip(start,end):undefined;const endExtend=extend?.end?computeExtensionTip(end,start):undefined;const line=useRef(null);const{dragging}=useDraggable({gestureTarget:line,point:start,onMove,constrainKeyboardMovement:p=>snap(snapStep,p)});return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsxs("g",{ref:line,tabIndex:disableKeyboardInteraction?-1:0,"aria-label":ariaLabel,"aria-describedby":ariaDescribedBy,"aria-live":ariaLive,"aria-disabled":disableKeyboardInteraction,className:"movable-line","data-testid":"movable-line",style:{cursor:dragging?"grabbing":"grab"},role:"button",children:[jsxRuntimeExports.jsx(SVGLine,{start:startPtPx,end:endPtPx,style:{stroke:"transparent",strokeWidth:TARGET_SIZE}}),jsxRuntimeExports.jsx(SVGLine,{start:startPtPx,end:endPtPx,className:"movable-line-focus-outline",style:{}}),jsxRuntimeExports.jsx(SVGLine,{start:startPtPx,end:endPtPx,className:"movable-line-focus-outline-gap",style:{}}),jsxRuntimeExports.jsx(SVGLine,{start:startPtPx,end:endPtPx,style:{stroke:interactiveColor,strokeWidth:"var(--movable-line-stroke-weight)"},className:dragging?"movable-dragging":"",testId:"movable-line__line"})]}),startExtend&&jsxRuntimeExports.jsx(Vector,{tail:start,tip:startExtend,testId:"movable-line__vector"}),endExtend&&jsxRuntimeExports.jsx(Vector,{tail:end,tip:endExtend,testId:"movable-line__vector"})]})};
1954
+
1955
+ const MovableLine=props=>{const{points:[start,end],ariaLabels,ariaDescribedBy,ariaLive,extend,onMoveLine=()=>{},onMovePoint=()=>{}}=props;const{snapStep}=useGraphConfig();const[ariaLives,setAriaLives]=React.useState(["off","off","off"]);const point1AriaLive=ariaLive??ariaLives[0];const point2AriaLive=ariaLive??ariaLives[1];const lineAriaLive=ariaLive??ariaLives[2];const{visiblePoint:visiblePoint1,focusableHandle:focusableHandle1}=useControlPoint({ariaLabel:ariaLabels?.point1AriaLabel,ariaDescribedBy:ariaDescribedBy,ariaLive:point1AriaLive,point:start,sequenceNumber:1,onMove:p=>{setAriaLives(["polite","off","off"]);onMovePoint(0,p);},constrain:getMovableLineKeyboardConstraint([start,end],snapStep,0)});const{visiblePoint:visiblePoint2,focusableHandle:focusableHandle2}=useControlPoint({ariaLabel:ariaLabels?.point2AriaLabel,ariaDescribedBy:ariaDescribedBy,ariaLive:point2AriaLive,point:end,sequenceNumber:2,onMove:p=>{setAriaLives(["off","polite","off"]);onMovePoint(1,p);},constrain:getMovableLineKeyboardConstraint([start,end],snapStep,1)});const line=jsxRuntimeExports.jsx(Line$1,{ariaLabel:ariaLabels?.grabHandleAriaLabel,ariaDescribedBy:ariaDescribedBy,ariaLive:lineAriaLive,start:start,end:end,extend:extend,onMove:newStart=>{setAriaLives(["off","off","polite"]);onMoveLine(newStart);}});return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[focusableHandle1,line,focusableHandle2,visiblePoint1,visiblePoint2]})};
1952
1956
 
1953
1957
  const{calculateAngleInDegrees: calculateAngleInDegrees$2,getClockwiseAngle: getClockwiseAngle$1,polar: polar$1}=angles;function renderAngleGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(AngleGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getAngleGraphDescription(state,i18n)}}function AngleGraph(props){const{dispatch,graphState}=props;const{graphDimensionsInPixels,interactiveColor}=useGraphConfig();const i18n=usePerseusI18n();const id=React.useId();const descriptionId=id+"-description";const{coords,pointLabels,showAngles,range,allowReflexAngles,snapDegrees}=graphState;const buildLabel=usePointAriaLabel(pointLabels);const endPoints=[coords[0],coords[2]];const centerPoint=coords[1];const angleLines=[[centerPoint,endPoints[0]],[centerPoint,endPoints[1]]];const linePixelCoords=[useTransformVectorsToPixels(centerPoint,endPoints[0]),useTransformVectorsToPixels(centerPoint,endPoints[1])];const svgLines=linePixelCoords.map(([startPtPx,endPtPx],i)=>{const trimmedRange=trimRange(range,graphDimensionsInPixels);const endExtend=getIntersectionOfRayWithBox(angleLines[i][0],angleLines[i][1],trimmedRange);return jsxRuntimeExports.jsxs("g",{children:[jsxRuntimeExports.jsx(SVGLine,{start:startPtPx,end:endPtPx,style:{stroke:interactiveColor,strokeWidth:"var(--movable-line-stroke-weight)"},testId:"angle-graph__line"}),jsxRuntimeExports.jsx(Vector,{tail:angleLines[i][1],tip:endExtend,testId:"angle-graph__vector"})]},`line-${i}`)});const angleParams={vertex:centerPoint,coords:endPoints,allowReflexAngles:allowReflexAngles||false,snapDegrees:snapDegrees||1,range:range,showAngles:showAngles||false};const{srAngleGraphAriaLabel,srAngleGraphAriaDescription,srAngleStartingSide,srAngleEndingSide,srAngleVertex}=describeAngleGraph(graphState,i18n);return jsxRuntimeExports.jsxs("g",{"aria-label":srAngleGraphAriaLabel,"aria-describedby":descriptionId,children:[svgLines,jsxRuntimeExports.jsx(Angle,{...angleParams}),jsxRuntimeExports.jsx(MovablePoint$1,{point:coords[1],sequenceNumber:1,constrain:p=>p,onMove:destination=>dispatch(actions.angle.movePoint(1,destination)),ariaLabel:buildLabel(1,coords[1])??srAngleVertex,ariaLive:"off"}),jsxRuntimeExports.jsx(MovablePoint$1,{point:coords[0],sequenceNumber:2,constrain:getAngleSideConstraint(coords[0],coords[1],snapDegrees||1),onMove:destination=>dispatch(actions.angle.movePoint(0,destination)),ariaLabel:buildLabel(0,coords[0])??srAngleEndingSide,ariaLive:"off"}),jsxRuntimeExports.jsx(MovablePoint$1,{point:coords[2],sequenceNumber:3,constrain:getAngleSideConstraint(coords[2],coords[1],snapDegrees||1),onMove:destination=>dispatch(actions.angle.movePoint(2,destination)),ariaLabel:buildLabel(2,coords[2])??srAngleStartingSide,ariaLive:"off"}),jsxRuntimeExports.jsx(SRDescInSVG,{id:descriptionId,children:srAngleGraphAriaDescription})]})}function getAngleGraphDescription(state,i18n){const{strings,locale}=i18n;const{coords}=state;return strings.srInteractiveElements({elements:strings.srAngleInteractiveElements({vertexX:srFormatNumber(coords[1][X],locale),vertexY:srFormatNumber(coords[1][Y],locale),startingSideX:srFormatNumber(coords[2][X],locale),startingSideY:srFormatNumber(coords[2][Y],locale),endingSideX:srFormatNumber(coords[0][X],locale),endingSideY:srFormatNumber(coords[0][Y],locale)})})}function describeAngleGraph(state,i18n){const{strings,locale}=i18n;const{coords,allowReflexAngles}=state;const[endingSide,vertex,startingSide]=coords;const angleMeasure=srFormatNumber(getClockwiseAngle$1(coords,allowReflexAngles),locale);const srAngleGraphAriaLabel=strings.srAngleGraphAriaLabel;const srAngleGraphAriaDescription=strings.srAngleGraphAriaDescription({angleMeasure,vertexX:srFormatNumber(vertex[X],locale),vertexY:srFormatNumber(vertex[Y],locale),startingSideX:srFormatNumber(startingSide[X],locale),startingSideY:srFormatNumber(startingSide[Y],locale),endingSideX:srFormatNumber(endingSide[X],locale),endingSideY:srFormatNumber(endingSide[Y],locale)});const srAngleStartingSide=strings.srAngleStartingSide({x:srFormatNumber(startingSide[X],locale),y:srFormatNumber(startingSide[Y],locale)});const srAngleEndingSide=strings.srAngleEndingSide({x:srFormatNumber(endingSide[X],locale),y:srFormatNumber(endingSide[Y],locale)});const srAngleVertex=strings.srAngleVertexWithAngleMeasure({x:srFormatNumber(vertex[X],locale),y:srFormatNumber(vertex[Y],locale),angleMeasure});return {srAngleGraphAriaLabel,srAngleGraphAriaDescription,srAngleStartingSide,srAngleEndingSide,srAngleVertex}}const positiveX=[1,0];const negativeX=[-1,0];const positiveY=[0,1];const negativeY=[0,-1];function getAngleSideConstraint(sidePoint,vertex,snapDegrees){const currentAngle=calculateAngleInDegrees$2(vec.sub(sidePoint,vertex));const leftRay=[sidePoint,vec.add(sidePoint,negativeX)];const rightRay=[sidePoint,vec.add(sidePoint,positiveX)];const upRay=[sidePoint,vec.add(sidePoint,positiveY)];const downRay=[sidePoint,vec.add(sidePoint,negativeY)];const oneStepCounterClockwise=currentAngle+snapDegrees;const oneStepClockwise=currentAngle-snapDegrees;const counterClockwiseRay=[vertex,vec.add(vertex,polar$1(1,oneStepCounterClockwise))];const clockwiseRay=[vertex,vec.add(vertex,polar$1(1,oneStepClockwise))];const left=findIntersectionOfRays(leftRay,counterClockwiseRay)??findIntersectionOfRays(leftRay,clockwiseRay);const right=findIntersectionOfRays(rightRay,counterClockwiseRay)??findIntersectionOfRays(rightRay,clockwiseRay);const up=findIntersectionOfRays(upRay,counterClockwiseRay)??findIntersectionOfRays(upRay,clockwiseRay);const down=findIntersectionOfRays(downRay,counterClockwiseRay)??findIntersectionOfRays(downRay,clockwiseRay);return {up:up??sidePoint,down:down??sidePoint,left:left??sidePoint,right:right??sidePoint}}
1954
1958
 
@@ -1958,25 +1962,25 @@ function renderCircleGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.
1958
1962
 
1959
1963
  const ACTIVE_MAJOR=22;const ACTIVE_MINOR=12;const INACTIVE_MAJOR=16;const INACTIVE_MINOR=6;const RING_PAD=2;const HALO_PAD=3;const FOCUS_RING_PAD=2;const GRIP_DOT_MAJOR_OFFSETS=[-3,0,3];const GRIP_DOT_MINOR_OFFSETS=[-2,2];function MovablePillHandle(props){const{center,active,focused,rotation=0}=props;const[cx,cy]=center;const{interactiveColor}=useGraphConfig();const majorSize=active?ACTIVE_MAJOR:INACTIVE_MAJOR;const minorSize=active?ACTIVE_MINOR:INACTIVE_MINOR;const pillW=majorSize;const pillH=minorSize;const pillRadius=pillH/2;const haloW=pillW+(RING_PAD+HALO_PAD)*2;const haloH=pillH+(RING_PAD+HALO_PAD)*2;const haloRadius=haloH/2;const ringW=pillW+RING_PAD*2;const ringH=pillH+RING_PAD*2;const ringRadius=ringH/2;const focusRingW=haloW+FOCUS_RING_PAD*2;const focusRingH=haloH+FOCUS_RING_PAD*2;const focusRingRadius=focusRingH/2;return jsxRuntimeExports.jsxs("g",{"aria-hidden":true,style:{pointerEvents:"none"},transform:`translate(${cx} ${cy}) rotate(${rotation})`,"data-testid":"movable-pill-handle",children:[focused&&jsxRuntimeExports.jsx("rect",{className:"movable-pill-handle-focus-ring","data-testid":"movable-pill-handle-focus-ring",x:-focusRingW/2,y:-focusRingH/2,width:focusRingW,height:focusRingH,rx:focusRingRadius,ry:focusRingRadius,stroke:interactiveColor}),jsxRuntimeExports.jsx("rect",{className:"movable-pill-handle-halo",x:-haloW/2,y:-haloH/2,width:haloW,height:haloH,rx:haloRadius,ry:haloRadius,fill:interactiveColor}),jsxRuntimeExports.jsx("rect",{className:"movable-pill-handle-ring",x:-ringW/2,y:-ringH/2,width:ringW,height:ringH,rx:ringRadius,ry:ringRadius}),jsxRuntimeExports.jsx("rect",{className:"movable-pill-handle","data-testid":"movable-pill-handle-pill",x:-pillW/2,y:-pillH/2,width:pillW,height:pillH,rx:pillRadius,ry:pillRadius,fill:interactiveColor}),active&&GRIP_DOT_MINOR_OFFSETS.map(dy=>GRIP_DOT_MAJOR_OFFSETS.map(dx=>jsxRuntimeExports.jsx("circle",{className:"movable-pill-handle-dot","data-testid":"movable-pill-handle-dot",cx:dx,cy:dy},`${dx},${dy}`)))]})}
1960
1964
 
1961
- function MovableAsymptote(props){const{start,end,mid,point,onMove,constrainKeyboardMovement,orientation,ariaLabel,children}=props;const{interactiveColor,disableKeyboardInteraction}=useGraphConfig();const[focused,setFocused]=React.useState(false);const[hovered,setHovered]=React.useState(false);const groupRef=React.useRef(null);const{dragging}=useDraggable({gestureTarget:groupRef,point,onMove,constrainKeyboardMovement:constrainKeyboardMovement??(p=>p)});React.useLayoutEffect(()=>{if(dragging&&!focused){groupRef.current?.focus();}},[dragging,focused]);return jsxRuntimeExports.jsxs("g",{ref:groupRef,tabIndex:disableKeyboardInteraction?-1:0,"aria-disabled":disableKeyboardInteraction,"aria-label":ariaLabel,"aria-live":"polite",className:"movable-line",style:{cursor:dragging?"grabbing":"grab"},role:"button","data-testid":"movable-asymptote",onFocus:()=>setFocused(true),onBlur:()=>setFocused(false),onMouseEnter:()=>setHovered(true),onMouseLeave:()=>setHovered(false),children:[jsxRuntimeExports.jsx(SVGLine,{start:start,end:end,style:{stroke:"transparent",strokeWidth:TARGET_SIZE}}),jsxRuntimeExports.jsx(SVGLine,{start:start,end:end,style:{stroke:semanticColor.core.background.base.default,strokeWidth:"var(--movable-asymptote-stroke-weight)",strokeLinecap:"round"},className:dragging?"movable-dragging":""}),jsxRuntimeExports.jsx(SVGLine,{start:start,end:end,style:{stroke:interactiveColor,strokeWidth:"var(--movable-asymptote-stroke-weight)",strokeDasharray:"var(--movable-asymptote-dash-length) var(--movable-asymptote-dash-gap)",strokeLinecap:"round"},className:dragging?"movable-dragging":"",testId:"movable-asymptote__line"}),children,jsxRuntimeExports.jsx(MovablePillHandle,{center:mid,rotation:orientation==="vertical"?90:0,active:dragging||focused||hovered,focused:focused})]})}
1965
+ function MovableAsymptote(props){const{start,end,mid,point,onMove,constrainKeyboardMovement,orientation,ariaLabel,ariaLive,children}=props;const{interactiveColor,disableKeyboardInteraction}=useGraphConfig();const[focused,setFocused]=React.useState(false);const[hovered,setHovered]=React.useState(false);const groupRef=React.useRef(null);const{dragging}=useDraggable({gestureTarget:groupRef,point,onMove,constrainKeyboardMovement:constrainKeyboardMovement??(p=>p)});React.useLayoutEffect(()=>{if(dragging&&!focused){groupRef.current?.focus();}},[dragging,focused]);return jsxRuntimeExports.jsxs("g",{ref:groupRef,tabIndex:disableKeyboardInteraction?-1:0,"aria-disabled":disableKeyboardInteraction,"aria-label":ariaLabel,"aria-live":ariaLive??"polite",className:"movable-line",style:{cursor:dragging?"grabbing":"grab"},role:"button","data-testid":"movable-asymptote",onFocus:()=>setFocused(true),onBlur:()=>setFocused(false),onMouseEnter:()=>setHovered(true),onMouseLeave:()=>setHovered(false),children:[jsxRuntimeExports.jsx(SVGLine,{start:start,end:end,style:{stroke:"transparent",strokeWidth:TARGET_SIZE}}),jsxRuntimeExports.jsx(SVGLine,{start:start,end:end,style:{stroke:semanticColor.core.background.base.default,strokeWidth:"var(--movable-asymptote-stroke-weight)",strokeLinecap:"round"},className:dragging?"movable-dragging":""}),jsxRuntimeExports.jsx(SVGLine,{start:start,end:end,style:{stroke:interactiveColor,strokeWidth:"var(--movable-asymptote-stroke-weight)",strokeDasharray:"var(--movable-asymptote-dash-length) var(--movable-asymptote-dash-gap)",strokeLinecap:"round"},className:dragging?"movable-dragging":"",testId:"movable-asymptote__line"}),children,jsxRuntimeExports.jsx(MovablePillHandle,{center:mid,rotation:orientation==="vertical"?90:0,active:dragging||focused||hovered,focused:focused})]})}
1962
1966
 
1963
- const{getExponentialCoefficients}=coefficients;function renderExponentialGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(ExponentialGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getExponentialDescription(state,i18n)}}function ExponentialGraph(props){const{dispatch,graphState}=props;const{interactiveColor,range}=useGraphConfig();const i18n=usePerseusI18n();const id=React.useId();const descriptionId=id+"-description";const{coords,pointLabels,asymptote,snapStep}=graphState;const buildLabel=usePointAriaLabel(pointLabels);const coeffs=getExponentialCoefficients(coords,asymptote);const asymptoteY=asymptote;const yMin=range[1][0];const yMax=range[1][1];const yPadding=(yMax-yMin)*4;const{srExponentialGraph,srExponentialDescription,srExponentialPoint1,srExponentialPoint2,srExponentialAsymptote}=describeExponentialGraph(graphState,i18n);const asymptoteLeft=[range[0][0],asymptoteY];const asymptoteRight=[range[0][1],asymptoteY];const handleCoord=getAsymptoteHandleCoord("horizontal",range,asymptote);const[leftPx,rightPx,midPx]=useTransformVectorsToPixels(asymptoteLeft,asymptoteRight,handleCoord);return jsxRuntimeExports.jsxs("g",{"aria-label":srExponentialGraph,"aria-describedby":descriptionId,children:[jsxRuntimeExports.jsx(MovableAsymptote,{start:leftPx,end:rightPx,mid:midPx,point:handleCoord,onMove:newPoint=>dispatch(actions.exponential.moveCenter(newPoint)),constrainKeyboardMovement:p=>skipAsymptoteKeyboardOverPoint(p,asymptote,coords,handleCoord,snapStep,"horizontal"),orientation:"horizontal",ariaLabel:srExponentialAsymptote,children:coeffs!==undefined&&jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(Plot$2.OfX,{y:x=>{const y=computeExponential(x,coeffs);if(y<yMin-yPadding||y>yMax+yPadding){return NaN}return y},color:interactiveColor,svgPathProps:{"aria-hidden":true,style:{pointerEvents:"none"}}})})}),coords.map((coord,i)=>jsxRuntimeExports.jsx(MovablePoint$1,{ariaLabel:buildLabel(i,coord)??(i===0?srExponentialPoint1:srExponentialPoint2),point:coord,sequenceNumber:i+1,constrain:getExponentialKeyboardConstraint(coords,asymptote,snapStep,i,range),onMove:destination=>dispatch(actions.exponential.movePoint(i,destination))},"point-"+i)),jsxRuntimeExports.jsx(SRDescInSVG,{id:descriptionId,children:srExponentialDescription})]})}const getExponentialKeyboardConstraint=(coords,asymptote,snapStep,pointIndex,range)=>{const otherPoint=coords[1-pointIndex];const handleCoord=getAsymptoteHandleCoord("horizontal",range,asymptote);return getAsymptoteGraphKeyboardConstraint(coords,snapStep,pointIndex,coord=>{const clamped=boundToEdgeAndSnapToGrid(coord,{snapStep,range});const clampedX=clamped[X];const clampedY=clamped[Y];if(coord[X]===otherPoint[X]||clampedX===otherPoint[X]){return false}if(clampedX===handleCoord[X]&&clampedY===handleCoord[Y]){return false}return true})};const computeExponential=function(x,coefficients){const{a,b,c}=coefficients;return a*Math.exp(b*x)+c};function getExponentialDescription(state,i18n){const strings=describeExponentialGraph(state,i18n);return strings.srExponentialInteractiveElements}function describeExponentialGraph(state,i18n){const{strings,locale}=i18n;const{coords,asymptote}=state;const[point1,point2]=coords;const formattedPoint1={x:srFormatNumber(point1[X],locale),y:srFormatNumber(point1[Y],locale)};const formattedPoint2={x:srFormatNumber(point2[X],locale),y:srFormatNumber(point2[Y],locale)};const asymptoteYFormatted=srFormatNumber(asymptote,locale);return {srExponentialGraph:strings.srExponentialGraph,srExponentialDescription:strings.srExponentialDescription({point1X:formattedPoint1.x,point1Y:formattedPoint1.y,point2X:formattedPoint2.x,point2Y:formattedPoint2.y,asymptoteY:asymptoteYFormatted}),srExponentialAsymptote:strings.srExponentialAsymptote({asymptoteY:asymptoteYFormatted}),srExponentialPoint1:strings.srExponentialPoint1(formattedPoint1),srExponentialPoint2:strings.srExponentialPoint2(formattedPoint2),srExponentialInteractiveElements:strings.srInteractiveElements({elements:strings.srExponentialInteractiveElements({point1X:srFormatNumber(point1[X],locale),point1Y:srFormatNumber(point1[Y],locale),point2X:srFormatNumber(point2[X],locale),point2Y:srFormatNumber(point2[Y],locale),asymptoteY:asymptoteYFormatted})})}}
1967
+ const{getExponentialCoefficients}=coefficients;function renderExponentialGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(ExponentialGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getExponentialDescription(state,i18n)}}function ExponentialGraph(props){const{dispatch,graphState}=props;const{interactiveColor,range}=useGraphConfig();const i18n=usePerseusI18n();const id=React.useId();const descriptionId=id+"-description";const{coords,pointLabels,asymptote,snapStep}=graphState;const buildLabel=usePointAriaLabel(pointLabels);const coeffs=getExponentialCoefficients(coords,asymptote);const asymptoteY=asymptote;const yMin=range[1][0];const yMax=range[1][1];const yPadding=(yMax-yMin)*4;const{srExponentialGraph,srExponentialDescription,srExponentialPoint1,srExponentialPoint2,srExponentialAsymptote}=describeExponentialGraph(graphState,i18n);const asymptoteLeft=[range[0][0],asymptoteY];const asymptoteRight=[range[0][1],asymptoteY];const handleCoord=getAsymptoteHandleCoord("horizontal",range,asymptote);const[leftPx,rightPx,midPx]=useTransformVectorsToPixels(asymptoteLeft,asymptoteRight,handleCoord);return jsxRuntimeExports.jsxs("g",{"aria-label":srExponentialGraph,"aria-describedby":descriptionId,children:[jsxRuntimeExports.jsx(MovableAsymptote,{start:leftPx,end:rightPx,mid:midPx,point:handleCoord,onMove:newPoint=>dispatch(actions.exponential.moveCenter(newPoint)),constrainKeyboardMovement:p=>skipAsymptoteKeyboardOverPoint(p,asymptote,coords,handleCoord,snapStep,"horizontal"),orientation:"horizontal",ariaLabel:srExponentialAsymptote,ariaLive:"off",children:coeffs!==undefined&&jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(Plot$2.OfX,{y:x=>{const y=computeExponential(x,coeffs);if(y<yMin-yPadding||y>yMax+yPadding){return NaN}return y},color:interactiveColor,svgPathProps:{"aria-hidden":true,style:{pointerEvents:"none"}}})})}),coords.map((coord,i)=>jsxRuntimeExports.jsx(MovablePoint$1,{ariaLabel:buildLabel(i,coord)??(i===0?srExponentialPoint1:srExponentialPoint2),point:coord,sequenceNumber:i+1,constrain:getExponentialKeyboardConstraint(coords,asymptote,snapStep,i,range),onMove:destination=>dispatch(actions.exponential.movePoint(i,destination)),ariaLive:"off"},"point-"+i)),jsxRuntimeExports.jsx(SRDescInSVG,{id:descriptionId,children:srExponentialDescription})]})}const getExponentialKeyboardConstraint=(coords,asymptote,snapStep,pointIndex,range)=>{const otherPoint=coords[1-pointIndex];const handleCoord=getAsymptoteHandleCoord("horizontal",range,asymptote);return getAsymptoteGraphKeyboardConstraint(coords,snapStep,pointIndex,coord=>{const clamped=boundToEdgeAndSnapToGrid(coord,{snapStep,range});const clampedX=clamped[X];const clampedY=clamped[Y];if(coord[X]===otherPoint[X]||clampedX===otherPoint[X]){return false}if(clampedX===handleCoord[X]&&clampedY===handleCoord[Y]){return false}return true})};const computeExponential=function(x,coefficients){const{a,b,c}=coefficients;return a*Math.exp(b*x)+c};function getExponentialDescription(state,i18n){const strings=describeExponentialGraph(state,i18n);return strings.srExponentialInteractiveElements}function describeExponentialGraph(state,i18n){const{strings,locale}=i18n;const{coords,asymptote}=state;const[point1,point2]=coords;const formattedPoint1={x:srFormatNumber(point1[X],locale),y:srFormatNumber(point1[Y],locale)};const formattedPoint2={x:srFormatNumber(point2[X],locale),y:srFormatNumber(point2[Y],locale)};const asymptoteYFormatted=srFormatNumber(asymptote,locale);return {srExponentialGraph:strings.srExponentialGraph,srExponentialDescription:strings.srExponentialDescription({point1X:formattedPoint1.x,point1Y:formattedPoint1.y,point2X:formattedPoint2.x,point2Y:formattedPoint2.y,asymptoteY:asymptoteYFormatted}),srExponentialAsymptote:strings.srExponentialAsymptote({asymptoteY:asymptoteYFormatted}),srExponentialPoint1:strings.srExponentialPoint1(formattedPoint1),srExponentialPoint2:strings.srExponentialPoint2(formattedPoint2),srExponentialInteractiveElements:strings.srInteractiveElements({elements:strings.srExponentialInteractiveElements({point1X:srFormatNumber(point1[X],locale),point1Y:srFormatNumber(point1[Y],locale),point2X:srFormatNumber(point2[X],locale),point2Y:srFormatNumber(point2[Y],locale),asymptoteY:asymptoteYFormatted})})}}
1964
1968
 
1965
1969
  function renderLinearGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(LinearGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getLinearGraphDescription(state,i18n)}}const LinearGraph=(props,key)=>{const{dispatch}=props;const{coords:line,pointLabels}=props.graphState;const{strings,locale}=usePerseusI18n();const buildLabel=usePointAriaLabel(pointLabels);const id=React.useId();const pointsDescriptionId=id+"-points";const interceptDescriptionId=id+"-intercept";const slopeDescriptionId=id+"-slope";const{srLinearGraph,srLinearGraphPoints,srLinearGrabHandle,slopeString,interceptString}=describeLinearGraph(props.graphState,{strings,locale});return jsxRuntimeExports.jsxs("g",{"aria-label":srLinearGraph,"aria-describedby":`${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`,children:[jsxRuntimeExports.jsx(MovableLine,{ariaLabels:{grabHandleAriaLabel:srLinearGrabHandle,point1AriaLabel:buildLabel(0,line[0]),point2AriaLabel:buildLabel(1,line[1])},ariaDescribedBy:`${interceptDescriptionId} ${slopeDescriptionId}`,ariaLive:"off",points:line,onMoveLine:newStart=>{dispatch(actions.linear.moveLine(newStart));},extend:{start:true,end:true},onMovePoint:(endpointIndex,destination)=>dispatch(actions.linear.movePoint(endpointIndex,destination))},0),jsxRuntimeExports.jsx(SRDescInSVG,{id:pointsDescriptionId,children:srLinearGraphPoints}),jsxRuntimeExports.jsx(SRDescInSVG,{id:interceptDescriptionId,children:interceptString}),jsxRuntimeExports.jsx(SRDescInSVG,{id:slopeDescriptionId,children:slopeString})]})};function getLinearGraphDescription(state,i18n){const strings=describeLinearGraph(state,i18n);return strings.srLinearInteractiveElement}function describeLinearGraph(state,i18n){const{coords:line}=state;const{strings,locale}=i18n;const srLinearGraph=strings.srLinearGraph;const srLinearGraphPoints=strings.srLinearGraphPoints({point1X:srFormatNumber(line[0][0],locale),point1Y:srFormatNumber(line[0][1],locale),point2X:srFormatNumber(line[1][0],locale),point2Y:srFormatNumber(line[1][1],locale)});const srLinearGrabHandle=strings.srLinearGrabHandle({point1X:srFormatNumber(line[0][0],locale),point1Y:srFormatNumber(line[0][1],locale),point2X:srFormatNumber(line[1][0],locale),point2Y:srFormatNumber(line[1][1],locale)});const slopeString=getSlopeStringForLine(line,strings);const interceptString=getInterceptStringForLine(line,strings,locale);const srLinearInteractiveElement=strings.srInteractiveElements({elements:[srLinearGraph,srLinearGraphPoints].join(" ")});return {srLinearGraph,srLinearGraphPoints,srLinearGrabHandle,slopeString,interceptString,srLinearInteractiveElement}}
1966
1970
 
1967
1971
  function renderLinearSystemGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(LinearSystemGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getLinearSystemGraphDescription(state,i18n)}}const LinearSystemGraph=props=>{const{dispatch}=props;const{coords:lines,pointLabels}=props.graphState;const{strings,locale}=usePerseusI18n();const id=React.useId();const intersectionId=`${id}-intersection`;const buildLabel=usePointAriaLabel(pointLabels);const intersectionPoint=geometry.getLineIntersection(lines[0],lines[1]);const intersectionDescription=intersectionPoint?strings.srLinearSystemIntersection({x:srFormatNumber(intersectionPoint[0],locale),y:srFormatNumber(intersectionPoint[1],locale)}):strings.srLinearSystemParallel;const linesAriaInfo=lines.map((line,i)=>{return {pointsDescriptionId:`${id}-line${i+1}-points`,interceptDescriptionId:`${id}-line${i+1}-intercept`,slopeDescriptionId:`${id}-line${i+1}-slope`,pointsDescription:strings.srLinearSystemPoints({lineNumber:i+1,point1X:srFormatNumber(line[0][0],locale),point1Y:srFormatNumber(line[0][1],locale),point2X:srFormatNumber(line[1][0],locale),point2Y:srFormatNumber(line[1][1],locale)}),interceptDescription:getInterceptStringForLine(line,strings,locale),slopeDescription:getSlopeStringForLine(line,strings)}});const individualLineDescriptions=linesAriaInfo.map(({pointsDescriptionId,interceptDescriptionId,slopeDescriptionId})=>`${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`).join(" ");return jsxRuntimeExports.jsxs("g",{"aria-label":strings.srLinearSystemGraph,"aria-describedby":`${individualLineDescriptions} ${intersectionId}`,children:[lines?.map((line,i)=>jsxRuntimeExports.jsx(MovableLine,{points:line,ariaLive:"off",ariaLabels:{point1AriaLabel:buildLabel(i*2,line[0])??strings.srLinearSystemPoint({lineNumber:i+1,pointSequence:1,x:srFormatNumber(line[0][0],locale),y:srFormatNumber(line[0][1],locale)}),point2AriaLabel:buildLabel(i*2+1,line[1])??strings.srLinearSystemPoint({lineNumber:i+1,pointSequence:2,x:srFormatNumber(line[1][0],locale),y:srFormatNumber(line[1][1],locale)}),grabHandleAriaLabel:strings.srLinearSystemGrabHandle({lineNumber:i+1,point1X:srFormatNumber(line[0][0],locale),point1Y:srFormatNumber(line[0][1],locale),point2X:srFormatNumber(line[1][0],locale),point2Y:srFormatNumber(line[1][1],locale)})},ariaDescribedBy:`${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId} ${intersectionId}`,onMoveLine:newStart=>{dispatch(actions.linearSystem.moveLine(i,newStart));},extend:{start:true,end:true},onMovePoint:(endpointIndex,destination)=>dispatch(actions.linearSystem.movePointInFigure(i,endpointIndex,destination))},i)),linesAriaInfo.map(({pointsDescriptionId,interceptDescriptionId,slopeDescriptionId,pointsDescription,interceptDescription,slopeDescription},i)=>jsxRuntimeExports.jsxs("span",{children:[jsxRuntimeExports.jsx(SRDescInSVG,{id:pointsDescriptionId,children:pointsDescription}),jsxRuntimeExports.jsx(SRDescInSVG,{id:interceptDescriptionId,children:interceptDescription}),jsxRuntimeExports.jsx(SRDescInSVG,{id:slopeDescriptionId,children:slopeDescription})]},`line-descriptions-${i}`)),jsxRuntimeExports.jsx(SRDescInSVG,{id:intersectionId,children:intersectionDescription})]})};function getLinearSystemGraphDescription(state,i18n){const{strings,locale}=i18n;const{coords:lines}=state;const graphDescription=strings.srLinearSystemGraph;const lineDescriptions=lines.map((line,i)=>{const point1=line[0];const point2=line[1];return strings.srLinearSystemPoints({lineNumber:i+1,point1X:srFormatNumber(point1[0],locale),point1Y:srFormatNumber(point1[1],locale),point2X:srFormatNumber(point2[0],locale),point2Y:srFormatNumber(point2[1],locale)})});const allDescriptions=[graphDescription,...lineDescriptions];return strings.srInteractiveElements({elements:allDescriptions.join(" ")})}
1968
1972
 
1969
- const{getLogarithmCoefficients}=coefficients;function renderLogarithmGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(LogarithmGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getLogarithmDescription(state,i18n)}}function LogarithmGraph(props){const{dispatch,graphState}=props;const{interactiveColor,range}=useGraphConfig();const i18n=usePerseusI18n();const id=React.useId();const descriptionId=id+"-description";const{coords,pointLabels,asymptote,snapStep}=graphState;const buildLabel=usePointAriaLabel(pointLabels);const coeffs=getLogarithmCoefficients(coords,asymptote);const asymptoteX=asymptote;const xMin=range[0][0];const xMax=range[0][1];const yMin=range[1][0];const yMax=range[1][1];const{srLogarithmGraph,srLogarithmDescription,srLogarithmPoint1,srLogarithmPoint2,srLogarithmAsymptote}=describeLogarithmGraph(graphState,i18n);const asymptoteBottom=[asymptoteX,yMin];const asymptoteTop=[asymptoteX,yMax];const handleCoord=getAsymptoteHandleCoord("vertical",range,asymptote);const[bottomPx,topPx,midPx]=useTransformVectorsToPixels(asymptoteBottom,asymptoteTop,handleCoord);return jsxRuntimeExports.jsxs("g",{"aria-label":srLogarithmGraph,"aria-describedby":descriptionId,children:[jsxRuntimeExports.jsx(MovableAsymptote,{start:bottomPx,end:topPx,mid:midPx,point:handleCoord,onMove:newPoint=>dispatch(actions.logarithm.moveCenter(newPoint)),constrainKeyboardMovement:p=>skipAsymptoteKeyboardOverPoint(p,asymptote,coords,handleCoord,snapStep,"vertical"),orientation:"vertical",ariaLabel:srLogarithmAsymptote,children:coeffs!==undefined&&renderLogarithmCurve({coeffs,coords,asymptoteX,xMin,xMax,yMin,yMax,interactiveColor})}),coords.map((coord,i)=>jsxRuntimeExports.jsx(MovablePoint$1,{ariaLabel:buildLabel(i,coord)??(i===0?srLogarithmPoint1:srLogarithmPoint2),point:coord,sequenceNumber:i+1,constrain:getLogarithmKeyboardConstraint(coords,asymptote,snapStep,i,range),onMove:destination=>dispatch(actions.logarithm.movePoint(i,destination))},"point-"+i)),jsxRuntimeExports.jsx(SRDescInSVG,{id:descriptionId,children:srLogarithmDescription})]})}const getLogarithmKeyboardConstraint=(coords,asymptote,snapStep,pointIndex,range)=>{const otherPoint=coords[1-pointIndex];const handleCoord=getAsymptoteHandleCoord("vertical",range,asymptote);return getAsymptoteGraphKeyboardConstraint(coords,snapStep,pointIndex,coord=>{const clamped=boundToEdgeAndSnapToGrid(coord,{snapStep,range});const clampedX=clamped[X];const clampedY=clamped[Y];if(coord[X]===otherPoint[X]||clampedX===otherPoint[X]){return false}if(coord[Y]===otherPoint[Y]||clampedY===otherPoint[Y]){return false}if(clampedX===handleCoord[X]&&clampedY===handleCoord[Y]){return false}return true})};const computeLogarithm=function(coefficients,x){const{a,b,c}=coefficients;const arg=b*x+c;if(arg<=0){return NaN}return a*Math.log(arg)};function renderLogarithmCurve({coeffs,coords,asymptoteX,xMin,xMax,yMin,yMax,interactiveColor}){const offScreenMargin=2;const{a,b,c}=coeffs;const targetY=a>0?yMin-offScreenMargin:yMax+offScreenMargin;const computedX=(Math.exp(targetY/a)-c)/b;const computedOffset=Math.abs(computedX-asymptoteX);const domainOffset=isFinite(computedOffset)&&computedOffset>0?computedOffset:1e-8;const pointsRightOfAsymptote=coords[0][X]>asymptoteX;return jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(Plot$2.OfX,{y:x=>computeLogarithm(coeffs,x),color:interactiveColor,svgPathProps:{"aria-hidden":true,style:{pointerEvents:"none"}},domain:pointsRightOfAsymptote?[asymptoteX+domainOffset,xMax]:[xMin,asymptoteX-domainOffset]})})}function getLogarithmDescription(state,i18n){const strings=describeLogarithmGraph(state,i18n);return strings.srLogarithmInteractiveElements}function describeLogarithmGraph(state,i18n){const{strings,locale}=i18n;const{coords,asymptote}=state;const[point1,point2]=coords;const formattedPoint1={x:srFormatNumber(point1[X],locale),y:srFormatNumber(point1[Y],locale)};const formattedPoint2={x:srFormatNumber(point2[X],locale),y:srFormatNumber(point2[Y],locale)};const asymptoteXFormatted=srFormatNumber(asymptote,locale);return {srLogarithmGraph:strings.srLogarithmGraph,srLogarithmDescription:strings.srLogarithmDescription({point1X:formattedPoint1.x,point1Y:formattedPoint1.y,point2X:formattedPoint2.x,point2Y:formattedPoint2.y,asymptoteX:asymptoteXFormatted}),srLogarithmAsymptote:strings.srLogarithmAsymptote({asymptoteX:asymptoteXFormatted}),srLogarithmPoint1:strings.srLogarithmPoint1(formattedPoint1),srLogarithmPoint2:strings.srLogarithmPoint2(formattedPoint2),srLogarithmInteractiveElements:strings.srInteractiveElements({elements:strings.srLogarithmInteractiveElements({point1X:srFormatNumber(point1[X],locale),point1Y:srFormatNumber(point1[Y],locale),point2X:srFormatNumber(point2[X],locale),point2Y:srFormatNumber(point2[Y],locale),asymptoteX:asymptoteXFormatted})})}}
1973
+ const{getLogarithmCoefficients}=coefficients;function renderLogarithmGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(LogarithmGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getLogarithmDescription(state,i18n)}}function LogarithmGraph(props){const{dispatch,graphState}=props;const{interactiveColor,range}=useGraphConfig();const i18n=usePerseusI18n();const id=React.useId();const descriptionId=id+"-description";const{coords,pointLabels,asymptote,snapStep}=graphState;const buildLabel=usePointAriaLabel(pointLabels);const coeffs=getLogarithmCoefficients(coords,asymptote);const asymptoteX=asymptote;const xMin=range[0][0];const xMax=range[0][1];const yMin=range[1][0];const yMax=range[1][1];const{srLogarithmGraph,srLogarithmDescription,srLogarithmPoint1,srLogarithmPoint2,srLogarithmAsymptote}=describeLogarithmGraph(graphState,i18n);const asymptoteBottom=[asymptoteX,yMin];const asymptoteTop=[asymptoteX,yMax];const handleCoord=getAsymptoteHandleCoord("vertical",range,asymptote);const[bottomPx,topPx,midPx]=useTransformVectorsToPixels(asymptoteBottom,asymptoteTop,handleCoord);return jsxRuntimeExports.jsxs("g",{"aria-label":srLogarithmGraph,"aria-describedby":descriptionId,children:[jsxRuntimeExports.jsx(MovableAsymptote,{start:bottomPx,end:topPx,mid:midPx,point:handleCoord,onMove:newPoint=>dispatch(actions.logarithm.moveCenter(newPoint)),constrainKeyboardMovement:p=>skipAsymptoteKeyboardOverPoint(p,asymptote,coords,handleCoord,snapStep,"vertical"),orientation:"vertical",ariaLabel:srLogarithmAsymptote,ariaLive:"off",children:coeffs!==undefined&&renderLogarithmCurve({coeffs,coords,asymptoteX,xMin,xMax,yMin,yMax,interactiveColor})}),coords.map((coord,i)=>jsxRuntimeExports.jsx(MovablePoint$1,{ariaLabel:buildLabel(i,coord)??(i===0?srLogarithmPoint1:srLogarithmPoint2),point:coord,sequenceNumber:i+1,constrain:getLogarithmKeyboardConstraint(coords,asymptote,snapStep,i,range),onMove:destination=>dispatch(actions.logarithm.movePoint(i,destination)),ariaLive:"off"},"point-"+i)),jsxRuntimeExports.jsx(SRDescInSVG,{id:descriptionId,children:srLogarithmDescription})]})}const getLogarithmKeyboardConstraint=(coords,asymptote,snapStep,pointIndex,range)=>{const otherPoint=coords[1-pointIndex];const handleCoord=getAsymptoteHandleCoord("vertical",range,asymptote);return getAsymptoteGraphKeyboardConstraint(coords,snapStep,pointIndex,coord=>{const clamped=boundToEdgeAndSnapToGrid(coord,{snapStep,range});const clampedX=clamped[X];const clampedY=clamped[Y];if(coord[X]===otherPoint[X]||clampedX===otherPoint[X]){return false}if(coord[Y]===otherPoint[Y]||clampedY===otherPoint[Y]){return false}if(clampedX===handleCoord[X]&&clampedY===handleCoord[Y]){return false}return true})};const computeLogarithm=function(coefficients,x){const{a,b,c}=coefficients;const arg=b*x+c;if(arg<=0){return NaN}return a*Math.log(arg)};function renderLogarithmCurve({coeffs,coords,asymptoteX,xMin,xMax,yMin,yMax,interactiveColor}){const offScreenMargin=2;const{a,b,c}=coeffs;const targetY=a>0?yMin-offScreenMargin:yMax+offScreenMargin;const computedX=(Math.exp(targetY/a)-c)/b;const computedOffset=Math.abs(computedX-asymptoteX);const domainOffset=isFinite(computedOffset)&&computedOffset>0?computedOffset:1e-8;const pointsRightOfAsymptote=coords[0][X]>asymptoteX;return jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(Plot$2.OfX,{y:x=>computeLogarithm(coeffs,x),color:interactiveColor,svgPathProps:{"aria-hidden":true,style:{pointerEvents:"none"}},domain:pointsRightOfAsymptote?[asymptoteX+domainOffset,xMax]:[xMin,asymptoteX-domainOffset]})})}function getLogarithmDescription(state,i18n){const strings=describeLogarithmGraph(state,i18n);return strings.srLogarithmInteractiveElements}function describeLogarithmGraph(state,i18n){const{strings,locale}=i18n;const{coords,asymptote}=state;const[point1,point2]=coords;const formattedPoint1={x:srFormatNumber(point1[X],locale),y:srFormatNumber(point1[Y],locale)};const formattedPoint2={x:srFormatNumber(point2[X],locale),y:srFormatNumber(point2[Y],locale)};const asymptoteXFormatted=srFormatNumber(asymptote,locale);return {srLogarithmGraph:strings.srLogarithmGraph,srLogarithmDescription:strings.srLogarithmDescription({point1X:formattedPoint1.x,point1Y:formattedPoint1.y,point2X:formattedPoint2.x,point2Y:formattedPoint2.y,asymptoteX:asymptoteXFormatted}),srLogarithmAsymptote:strings.srLogarithmAsymptote({asymptoteX:asymptoteXFormatted}),srLogarithmPoint1:strings.srLogarithmPoint1(formattedPoint1),srLogarithmPoint2:strings.srLogarithmPoint2(formattedPoint2),srLogarithmInteractiveElements:strings.srInteractiveElements({elements:strings.srLogarithmInteractiveElements({point1X:srFormatNumber(point1[X],locale),point1Y:srFormatNumber(point1[Y],locale),point2X:srFormatNumber(point2[X],locale),point2Y:srFormatNumber(point2[Y],locale),asymptoteX:asymptoteXFormatted})})}}
1970
1974
 
1971
1975
  function renderPointGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(PointGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getPointGraphDescription(state,i18n)}}function PointGraph(props){const{numPoints}=props.graphState;const graphConfig=useGraphConfig();const pointsRef=React.useRef([]);const{range:[x,y]}=graphConfig;const[[left,top]]=useTransformVectorsToPixels([x[0],y[1]]);React.useEffect(()=>{const focusedIndex=props.graphState.focusedPointIndex;if(focusedIndex!=null){pointsRef.current[focusedIndex]?.focus();}},[props.graphState.focusedPointIndex,props.graphState.coords.length,pointsRef]);const statefulProps={...props,graphConfig,pointsRef,top,left};if(numPoints==="unlimited"){return UnlimitedPointGraph(statefulProps)}return LimitedPointGraph(statefulProps)}function LimitedPointGraph(statefulProps){const{dispatch}=statefulProps;const{pointLabels}=statefulProps.graphState;const buildLabel=usePointAriaLabel(pointLabels);return jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment,{children:statefulProps.graphState.coords.map((point,i)=>jsxRuntimeExports.jsx(MovablePoint$1,{point:point,sequenceNumber:i+1,ariaLive:"off",ariaLabel:buildLabel(i,point),onMove:destination=>dispatch(actions.pointGraph.movePoint(i,destination))},i))})}function UnlimitedPointGraph(statefulProps){const{dispatch,graphConfig,pointsRef,top,left}=statefulProps;const{coords,pointLabels}=statefulProps.graphState;const buildLabel=usePointAriaLabel(pointLabels);const[isCurrentlyDragging,setIsCurrentlyDragging]=React.useState(false);const dragEndCallbackTimer=useTimeout(()=>setIsCurrentlyDragging(false),400);const{graphDimensionsInPixels}=graphConfig;const widthPx=graphDimensionsInPixels[0];const heightPx=graphDimensionsInPixels[1];return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx("rect",{style:{fill:"rgba(0,0,0,0)",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.pointGraph.addPoint(graphCoordinates[0]));}}),coords.map((point,i)=>jsxRuntimeExports.jsx(MovablePoint$1,{point:point,sequenceNumber:i+1,ariaLive:"off",ariaLabel:buildLabel(i,point),onDragStart:()=>{dragEndCallbackTimer.clear();setIsCurrentlyDragging(true);},onMove:destination=>{dispatch(actions.pointGraph.movePoint(i,destination));},onDragEnd:()=>{dragEndCallbackTimer.set();},ref:ref=>{pointsRef.current[i]=ref;},onFocus:()=>{dispatch(actions.pointGraph.focusPoint(i));},onClick:()=>{dispatch(actions.pointGraph.clickPoint(i));}},i))]})}function getPointGraphDescription(state,i18n){const{strings,locale}=i18n;if(state.coords.length===0){return strings.srNoInteractiveElements}const pointDescriptions=state.coords.map((point,index)=>buildPointAriaLabel(state.pointLabels,index,point,strings,locale)??strings.srPointAtCoordinates({num:index+1,x:srFormatNumber(point[0],locale),y:srFormatNumber(point[1],locale)}));return strings.srInteractiveElements({elements:pointDescriptions.join(" ")})}
1972
1976
 
1973
- const{magnitude: magnitude$1,vector: vector$1}=geometry;function initializeGraphState(params){const{graph,step,snapStep,range}=params;const shared={hasBeenInteractedWith:false,range,snapStep,pointLabels:"pointLabels"in graph?graph.pointLabels:undefined};switch(graph.type){case "segment":return {...shared,type:"segment",coords:getSegmentCoords(graph,range,step)};case "linear":return {...shared,type:graph.type,coords:getLineCoords(graph,range,step)};case "ray":return {...shared,type:graph.type,coords:getLineCoords(graph,range,step)};case "linear-system":return {...shared,type:graph.type,coords:getLinearSystemCoords(graph,range,step)};case "polygon":return {...shared,type:"polygon",numSides:graph.numSides||0,showAngles:Boolean(graph.showAngles),showSides:Boolean(graph.showSides),coords:getPolygonCoords(graph,range,step),snapTo:graph.snapTo??"grid",focusedPointIndex:null,showRemovePointButton:false,interactionMode:"mouse",showKeyboardInteractionInvitation:false,closedPolygon:false};case "point":return {...shared,type:graph.type,coords:getPointCoords(graph,range,step),numPoints:graph.numPoints||0,focusedPointIndex:null,showRemovePointButton:false,interactionMode:"mouse",showKeyboardInteractionInvitation:false};case "circle":return {...shared,type:graph.type,...getCircleCoords(graph)};case "quadratic":return {...shared,type:graph.type,coords:getQuadraticCoords(graph,range,step)};case "sinusoid":return {...shared,type:graph.type,coords:getSinusoidCoords(graph,range,step)};case "exponential":return {...shared,type:graph.type,...getExponentialCoords(graph,range,step)};case "angle":return {...shared,type:graph.type,showAngles:Boolean(graph.showAngles),coords:getAngleCoords({graph,range,step}),angleOffsetDeg:graph.angleOffsetDeg??0,allowReflexAngles:Boolean(graph.allowReflexAngles),snapDegrees:Number(graph.snapDegrees)};case "none":return {...shared,type:"none"};case "absolute-value":return {...shared,type:graph.type,coords:getAbsoluteValueCoords(graph,range,step)};case "tangent":return {...shared,type:graph.type,coords:getTangentCoords(graph,range,step)};case "logarithm":return {...shared,type:graph.type,...getLogarithmCoords(graph,range,step)};case "vector":return {...shared,type:graph.type,coords:getVectorCoords(graph,range,step)};default:throw new UnreachableCaseError(graph)}}function getPointCoords(graph,range,step){const numPoints=graph.numPoints||1;let coords=graph.coords?.slice();if(coords){return coords}const startCoords=graph.startCoords?.slice();if(startCoords){return startCoords}switch(numPoints){case 1:coords=[graph.coord||[0,0]];break;case 2:coords=[[-5,0],[5,0]];break;case 3:coords=[[-5,0],[0,0],[5,0]];break;case 4:coords=[[-6,0],[-2,0],[2,0],[6,0]];break;case 5:coords=[[-6,0],[-3,0],[0,0],[3,0],[6,0]];break;case 6:coords=[[-5,0],[-3,0],[-1,0],[1,0],[3,0],[5,0]];break;default:coords=[];break}const newCoords=normalizeCoords$1(coords,[[-10,10],[-10,10]]);return normalizePoints(range,step,newCoords)}function getSegmentCoords(graph,range,step){if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}const ys=n=>{switch(n){case 2:return [5,-5];case 3:return [5,0,-5];case 4:return [6,2,-2,-6];case 5:return [6,3,0,-3,-6];case 6:return [5,3,1,-1,-3,-5];default:return [5]}};const defaultRange=[[-10,10],[-10,10]];return ys(graph.numSegments).map(y=>{let endpoints=[[-5,y],[5,y]];endpoints=normalizeCoords$1(endpoints,defaultRange);endpoints=normalizePoints(range,step,endpoints);return endpoints})}const defaultLinearCoords=[[[.25,.75],[.75,.75]],[[.25,.25],[.75,.25]]];function getLineCoords(graph,range,step){if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}return normalizePoints(range,step,defaultLinearCoords[0])}function getVectorCoords(graph,range,step){if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}return normalizePoints(range,step,[[.6,.6],[.85,.85]])}function getLinearSystemCoords(graph,range,step){if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}return defaultLinearCoords.map(points=>normalizePoints(range,step,points))}function getPolygonCoords(graph,range,step){let coords=graph.coords?.slice();if(coords){return coords}const startCoords=graph.startCoords?.slice();if(startCoords){return startCoords}const n=graph.numSides||3;if(n==="unlimited"){coords=[];}else {const angle=2*Math.PI/n;const offset=(1/n-1/2)*Math.PI;const radius=graph.snapTo==="sides"?Math.sqrt(3)/3*7:4;coords=[...Array(n).keys()].map(i=>[radius*Math.cos(i*angle+offset),radius*Math.sin(i*angle+offset)]);}coords=normalizeCoords$1(coords,[[-10,10],[-10,10]]);const snapToGrid=!["angles","sides"].includes(graph.snapTo||"");coords=normalizePoints(range,step,coords,!snapToGrid);return coords}function getSinusoidCoords(graph,range,step){if(graph.coords){return [graph.coords[0],graph.coords[1]]}if(graph.startCoords){return [graph.startCoords[0],graph.startCoords[1]]}let coords=[[.5,.5],[.65,.6]];coords=normalizePoints(range,step,coords,true);return coords}function getAbsoluteValueCoords(graph,range,step){if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}const defaultCoords=[[.5,.5],[.75,.75]];return normalizePoints(range,step,defaultCoords,true)}function getTangentCoords(graph,range,step){if(graph.coords){return [graph.coords[0],graph.coords[1]]}if(graph.startCoords){return [graph.startCoords[0],graph.startCoords[1]]}let coords=[[.5,.5],[.75,.75]];coords=normalizePoints(range,step,coords,true);return coords}function getQuadraticCoords(graph,range,step){if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}const defaultCoords=[[.25,.75],[.5,.25],[.75,.75]];return normalizePoints(range,step,defaultCoords,true)}function getCircleCoords(graph){if(graph.center!=null&&graph.radius!=null){return {center:graph.center,radiusPoint:vec.add(graph.center,[graph.radius,0])}}if(graph.startCoords?.center&&graph.startCoords.radius){return {center:graph.startCoords.center,radiusPoint:vec.add(graph.startCoords.center,[graph.startCoords.radius,0])}}return {center:[0,0],radiusPoint:[2,0]}}function getExponentialCoords(graph,range,step){if(graph.coords&&graph.asymptote!=null){return {coords:[graph.coords[0],graph.coords[1]],asymptote:graph.asymptote}}let defaultCoords=[[.5,.6],[.75,.75]];defaultCoords=normalizePoints(range,step,defaultCoords,true);const coords=graph.startCoords?graph.startCoords.coords:defaultCoords;const asymptote=graph.startCoords?graph.startCoords.asymptote:0;return {coords,asymptote}}function getLogarithmCoords(graph,range,step){if(graph.coords&&graph.asymptote!=null){return {coords:[graph.coords[0],graph.coords[1]],asymptote:graph.asymptote}}let defaultCoords=[[.6,.55],[.75,.75]];defaultCoords=normalizePoints(range,step,defaultCoords,true);const coords=graph.startCoords?graph.startCoords.coords:defaultCoords;const asymptote=graph.startCoords?graph.startCoords.asymptote:0;return {coords,asymptote}}const getAngleCoords=params=>{const{graph,range,step}=params;if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}const{snapDegrees,angleOffsetDeg}=graph;const snap=snapDegrees||1;let angle=snap;while(angle<20){angle+=snap;}angle=angle*Math.PI/180;const offset=(angleOffsetDeg||0)*Math.PI/180;let defaultCoords=[[.85,.5],[.5,.5]];defaultCoords=normalizePoints(range,step,defaultCoords,true);const radius=magnitude$1(vector$1(...defaultCoords));const coords=[...defaultCoords,[0,0]];coords[0]=[coords[1][0]+radius*Math.cos(offset),coords[1][1]+radius*Math.sin(offset)];coords[2]=[coords[1][0]+radius*Math.cos(angle+offset),coords[1][1]+radius*Math.sin(angle+offset)];return coords};
1977
+ const{magnitude: magnitude$1,vector: vector$1}=geometry;function initializeGraphState(params){const{graph,step,snapStep,range}=params;const shared={hasBeenInteractedWith:false,range,snapStep,pointLabels:"pointLabels"in graph?graph.pointLabels:undefined,showPointLabels:"showPointLabels"in graph?Boolean(graph.showPointLabels):undefined};switch(graph.type){case "segment":return {...shared,type:"segment",coords:getSegmentCoords(graph,range,step)};case "linear":return {...shared,type:graph.type,coords:getLineCoords(graph,range,step)};case "ray":return {...shared,type:graph.type,coords:getLineCoords(graph,range,step)};case "linear-system":return {...shared,type:graph.type,coords:getLinearSystemCoords(graph,range,step)};case "polygon":return {...shared,type:"polygon",numSides:graph.numSides||0,showAngles:Boolean(graph.showAngles),showSides:Boolean(graph.showSides),coords:getPolygonCoords(graph,range,step),snapTo:graph.snapTo??"grid",focusedPointIndex:null,showRemovePointButton:false,interactionMode:"mouse",showKeyboardInteractionInvitation:false,closedPolygon:false};case "point":return {...shared,type:graph.type,coords:getPointCoords(graph,range,step),numPoints:graph.numPoints||0,focusedPointIndex:null,showRemovePointButton:false,interactionMode:"mouse",showKeyboardInteractionInvitation:false};case "circle":return {...shared,type:graph.type,...getCircleCoords(graph)};case "quadratic":return {...shared,type:graph.type,coords:getQuadraticCoords(graph,range,step)};case "sinusoid":return {...shared,type:graph.type,coords:getSinusoidCoords(graph,range,step)};case "exponential":return {...shared,type:graph.type,...getExponentialCoords(graph,range,step)};case "angle":return {...shared,type:graph.type,showAngles:Boolean(graph.showAngles),coords:getAngleCoords({graph,range,step}),angleOffsetDeg:graph.angleOffsetDeg??0,allowReflexAngles:Boolean(graph.allowReflexAngles),snapDegrees:Number(graph.snapDegrees)};case "none":return {...shared,type:"none"};case "absolute-value":return {...shared,type:graph.type,coords:getAbsoluteValueCoords(graph,range,step)};case "tangent":return {...shared,type:graph.type,coords:getTangentCoords(graph,range,step)};case "logarithm":return {...shared,type:graph.type,...getLogarithmCoords(graph,range,step)};case "vector":return {...shared,type:graph.type,coords:getVectorCoords(graph,range,step)};default:throw new UnreachableCaseError(graph)}}function getPointCoords(graph,range,step){const numPoints=graph.numPoints||1;let coords=graph.coords?.slice();if(coords){return coords}const startCoords=graph.startCoords?.slice();if(startCoords){return startCoords}switch(numPoints){case 1:coords=[graph.coord||[0,0]];break;case 2:coords=[[-5,0],[5,0]];break;case 3:coords=[[-5,0],[0,0],[5,0]];break;case 4:coords=[[-6,0],[-2,0],[2,0],[6,0]];break;case 5:coords=[[-6,0],[-3,0],[0,0],[3,0],[6,0]];break;case 6:coords=[[-5,0],[-3,0],[-1,0],[1,0],[3,0],[5,0]];break;default:coords=[];break}const newCoords=normalizeCoords$1(coords,[[-10,10],[-10,10]]);return normalizePoints(range,step,newCoords)}function getSegmentCoords(graph,range,step){if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}const ys=n=>{switch(n){case 2:return [5,-5];case 3:return [5,0,-5];case 4:return [6,2,-2,-6];case 5:return [6,3,0,-3,-6];case 6:return [5,3,1,-1,-3,-5];default:return [5]}};const defaultRange=[[-10,10],[-10,10]];return ys(graph.numSegments).map(y=>{let endpoints=[[-5,y],[5,y]];endpoints=normalizeCoords$1(endpoints,defaultRange);endpoints=normalizePoints(range,step,endpoints);return endpoints})}const defaultLinearCoords=[[[.25,.75],[.75,.75]],[[.25,.25],[.75,.25]]];function getLineCoords(graph,range,step){if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}return normalizePoints(range,step,defaultLinearCoords[0])}function getVectorCoords(graph,range,step){if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}return normalizePoints(range,step,[[.6,.6],[.85,.85]])}function getLinearSystemCoords(graph,range,step){if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}return defaultLinearCoords.map(points=>normalizePoints(range,step,points))}function getPolygonCoords(graph,range,step){let coords=graph.coords?.slice();if(coords){return coords}const startCoords=graph.startCoords?.slice();if(startCoords){return startCoords}const n=graph.numSides||3;if(n==="unlimited"){coords=[];}else {const angle=2*Math.PI/n;const offset=(1/n-1/2)*Math.PI;const radius=graph.snapTo==="sides"?Math.sqrt(3)/3*7:4;coords=[...Array(n).keys()].map(i=>[radius*Math.cos(i*angle+offset),radius*Math.sin(i*angle+offset)]);}coords=normalizeCoords$1(coords,[[-10,10],[-10,10]]);const snapToGrid=!["angles","sides"].includes(graph.snapTo||"");coords=normalizePoints(range,step,coords,!snapToGrid);return coords}function getSinusoidCoords(graph,range,step){if(graph.coords){return [graph.coords[0],graph.coords[1]]}if(graph.startCoords){return [graph.startCoords[0],graph.startCoords[1]]}let coords=[[.5,.5],[.65,.6]];coords=normalizePoints(range,step,coords,true);return coords}function getAbsoluteValueCoords(graph,range,step){if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}const defaultCoords=[[.5,.5],[.75,.75]];return normalizePoints(range,step,defaultCoords,true)}function getTangentCoords(graph,range,step){if(graph.coords){return [graph.coords[0],graph.coords[1]]}if(graph.startCoords){return [graph.startCoords[0],graph.startCoords[1]]}let coords=[[.5,.5],[.75,.75]];coords=normalizePoints(range,step,coords,true);return coords}function getQuadraticCoords(graph,range,step){if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}const defaultCoords=[[.25,.75],[.5,.25],[.75,.75]];return normalizePoints(range,step,defaultCoords,true)}function getCircleCoords(graph){if(graph.center!=null&&graph.radius!=null){return {center:graph.center,radiusPoint:vec.add(graph.center,[graph.radius,0])}}if(graph.startCoords?.center&&graph.startCoords.radius){return {center:graph.startCoords.center,radiusPoint:vec.add(graph.startCoords.center,[graph.startCoords.radius,0])}}return {center:[0,0],radiusPoint:[2,0]}}function getExponentialCoords(graph,range,step){if(graph.coords&&graph.asymptote!=null){return {coords:[graph.coords[0],graph.coords[1]],asymptote:graph.asymptote}}let defaultCoords=[[.5,.6],[.75,.75]];defaultCoords=normalizePoints(range,step,defaultCoords,true);const coords=graph.startCoords?graph.startCoords.coords:defaultCoords;const asymptote=graph.startCoords?graph.startCoords.asymptote:0;return {coords,asymptote}}function getLogarithmCoords(graph,range,step){if(graph.coords&&graph.asymptote!=null){return {coords:[graph.coords[0],graph.coords[1]],asymptote:graph.asymptote}}let defaultCoords=[[.6,.55],[.75,.75]];defaultCoords=normalizePoints(range,step,defaultCoords,true);const coords=graph.startCoords?graph.startCoords.coords:defaultCoords;const asymptote=graph.startCoords?graph.startCoords.asymptote:0;return {coords,asymptote}}const getAngleCoords=params=>{const{graph,range,step}=params;if(graph.coords){return graph.coords}if(graph.startCoords){return graph.startCoords}const{snapDegrees,angleOffsetDeg}=graph;const snap=snapDegrees||1;let angle=snap;while(angle<20){angle+=snap;}angle=angle*Math.PI/180;const offset=(angleOffsetDeg||0)*Math.PI/180;let defaultCoords=[[.85,.5],[.5,.5]];defaultCoords=normalizePoints(range,step,defaultCoords,true);const radius=magnitude$1(vector$1(...defaultCoords));const coords=[...defaultCoords,[0,0]];coords[0]=[coords[1][0]+radius*Math.cos(offset),coords[1][1]+radius*Math.sin(offset)];coords[2]=[coords[1][0]+radius*Math.cos(angle+offset),coords[1][1]+radius*Math.sin(angle+offset)];return coords};
1974
1978
 
1975
- const{getAngleFromVertex,getClockwiseAngle,polar}=angles;const{angleMeasures,ccw,lawOfCosines,magnitude,polygonSidesIntersect,reverseVector,sign,vector}=geometry;const{getQuadraticCoefficients: getQuadraticCoefficients$1}=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:resolvePointLabel(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:resolvePointLabel(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:String(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}const resolvedAngleLabel=resolvePointLabel(state.pointLabels,action.index);return {...newState,stateAnnouncement:{type:"move-angle-point",pointIndex:action.index,pointLabel:resolvedAngleLabel,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:String(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:String(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:resolvePointLabel(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})}}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})}}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})}}case "tangent":{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})}}case "quadratic":{const newCoords=[...state.coords];const boundDestination=boundToEdgeAndSnapToGrid(action.destination,state);newCoords[action.index]=boundDestination;const QuadraticCoefficients=getQuadraticCoefficients$1(newCoords);if(QuadraticCoefficients===undefined){return state}return {...state,hasBeenInteractedWith:true,coords:setAtIndex({array:state.coords,index:action.index,newValue:boundDestination})}}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}}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}}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)}}}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=_.map([[coords[rel(-1)],boundedDestinationPoint],[boundedDestinationPoint,coords[rel(1)]],[coords[rel(-1)],coords[rel(1)]]],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}
1979
+ const{getAngleFromVertex,getClockwiseAngle,polar}=angles;const{angleMeasures,ccw,lawOfCosines,magnitude,polygonSidesIntersect,reverseVector,sign,vector}=geometry;const{getQuadraticCoefficients: getQuadraticCoefficients$1}=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:resolvePointLabel(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:resolvePointLabel(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:String(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}const resolvedAngleLabel=resolvePointLabel(state.pointLabels,action.index);return {...newState,stateAnnouncement:{type:"move-angle-point",pointIndex:action.index,pointLabel:resolvedAngleLabel,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:String(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:String(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:resolvePointLabel(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:resolvePointLabel(state.pointLabels,action.index),x:boundDestination[X],y:boundDestination[Y]}}}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:resolvePointLabel(state.pointLabels,action.index),x:boundDestination[X],y:boundDestination[Y]}}}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:resolvePointLabel(state.pointLabels,action.index),x:boundDestination[X],y:boundDestination[Y]}}}case "tangent":{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-tangent-point",pointIndex:action.index,pointLabel:resolvePointLabel(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$1(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:resolvePointLabel(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)}}}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=_.map([[coords[rel(-1)],boundedDestinationPoint],[boundedDestinationPoint,coords[rel(1)]],[coords[rel(-1)],coords[rel(1)]]],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}
1976
1980
 
1977
1981
  const{clockwise}=geometry;const{convertRadiansToDegrees}=angles;function renderPolygonGraph(state,dispatch,i18n,markings){return {graph:jsxRuntimeExports.jsx(PolygonGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getPolygonGraphDescription(state,i18n,markings)}}const PolygonGraph=props=>{const{dispatch}=props;const{numSides,coords,snapStep,snapTo="grid"}=props.graphState;const graphConfig=useGraphConfig();const polygonRef=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{dragging}=useDraggable({gestureTarget:polygonRef,point:dragReferencePoint,onMove:newStart=>{dispatch(actions.polygon.moveAll(newStart));},constrainKeyboardMovement:constrain});const[hovered,setHovered]=React.useState(false);const[focusVisible,setFocusVisible]=React.useState(false);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 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$1,{ariaDescribedBy:`${angleId} ${side1Id} ${side2Id}`,ariaLabel:buildLabel(i,point),ariaLive:"off",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:"rgba(0,0,0,0)",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$1,{ariaDescribedBy:`${angleId} ${sideIds}`,ariaLabel:buildLabel(i,point),ariaLive:"off",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 getPolygonGraphDescription(state,i18n,markings){const strings=describePolygonGraph(state,i18n,markings);return strings.srPolygonInteractiveElements}function describePolygonGraph(state,i18n,markings){const{strings,locale}=i18n;const{coords,pointLabels}=state;const buildLabel=(index,point)=>buildPointAriaLabel(pointLabels,index,point,strings,locale);const isCoordinatePlane=markings==="axes"||markings==="graph";const hasOnePoint=coords.length===1;const srPolygonGraph=isCoordinatePlane?strings.srPolygonGraphCoordinatePlane:strings.srPolygonGraph;const srPolygonGraphPointsNum=hasOnePoint?strings.srPolygonGraphPointsOne:strings.srPolygonGraphPointsNum({num:coords.length});let srPolygonGraphPoints;if(isCoordinatePlane){const pointsString=coords.map((coord,i)=>buildLabel(i,coord)??strings.srPointAtCoordinates({num:i+1,x:srFormatNumber(coord[0],locale),y:srFormatNumber(coord[1],locale)}));srPolygonGraphPoints=pointsString.join(" ");}const srPolygonElementsNum=hasOnePoint?strings.srPolygonElementsOne:strings.srPolygonElementsNum({num:coords.length});const srPolygonInteractiveElements=coords.length>0?strings.srInteractiveElements({elements:[srPolygonElementsNum,srPolygonGraphPoints].join(" ")}):null;return {srPolygonGraph,srPolygonGraphPointsNum,srPolygonGraphPoints,srPolygonElementsNum,srPolygonInteractiveElements}}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]))}}
1978
1982
 
1979
- function renderQuadraticGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(QuadraticGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getQuadraticGraphDescription(state,i18n)}}function QuadraticGraph(props){const{dispatch,graphState}=props;const{coords,pointLabels,snapStep}=graphState;const{interactiveColor}=useGraphConfig();const{strings,locale}=usePerseusI18n();const buildLabel=usePointAriaLabel(pointLabels);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(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(i+1,coord,strings,locale);const srVertex=srQuadraticVertex?` ${srQuadraticVertex}`:"";return jsxRuntimeExports.jsx(MovablePoint$1,{ariaLabel:buildLabel(i,coord)??`${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 getQuadraticCoefficients=coords=>{const p1=coords[0];const p2=coords[1];const p3=coords[2];const denom=(p1[0]-p2[0])*(p1[0]-p3[0])*(p2[0]-p3[0]);if(denom===0){return}const a=(p3[0]*(p2[1]-p1[1])+p2[0]*(p1[1]-p3[1])+p1[0]*(p3[1]-p2[1]))/denom;const b=(p3[0]*p3[0]*(p1[1]-p2[1])+p2[0]*p2[0]*(p3[1]-p1[1])+p1[0]*p1[0]*(p2[1]-p3[1]))/denom;const c=(p2[0]*p3[0]*(p2[0]-p3[0])*p1[1]+p3[0]*p1[0]*(p3[0]-p1[0])*p2[1]+p1[0]*p2[0]*(p1[0]-p2[0])*p3[1])/denom;return [a,b,c]};function getQuadraticGraphDescription(state,i18n){const strings=describeQuadraticGraph(state,i18n);return strings.srQuadraticInteractiveElements}function describeQuadraticGraph(state,i18n){const{strings,locale}=i18n;const coeffs=getQuadraticCoefficients(state.coords);const[a,b,c]=coeffs??[0,0,0];const vertex=[-b/(2*a),c-b*b/(4*a)];const xIntercepts=getQuadraticXIntercepts(a,b,c);const srQuadraticGraph=strings.srQuadraticGraph;const srQuadraticFaceUp=strings.srQuadraticFaceUp;const srQuadraticFaceDown=strings.srQuadraticFaceDown;const srQuadraticDirection=a===0?undefined:a>0?srQuadraticFaceUp:srQuadraticFaceDown;const srQuadraticVertex=a!==0?getQuadraticVertexString(vertex,strings):undefined;const srQuadraticXIntercepts=xIntercepts.length===2?strings.srQuadraticTwoXIntercepts({intercept1:srFormatNumber(xIntercepts[0],locale),intercept2:srFormatNumber(xIntercepts[1],locale)}):xIntercepts.length===1?strings.srQuadraticOneXIntercept({intercept:srFormatNumber(xIntercepts[0],locale)}):undefined;const srQuadraticYIntercept=strings.srQuadraticYIntercept({intercept:srFormatNumber(c,locale)});const srQuadraticInteractiveElements=strings.srInteractiveElements({elements:strings.srQuadraticInteractiveElements({point1X:srFormatNumber(state.coords[0][0],locale),point1Y:srFormatNumber(state.coords[0][1],locale),point2X:srFormatNumber(state.coords[1][0],locale),point2Y:srFormatNumber(state.coords[1][1],locale),point3X:srFormatNumber(state.coords[2][0],locale),point3Y:srFormatNumber(state.coords[2][1],locale)})});return {srQuadraticGraph,srQuadraticDirection,srQuadraticVertex,srQuadraticXIntercepts,srQuadraticYIntercept,srQuadraticInteractiveElements}}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};
1983
+ function renderQuadraticGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(QuadraticGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getQuadraticGraphDescription(state,i18n)}}function QuadraticGraph(props){const{dispatch,graphState}=props;const{coords,pointLabels,snapStep}=graphState;const{interactiveColor}=useGraphConfig();const{strings,locale}=usePerseusI18n();const buildLabel=usePointAriaLabel(pointLabels);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(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(i+1,coord,strings,locale);const srVertex=srQuadraticVertex?` ${srQuadraticVertex}`:"";return jsxRuntimeExports.jsx(MovablePoint$1,{ariaLabel:buildLabel(i,coord)??`${srQuadraticPoint}${srVertex}`,point:coord,sequenceNumber:i+1,constrain:getQuadraticKeyboardConstraint(coords,snapStep,i),onMove:destination=>dispatch(actions.quadratic.movePoint(i,destination)),ariaLive:"off"},"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 getQuadraticCoefficients=coords=>{const p1=coords[0];const p2=coords[1];const p3=coords[2];const denom=(p1[0]-p2[0])*(p1[0]-p3[0])*(p2[0]-p3[0]);if(denom===0){return}const a=(p3[0]*(p2[1]-p1[1])+p2[0]*(p1[1]-p3[1])+p1[0]*(p3[1]-p2[1]))/denom;const b=(p3[0]*p3[0]*(p1[1]-p2[1])+p2[0]*p2[0]*(p3[1]-p1[1])+p1[0]*p1[0]*(p2[1]-p3[1]))/denom;const c=(p2[0]*p3[0]*(p2[0]-p3[0])*p1[1]+p3[0]*p1[0]*(p3[0]-p1[0])*p2[1]+p1[0]*p2[0]*(p1[0]-p2[0])*p3[1])/denom;return [a,b,c]};function getQuadraticGraphDescription(state,i18n){const strings=describeQuadraticGraph(state,i18n);return strings.srQuadraticInteractiveElements}function describeQuadraticGraph(state,i18n){const{strings,locale}=i18n;const coeffs=getQuadraticCoefficients(state.coords);const[a,b,c]=coeffs??[0,0,0];const vertex=getQuadraticVertex([a,b,c]);const xIntercepts=getQuadraticXIntercepts(a,b,c);const srQuadraticGraph=strings.srQuadraticGraph;const srQuadraticFaceUp=strings.srQuadraticFaceUp;const srQuadraticFaceDown=strings.srQuadraticFaceDown;const srQuadraticDirection=a===0?undefined:a>0?srQuadraticFaceUp:srQuadraticFaceDown;const srQuadraticVertex=vertex===undefined?undefined:getQuadraticVertexString(vertex,strings);const srQuadraticXIntercepts=xIntercepts.length===2?strings.srQuadraticTwoXIntercepts({intercept1:srFormatNumber(xIntercepts[0],locale),intercept2:srFormatNumber(xIntercepts[1],locale)}):xIntercepts.length===1?strings.srQuadraticOneXIntercept({intercept:srFormatNumber(xIntercepts[0],locale)}):undefined;const srQuadraticYIntercept=strings.srQuadraticYIntercept({intercept:srFormatNumber(c,locale)});const srQuadraticInteractiveElements=strings.srInteractiveElements({elements:strings.srQuadraticInteractiveElements({point1X:srFormatNumber(state.coords[0][0],locale),point1Y:srFormatNumber(state.coords[0][1],locale),point2X:srFormatNumber(state.coords[1][0],locale),point2Y:srFormatNumber(state.coords[1][1],locale),point3X:srFormatNumber(state.coords[2][0],locale),point3Y:srFormatNumber(state.coords[2][1],locale)})});return {srQuadraticGraph,srQuadraticDirection,srQuadraticVertex,srQuadraticXIntercepts,srQuadraticYIntercept,srQuadraticInteractiveElements}}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};
1980
1984
 
1981
1985
  function renderRayGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(RayGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getRayGraphDescription(state,i18n)}}const RayGraph=props=>{const{dispatch}=props;const{coords:line,pointLabels}=props.graphState;const handleMoveLine=newStart=>dispatch(actions.ray.moveRay(newStart));const handleMovePoint=(pointIndex,newPoint)=>dispatch(actions.ray.movePoint(pointIndex,newPoint));const{strings,locale}=usePerseusI18n();const buildLabel=usePointAriaLabel(pointLabels);const id=React.useId();const pointsDescriptionId=id+"-points";const{srRayGraph,srRayPoints,srRayEndpoint,srRayTerminalPoint,srRayGrabHandle}=describeRayGraph(props.graphState,{strings,locale});const point1AriaLabel=buildLabel(0,line[0])??srRayEndpoint;const point2AriaLabel=buildLabel(1,line[1])??srRayTerminalPoint;return jsxRuntimeExports.jsxs("g",{"aria-label":srRayGraph,"aria-describedby":pointsDescriptionId,children:[jsxRuntimeExports.jsx(MovableLine,{points:line,ariaLabels:{point1AriaLabel,point2AriaLabel,grabHandleAriaLabel:srRayGrabHandle},ariaLive:"off",onMoveLine:handleMoveLine,onMovePoint:handleMovePoint,extend:{start:false,end:true}}),jsxRuntimeExports.jsx(SRDescInSVG,{id:pointsDescriptionId,children:srRayPoints})]})};function getRayGraphDescription(state,i18n){const strings=describeRayGraph(state,i18n);return strings.srRayInteractiveElement}function describeRayGraph(state,i18n){const{coords:line}=state;const{strings,locale}=i18n;const srRayGraph=strings.srRayGraph;const srRayPoints=strings.srRayPoints({point1X:srFormatNumber(line[0][0],locale),point1Y:srFormatNumber(line[0][1],locale),point2X:srFormatNumber(line[1][0],locale),point2Y:srFormatNumber(line[1][1],locale)});const srRayEndpoint=strings.srRayEndpoint({x:srFormatNumber(line[0][0],locale),y:srFormatNumber(line[0][1],locale)});const srRayTerminalPoint=strings.srRayTerminalPoint({x:srFormatNumber(line[1][0],locale),y:srFormatNumber(line[1][1],locale)});const srRayGrabHandle=strings.srRayGrabHandle({point1X:srFormatNumber(line[0][0],locale),point1Y:srFormatNumber(line[0][1],locale),point2X:srFormatNumber(line[1][0],locale),point2Y:srFormatNumber(line[1][1],locale)});const srRayInteractiveElement=strings.srInteractiveElements({elements:[srRayGraph,srRayPoints].join(" ")});return {srRayGraph,srRayPoints,srRayEndpoint,srRayTerminalPoint,srRayGrabHandle,srRayInteractiveElement}}
1982
1986
 
@@ -1984,7 +1988,7 @@ function renderSegmentGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports
1984
1988
 
1985
1989
  function renderSinusoidGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(SinusoidGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getSinusoidDescription(state,i18n)}}function SinusoidGraph(props){const{dispatch,graphState}=props;const{interactiveColor}=useGraphConfig();const i18n=usePerseusI18n();const id=React.useId();const descriptionId=id+"-description";const{coords,pointLabels,snapStep}=graphState;const buildLabel=usePointAriaLabel(pointLabels);const coeffRef=React.useRef({amplitude:1,angularFrequency:1,phase:1,verticalOffset:0});const coeffs=getSinusoidCoefficients(coords);if(coeffs!==undefined){coeffRef.current=coeffs;}const{srSinusoidGraph,srSinusoidDescription,srSinusoidRootPoint,srSinusoidPeakPoint}=describeSinusoidGraph(graphState,i18n);return jsxRuntimeExports.jsxs("g",{"aria-label":srSinusoidGraph,"aria-describedby":descriptionId,children:[jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(Plot$2.OfX,{y:x=>computeSine(x,coeffRef.current),color:interactiveColor,svgPathProps:{"aria-hidden":true}})}),coords.map((coord,i)=>jsxRuntimeExports.jsx(MovablePoint$1,{ariaLabel:buildLabel(i,coord)??(i===0?srSinusoidRootPoint:srSinusoidPeakPoint),point:coord,sequenceNumber:i+1,constrain:getSinusoidKeyboardConstraint(coords,snapStep,i),onMove:destination=>dispatch(actions.sinusoid.movePoint(i,destination)),ariaLive:"off"},"point-"+i)),jsxRuntimeExports.jsx(SRDescInSVG,{id:descriptionId,children:srSinusoidDescription})]})}const getSinusoidKeyboardConstraint=(coords,snapStep,pointIndex)=>{const coordToBeMoved=coords[pointIndex];const otherPoint=coords[1-pointIndex];const movePointWithConstraint=moveFunc=>{let movedCoord=moveFunc(coordToBeMoved);if(movedCoord[X]===otherPoint[X]){movedCoord=moveFunc(movedCoord);}return movedCoord};return {up:movePointWithConstraint(coord=>vec.add(coord,[0,snapStep[1]])),down:movePointWithConstraint(coord=>vec.sub(coord,[0,snapStep[1]])),left:movePointWithConstraint(coord=>vec.sub(coord,[snapStep[0],0])),right:movePointWithConstraint(coord=>vec.add(coord,[snapStep[0],0]))}};const computeSine=function(x,sinusoidCoefficients){const{amplitude:a,angularFrequency:b,phase:c,verticalOffset:d}=sinusoidCoefficients;return a*Math.sin(b*x-c)+d};const getSinusoidCoefficients=coords=>{const p1=coords[0];const p2=coords[1];if(p2[X]===p1[X]){return}const amplitude=p2[Y]-p1[Y];const angularFrequency=Math.PI/(2*(p2[X]-p1[X]));const phase=p1[X]*angularFrequency;const verticalOffset=p1[Y];return {amplitude,angularFrequency,phase,verticalOffset}};function getSinusoidDescription(state,i18n){const strings=describeSinusoidGraph(state,i18n);return strings.srSinusoidInteractiveElements}function describeSinusoidGraph(state,i18n){const{strings,locale}=i18n;const{coords}=state;const[root,peak]=coords;const diffX=Math.abs(peak[X]-root[X]);const diffY=Math.abs(peak[Y]-root[Y]);const formattedRoot={x:srFormatNumber(root[X],locale),y:srFormatNumber(root[Y],locale)};const formattedPeak={x:srFormatNumber(peak[X],locale),y:srFormatNumber(peak[Y],locale)};const srSinusoidGraph=strings.srSinusoidGraph;const srSinusoidDescription=strings.srSinusoidDescription({minValue:srFormatNumber(root[Y]-diffY,locale),maxValue:srFormatNumber(root[Y]+diffY,locale),cycleStart:srFormatNumber(root[X]-2*diffX,locale),cycleEnd:srFormatNumber(root[X]+2*diffX,locale)});const srSinusoidRootPoint=strings.srSinusoidRootPoint(formattedRoot);const srSinusoidPeakPoint=peak[Y]===root[Y]?strings.srSinusoidFlatPoint(formattedPeak):peak[Y]>root[Y]?strings.srSinusoidMaxPoint(formattedPeak):strings.srSinusoidMinPoint(formattedPeak);const srSinusoidInteractiveElements=strings.srInteractiveElements({elements:strings.srSinusoidInteractiveElements({point1X:srFormatNumber(root[X],locale),point1Y:srFormatNumber(root[Y],locale),point2X:srFormatNumber(peak[X],locale),point2Y:srFormatNumber(peak[Y],locale)})});return {srSinusoidGraph,srSinusoidDescription,srSinusoidRootPoint,srSinusoidPeakPoint,srSinusoidInteractiveElements}}
1986
1990
 
1987
- function renderTangentGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(TangentGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getTangentDescription(state,i18n)}}function TangentGraph(props){const{dispatch,graphState}=props;const{interactiveColor,range}=useGraphConfig();const i18n=usePerseusI18n();const id=React.useId();const descriptionId=id+"-description";const{coords,pointLabels,snapStep}=graphState;const buildLabel=usePointAriaLabel(pointLabels);const coeffRef=React.useRef({amplitude:1,angularFrequency:1,phase:1,verticalOffset:0});const coeffs=getTangentCoefficients(coords);if(coeffs!==undefined){coeffRef.current=coeffs;}const xRange=[range[0][0],range[0][1]];const segments=getPlotSegments(coeffRef.current,xRange);const{srTangentGraph,srTangentDescription,srTangentInflectionPoint,srTangentSecondPoint}=describeTangentGraph(graphState,i18n);return jsxRuntimeExports.jsxs("g",{"aria-label":srTangentGraph,"aria-describedby":descriptionId,children:[jsxRuntimeExports.jsx(ClipToGraphBounds,{children:segments.map(([segStart,segEnd],i)=>jsxRuntimeExports.jsx(Plot$2.OfX,{y:x=>computeTangent(x,coeffRef.current),domain:[segStart,segEnd],color:interactiveColor,svgPathProps:{"aria-hidden":true}},`tangent-segment-${i}`))}),coords.map((coord,i)=>jsxRuntimeExports.jsx(MovablePoint$1,{ariaLabel:buildLabel(i,coord)??(i===0?srTangentInflectionPoint:srTangentSecondPoint),point:coord,sequenceNumber:i+1,constrain:getTangentKeyboardConstraint(coords,snapStep,i),onMove:destination=>dispatch(actions.tangent.movePoint(i,destination))},"point-"+i)),jsxRuntimeExports.jsx(SRDescInSVG,{id:descriptionId,children:srTangentDescription})]})}const getTangentKeyboardConstraint=(coords,snapStep,pointIndex)=>{const coordToBeMoved=coords[pointIndex];const otherPoint=coords[1-pointIndex];const movePointWithConstraint=moveFunc=>{let movedCoord=moveFunc(coordToBeMoved);if(movedCoord[X]===otherPoint[X]){movedCoord=moveFunc(movedCoord);}return movedCoord};return {up:movePointWithConstraint(coord=>vec.add(coord,[0,snapStep[1]])),down:movePointWithConstraint(coord=>vec.sub(coord,[0,snapStep[1]])),left:movePointWithConstraint(coord=>vec.sub(coord,[snapStep[0],0])),right:movePointWithConstraint(coord=>vec.add(coord,[snapStep[0],0]))}};const computeTangent=function(x,tangentCoefficients){const{amplitude:a,angularFrequency:b,phase:c,verticalOffset:d}=tangentCoefficients;const arg=b*x-c;const normalized=(arg-Math.PI/2)/Math.PI%1;const distToAsymptote=Math.abs(normalized>.5?normalized-1:normalized<-0.5?normalized+1:normalized);if(distToAsymptote<.001){return NaN}return a*Math.tan(arg)+d};const getTangentCoefficients=coords=>{const p1=coords[0];const p2=coords[1];if(p2[X]===p1[X]){return}const amplitude=p2[Y]-p1[Y];const angularFrequency=Math.PI/(4*(p2[X]-p1[X]));const phase=p1[X]*angularFrequency;const verticalOffset=p1[Y];return {amplitude,angularFrequency,phase,verticalOffset}};function getAsymptotePositions(coeffs,xRange){const{angularFrequency:b,phase:c}=coeffs;if(b===0){return []}const period=Math.PI/Math.abs(b);const referenceAsymptote=(c+Math.PI/2)/b;const asymptotes=[];let x=referenceAsymptote;while(x>xRange[0]-period){if(x>xRange[0]&&x<xRange[1]){asymptotes.push(x);}x-=period;}x=referenceAsymptote+period;while(x<xRange[1]+period){if(x>xRange[0]&&x<xRange[1]){asymptotes.push(x);}x+=period;}return asymptotes.sort((a,b)=>a-b)}function getPlotSegments(coeffs,xRange){const asymptotes=getAsymptotePositions(coeffs,xRange);const eps=.01;const segments=[];let start=xRange[0];for(const asymptote of asymptotes){segments.push([start,asymptote-eps]);start=asymptote+eps;}segments.push([start,xRange[1]]);return segments}function getTangentDescription(state,i18n){return describeTangentGraph(state,i18n).srTangentInteractiveElements}function describeTangentGraph(state,i18n){const{strings,locale}=i18n;const{coords}=state;const[inflection,secondPoint]=coords;const formattedInflection={x:srFormatNumber(inflection[X],locale),y:srFormatNumber(inflection[Y],locale)};const formattedSecondPoint={x:srFormatNumber(secondPoint[X],locale),y:srFormatNumber(secondPoint[Y],locale)};const srTangentGraph=strings.srTangentGraph;const srTangentDescription=strings.srTangentDescription({inflectionX:srFormatNumber(inflection[X],locale),inflectionY:srFormatNumber(inflection[Y],locale)});const srTangentInflectionPoint=strings.srTangentInflectionPoint(formattedInflection);const srTangentSecondPoint=strings.srTangentSecondPoint(formattedSecondPoint);const srTangentInteractiveElements=strings.srInteractiveElements({elements:strings.srTangentInteractiveElements({point1X:srFormatNumber(inflection[X],locale),point1Y:srFormatNumber(inflection[Y],locale),point2X:srFormatNumber(secondPoint[X],locale),point2Y:srFormatNumber(secondPoint[Y],locale)})});return {srTangentGraph,srTangentDescription,srTangentInflectionPoint,srTangentSecondPoint,srTangentInteractiveElements}}
1991
+ function renderTangentGraph(state,dispatch,i18n){return {graph:jsxRuntimeExports.jsx(TangentGraph,{graphState:state,dispatch:dispatch}),interactiveElementsDescription:getTangentDescription(state,i18n)}}function TangentGraph(props){const{dispatch,graphState}=props;const{interactiveColor,range}=useGraphConfig();const i18n=usePerseusI18n();const id=React.useId();const descriptionId=id+"-description";const{coords,pointLabels,snapStep}=graphState;const buildLabel=usePointAriaLabel(pointLabels);const coeffRef=React.useRef({amplitude:1,angularFrequency:1,phase:1,verticalOffset:0});const coeffs=getTangentCoefficients(coords);if(coeffs!==undefined){coeffRef.current=coeffs;}const xRange=[range[0][0],range[0][1]];const segments=getPlotSegments(coeffRef.current,xRange);const{srTangentGraph,srTangentDescription,srTangentInflectionPoint,srTangentSecondPoint}=describeTangentGraph(graphState,i18n);return jsxRuntimeExports.jsxs("g",{"aria-label":srTangentGraph,"aria-describedby":descriptionId,children:[jsxRuntimeExports.jsx(ClipToGraphBounds,{children:segments.map(([segStart,segEnd],i)=>jsxRuntimeExports.jsx(Plot$2.OfX,{y:x=>computeTangent(x,coeffRef.current),domain:[segStart,segEnd],color:interactiveColor,svgPathProps:{"aria-hidden":true}},`tangent-segment-${i}`))}),coords.map((coord,i)=>jsxRuntimeExports.jsx(MovablePoint$1,{ariaLabel:buildLabel(i,coord)??(i===0?srTangentInflectionPoint:srTangentSecondPoint),point:coord,sequenceNumber:i+1,constrain:getTangentKeyboardConstraint(coords,snapStep,i),onMove:destination=>dispatch(actions.tangent.movePoint(i,destination)),ariaLive:"off"},"point-"+i)),jsxRuntimeExports.jsx(SRDescInSVG,{id:descriptionId,children:srTangentDescription})]})}const getTangentKeyboardConstraint=(coords,snapStep,pointIndex)=>{const coordToBeMoved=coords[pointIndex];const otherPoint=coords[1-pointIndex];const movePointWithConstraint=moveFunc=>{let movedCoord=moveFunc(coordToBeMoved);if(movedCoord[X]===otherPoint[X]){movedCoord=moveFunc(movedCoord);}return movedCoord};return {up:movePointWithConstraint(coord=>vec.add(coord,[0,snapStep[1]])),down:movePointWithConstraint(coord=>vec.sub(coord,[0,snapStep[1]])),left:movePointWithConstraint(coord=>vec.sub(coord,[snapStep[0],0])),right:movePointWithConstraint(coord=>vec.add(coord,[snapStep[0],0]))}};const computeTangent=function(x,tangentCoefficients){const{amplitude:a,angularFrequency:b,phase:c,verticalOffset:d}=tangentCoefficients;const arg=b*x-c;const normalized=(arg-Math.PI/2)/Math.PI%1;const distToAsymptote=Math.abs(normalized>.5?normalized-1:normalized<-0.5?normalized+1:normalized);if(distToAsymptote<.001){return NaN}return a*Math.tan(arg)+d};const getTangentCoefficients=coords=>{const p1=coords[0];const p2=coords[1];if(p2[X]===p1[X]){return}const amplitude=p2[Y]-p1[Y];const angularFrequency=Math.PI/(4*(p2[X]-p1[X]));const phase=p1[X]*angularFrequency;const verticalOffset=p1[Y];return {amplitude,angularFrequency,phase,verticalOffset}};function getAsymptotePositions(coeffs,xRange){const{angularFrequency:b,phase:c}=coeffs;if(b===0){return []}const period=Math.PI/Math.abs(b);const referenceAsymptote=(c+Math.PI/2)/b;const asymptotes=[];let x=referenceAsymptote;while(x>xRange[0]-period){if(x>xRange[0]&&x<xRange[1]){asymptotes.push(x);}x-=period;}x=referenceAsymptote+period;while(x<xRange[1]+period){if(x>xRange[0]&&x<xRange[1]){asymptotes.push(x);}x+=period;}return asymptotes.sort((a,b)=>a-b)}function getPlotSegments(coeffs,xRange){const asymptotes=getAsymptotePositions(coeffs,xRange);const eps=.01;const segments=[];let start=xRange[0];for(const asymptote of asymptotes){segments.push([start,asymptote-eps]);start=asymptote+eps;}segments.push([start,xRange[1]]);return segments}function getTangentDescription(state,i18n){return describeTangentGraph(state,i18n).srTangentInteractiveElements}function describeTangentGraph(state,i18n){const{strings,locale}=i18n;const{coords}=state;const[inflection,secondPoint]=coords;const formattedInflection={x:srFormatNumber(inflection[X],locale),y:srFormatNumber(inflection[Y],locale)};const formattedSecondPoint={x:srFormatNumber(secondPoint[X],locale),y:srFormatNumber(secondPoint[Y],locale)};const srTangentGraph=strings.srTangentGraph;const srTangentDescription=strings.srTangentDescription({inflectionX:srFormatNumber(inflection[X],locale),inflectionY:srFormatNumber(inflection[Y],locale)});const srTangentInflectionPoint=strings.srTangentInflectionPoint(formattedInflection);const srTangentSecondPoint=strings.srTangentSecondPoint(formattedSecondPoint);const srTangentInteractiveElements=strings.srInteractiveElements({elements:strings.srTangentInteractiveElements({point1X:srFormatNumber(inflection[X],locale),point1Y:srFormatNumber(inflection[Y],locale),point2X:srFormatNumber(secondPoint[X],locale),point2Y:srFormatNumber(secondPoint[Y],locale)})});return {srTangentGraph,srTangentDescription,srTangentInflectionPoint,srTangentSecondPoint,srTangentInteractiveElements}}
1988
1992
 
1989
1993
  const hitboxSizePx=48;const ARROW_SCALE=1.5;const arrowPath=pathBuilder().move(-5,5).line(0,0).line(-5,-5).scale(ARROW_SCALE).build();function buildRoundedTriangle(tipX,tipY,armX,armY,backX,backY,backR,tipR){return pathBuilder().move(tipX,tipY).line(armX,armY).circularArc(backR,backX,backY,{sweep:true}).line(backX,-5).circularArc(backR,armX,-7.8,{sweep:true}).line(tipX,-2.8).circularArc(tipR,tipX,tipY,{sweep:true}).scale(ARROW_SCALE).build()}const arrowPathHalo=buildRoundedTriangle(2.8,2.8,-2.2,7.8,-9,5,4,4);const chevronPathAttrs={d:arrowPath,fill:"none",strokeLinejoin:"round",strokeLinecap:"round"};const MovableArrowheadView=forwardRef(function MovableArrowheadViewWithRef(props,hitboxRef){const{showTooltips,interactiveColor,disableKeyboardInteraction,snapStep}=useGraphConfig();const{point,angle,dragging,cursor,showFocusRing,onClick=()=>{}}=props;const wbColorName=disableKeyboardInteraction?"fadedOffBlack64":"blue";const classes=classNames("movable-arrowhead",dragging&&"movable-arrowhead--dragging",showFocusRing&&"movable-arrowhead--focus");const[[x,y]]=useTransformVectorsToPixels(point);const xSigFigs=countSignificantDecimals(snapStep[X]);const ySigFigs=countSignificantDecimals(snapStep[Y]);const xTickLabel=point[X].toFixed(xSigFigs);const yTickLabel=point[Y].toFixed(ySigFigs);const tooltipContent=`(${xTickLabel}, ${yTickLabel})`;const svgForArrowhead=jsxRuntimeExports.jsxs("g",{"aria-hidden":true,ref:hitboxRef,className:classes,style:{"--movable-arrowhead-color":interactiveColor,cursor},"data-testid":"movable-arrowhead",onClick:onClick,children:[jsxRuntimeExports.jsx("circle",{className:"movable-arrowhead-hitbox",r:hitboxSizePx/2,cx:x,cy:y}),jsxRuntimeExports.jsxs("g",{transform:`translate(${x} ${y}) rotate(${angle})`,children:[jsxRuntimeExports.jsx("path",{d:arrowPathHalo,className:"movable-arrowhead-halo"}),jsxRuntimeExports.jsx("path",{...chevronPathAttrs,className:"movable-arrowhead-ring"}),jsxRuntimeExports.jsx("path",{...chevronPathAttrs,className:"movable-arrowhead-center"})]})]});return jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment,{children:showTooltips?jsxRuntimeExports.jsx(Tooltip,{autoUpdate:true,opened:true,backgroundColor:wbColorName,content:tooltipContent,contentStyle:{color:semanticColor.core.foreground.knockout.default},children:svgForArrowhead}):svgForArrowhead})});function classNames(...names){return names.filter(Boolean).join(" ")}
1990
1994
 
@@ -1994,7 +1998,7 @@ const{calculateAngleInDegrees: calculateAngleInDegrees$1}=angles;const LINE_PULL
1994
1998
 
1995
1999
  const{calculateAngleInDegrees,convertDegreesToRadians}=angles;const protractorImage="https://cdn.kastatic.org/images/perseus/protractor.svg";const centerToTopLeft=[-195,-190];const centerToRotationHandle=[-201,-15];function Protractor(){const staticUrl=getDependencies().staticUrl;const{range,snapStep}=useGraphConfig();const[[xMin,xMax],[yMin,yMax]]=range;const initialCenter=[lerp(xMin,xMax,.5),lerp(yMin,yMax,.25)];const[center,setCenter]=useState(initialCenter);const[rotationHandleOffset,setRotationHandleOffset]=useState(centerToRotationHandle);const draggableRef=useRef(null);const{dragging}=useDraggable({gestureTarget:draggableRef,onMove:point=>setCenter(bound$1({snapStep,range,point})),point:center,constrainKeyboardMovement:point=>bound$1({snapStep,range,point})});const rotationHandleRef=useRef(null);useDraggablePx({gestureTarget:rotationHandleRef,onMove:setRotationHandleOffset,point:rotationHandleOffset,constrain:constrainToCircle});const[centerPx]=useTransformVectorsToPixels(center);const topLeftPx=vec.add(centerPx,centerToTopLeft);const angle=calculateAngleInDegrees(rotationHandleOffset)-calculateAngleInDegrees(centerToRotationHandle);return jsxRuntimeExports.jsxs("g",{ref:draggableRef,transform:`translate(${topLeftPx[X]}, ${topLeftPx[Y]}), rotate(${angle})`,style:{transformOrigin:`${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`,cursor:dragging?"grabbing":"grab"},children:[jsxRuntimeExports.jsx("image",{href:staticUrl(protractorImage)}),jsxRuntimeExports.jsx("g",{transform:`translate(5, ${-centerToTopLeft[1]})`,ref:rotationHandleRef,children:jsxRuntimeExports.jsx(RotationArrow,{})})]})}function RotationArrow(){const radius=175;const angleDeg=10;const angleRad=convertDegreesToRadians(angleDeg);const endX=radius*(1-Math.cos(angleRad));const endY=radius*-Math.sin(angleRad);const rotationArrow=pathBuilder().move(0,0).circularArc(radius,endX,endY,{sweep:true}).build();const arrowhead=pathBuilder().move(-8,0).line(0,10).line(8,0).build();const targetRadius=TARGET_SIZE/2;return jsxRuntimeExports.jsxs("g",{className:"protractor-rotation-handle",children:[jsxRuntimeExports.jsx("path",{className:"protractor-rotation-handle-arrow-arc",d:rotationArrow}),jsxRuntimeExports.jsx("path",{className:"protractor-rotation-handle-arrowhead",d:arrowhead}),jsxRuntimeExports.jsx("path",{className:"protractor-rotation-handle-arrowhead",d:arrowhead,transform:`translate(${endX}, ${endY}), rotate(${180+angleDeg})`}),jsxRuntimeExports.jsx("ellipse",{cx:"0px",cy:"-15px",rx:targetRadius,ry:targetRadius,fill:"none"})]})}const protractorRadius=vec.mag(centerToRotationHandle);function constrainToCircle(edgePoint){return vec.withMag(edgePoint,protractorRadius)}function useDraggablePx(args){const{gestureTarget:target,onMove,point,constrain=p=>p}=args;const pickupPx=React.useRef([0,0]);useDrag(state=>{const{event,first,movement:pixelMovement}=state;event?.stopPropagation();if(first){pickupPx.current=point;}if(vec.mag(pixelMovement)===0){return}onMove?.(constrain(vec.add(pickupPx.current,pixelMovement)));},{target,eventOptions:{passive:false}});}
1996
2000
 
1997
- const GRAPH_LEFT_MARGIN=20;const MafsGraph=props=>{const{state,dispatch,labels,labelLocation,readOnly,fullGraphAriaLabel,fullGraphAriaDescription,widgetId}=props;const{type}=state;const[width,height]=props.box;const tickStep=props.step;const uniqueId=React.useId();const descriptionId=`interactive-graph-description-${uniqueId}`;const interactiveElementsDescriptionId=`interactive-graph-interactive-elements-description-${uniqueId}`;const unlimitedGraphKeyboardPromptId=`unlimited-graph-keyboard-prompt-${uniqueId}`;const instructionsId=`instructions-${uniqueId}`;const graphRef=React.useRef(null);const{analytics}=useDependencies();const i18n=usePerseusI18n();const{strings}=i18n;const interactionPrompt=isUnlimitedGraphState(state)&&state.showKeyboardInteractionInvitation;useOnMountEffect(()=>{analytics.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:type,widgetType:"interactive-graph",widgetId:widgetId}});});const{graph,interactiveElementsDescription}=renderGraphElements({state,dispatch,i18n,markings:props.markings});const disableInteraction=readOnly||!!props.static;const graphInfo={range:state.range,width,height};const[xAxisLabelLocation,yAxisLabelLocation]=getLabelPosition(graphInfo,labelLocation,tickStep);const needsExtraMargin=labelLocation==="alongEdge"&&yAxisLabelLocation[0]<-14*fontSizeYAxisLabelMultiplier;const marginLabelDiff=GRAPH_LEFT_MARGIN-fontSize*fontSizeYAxisLabelMultiplier;const marginWithExtraOffset=-1*(yAxisLabelLocation[X]-marginLabelDiff);const showsAxisLabels=props.markings==="graph"||props.markings==="axes";const hasXAxisLabel=!!(labels[0]&&labels[0].trim());const graphMarginBottom=getGraphBottomMargin(xAxisLabelLocation[Y],height,hasXAxisLabel,showsAxisLabels);return jsxRuntimeExports.jsx(GraphConfigContext.Provider,{value:{range:state.range,snapStep:state.snapStep,markings:props.markings,tickStep:tickStep,gridStep:props.gridStep,showTooltips:!!props.showTooltips,showAxisArrows:props.showAxisArrows,showAxisTicks:props.showAxisTicks,graphDimensionsInPixels:props.box,width,height,labels,labelLocation,disableKeyboardInteraction:disableInteraction,interactiveColor:disableInteraction?"var(--static-gray)":"var(--mafs-blue)"},children:jsxRuntimeExports.jsxs(View,{className:"mafs-graph-container",children:[jsxRuntimeExports.jsxs(View,{className:"mafs-graph",style:{position:"relative",padding:"25px 25px 0 0",boxSizing:"content-box",marginLeft:needsExtraMargin?`${marginWithExtraOffset}px`:`${GRAPH_LEFT_MARGIN}px`,marginBottom:graphMarginBottom,pointerEvents:props.static?"none":"auto",userSelect:"none",width,height},onKeyUp:event=>{handleKeyboardEvent(event,state,dispatch);},"aria-label":fullGraphAriaLabel,"aria-describedby":describedByIds(fullGraphAriaDescription&&descriptionId,interactiveElementsDescription&&interactiveElementsDescriptionId,isUnlimitedGraphState(state)&&unlimitedGraphKeyboardPromptId,state.type!=="none"&&!disableInteraction&&instructionsId),ref:graphRef,tabIndex:0,onFocus:event=>{handleFocusEvent(event,state,dispatch);},onBlur:event=>{handleBlurEvent(event,state,dispatch);},children:[fullGraphAriaDescription&&jsxRuntimeExports.jsx(View,{id:descriptionId,tabIndex:-1,className:"mafs-sr-only",children:fullGraphAriaDescription}),interactiveElementsDescription&&jsxRuntimeExports.jsx(View,{id:interactiveElementsDescriptionId,tabIndex:-1,className:"mafs-sr-only",children:interactiveElementsDescription}),state.type!=="none"&&jsxRuntimeExports.jsx(View,{id:instructionsId,tabIndex:-1,className:"mafs-sr-only",children:isUnlimitedGraphState(state)?strings.srUnlimitedGraphInstructions:strings.srGraphInstructions}),jsxRuntimeExports.jsx(LegacyGrid,{box:props.box,backgroundImage:props.backgroundImage}),jsxRuntimeExports.jsxs(View,{style:{position:"absolute",bottom:0,left:0},children:[(props.markings==="graph"||props.markings==="axes")&&jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment,{children:jsxRuntimeExports.jsx(AxisLabels,{i18n:i18n,xAxisLabelLocation:xAxisLabelLocation,yAxisLabelLocation:yAxisLabelLocation})}),jsxRuntimeExports.jsx(View,{"aria-hidden":props.lockedFigures.length===0,children:jsxRuntimeExports.jsxs(Mafs,{preserveAspectRatio:false,viewBox:{x:state.range[X],y:state.range[Y],padding:0},pan:false,zoom:false,width:width,height:height,children:[jsxRuntimeExports.jsx(SvgDefs,{}),jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(Grid,{gridStep:props.gridStep,range:state.range,containerSizeClass:props.containerSizeClass,markings:props.markings,width:width,height:height})}),(props.markings==="graph"||props.markings==="axes")&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(AxisTicks,{}),jsxRuntimeExports.jsx(AxisArrows,{})]}),props.lockedFigures.length>0&&jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(GraphLockedLayer,{lockedFigures:props.lockedFigures,range:state.range})})]})}),jsxRuntimeExports.jsx(GraphLockedLabelsLayer,{lockedFigures:props.lockedFigures}),jsxRuntimeExports.jsx(View,{style:{position:"absolute"},children:jsxRuntimeExports.jsxs(Mafs,{preserveAspectRatio:false,viewBox:{x:state.range[X],y:state.range[Y],padding:0},pan:false,zoom:false,width:width,height:height,children:[props.showProtractor&&jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(Protractor,{})}),graph]})})]}),interactionPrompt&&jsxRuntimeExports.jsx(View,{style:{display:interactionPrompt?undefined:"hidden",textAlign:"center",backgroundColor:semanticColor.core.background.base.default,border:`1px solid ${semanticColor.core.border.neutral.subtle}`,padding:"16px 0",boxShadow:"0px 8px 8px 0px #21242C14",top:"50%",transform:"translateY(-50%)"},children:jsxRuntimeExports.jsx(BodyText,{id:unlimitedGraphKeyboardPromptId,children:strings.graphKeyboardPrompt})})]}),renderGraphControls({state,dispatch,width,perseusStrings:strings})]})})};const renderPointGraphControls=props=>{const{interactionMode,showRemovePointButton,focusedPointIndex}=props.state;const{perseusStrings}=props;const shouldShowRemoveButton=showRemovePointButton&&focusedPointIndex!==null;return jsxRuntimeExports.jsxs(View,{style:{flexDirection:"row",width:props.width},children:[interactionMode==="keyboard"&&jsxRuntimeExports.jsx(Button,{kind:"secondary",style:{width:"100%",marginLeft:"20px"},tabIndex:0,onClick:()=>{props.dispatch(actions.pointGraph.addPoint([0,0]));},children:perseusStrings.addPoint}),interactionMode==="mouse"&&jsxRuntimeExports.jsx(Button,{id:REMOVE_BUTTON_ID,kind:"secondary",actionType:"destructive",tabIndex:-1,style:{width:"100%",marginLeft:"20px",visibility:shouldShowRemoveButton?"visible":"hidden"},onClick:_event=>{props.dispatch(actions.pointGraph.removePoint(props.state.focusedPointIndex));},children:perseusStrings.removePoint})]})};const renderPolygonGraphControls=props=>{const{interactionMode,showRemovePointButton,focusedPointIndex,closedPolygon,coords}=props.state;const{perseusStrings}=props;const shouldShowRemoveButton=showRemovePointButton&&focusedPointIndex!==null;const disableCloseButton=getArrayWithoutDuplicates(coords).length<3;const polygonButton=closedPolygon?jsxRuntimeExports.jsx(Button,{kind:"secondary",style:{width:"100%",marginLeft:"20px"},tabIndex:0,onClick:()=>{props.dispatch(actions.polygon.openPolygon());},children:perseusStrings.openPolygon}):jsxRuntimeExports.jsx(Button,{kind:"secondary",disabled:disableCloseButton,style:{width:"100%",marginLeft:"20px"},tabIndex:disableCloseButton?-1:0,onClick:()=>{props.dispatch(actions.polygon.closePolygon());},children:perseusStrings.closePolygon});return jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment,{children:jsxRuntimeExports.jsxs(View,{style:{flexDirection:"row",width:props.width},children:[interactionMode==="keyboard"&&jsxRuntimeExports.jsx(Button,{kind:"secondary",style:{width:"100%",marginLeft:"20px"},disabled:closedPolygon,tabIndex:closedPolygon?-1:0,onClick:()=>{props.dispatch(actions.polygon.addPoint([0,0]));},children:perseusStrings.addPoint}),interactionMode==="mouse"&&jsxRuntimeExports.jsx(Button,{id:REMOVE_BUTTON_ID,kind:"secondary",actionType:"destructive",disabled:closedPolygon||!shouldShowRemoveButton,tabIndex:-1,style:{width:"100%",marginLeft:"20px"},onClick:_event=>{props.dispatch(actions.polygon.removePoint(props.state.focusedPointIndex));},children:perseusStrings.removePoint}),polygonButton]})})};const renderGraphControls=props=>{const{state,dispatch,width,perseusStrings}=props;const{type}=state;switch(type){case "point":if(state.numPoints==="unlimited"){return renderPointGraphControls({state,dispatch,width,perseusStrings})}return null;case "polygon":if(state.numSides==="unlimited"){return renderPolygonGraphControls({state,dispatch,width,perseusStrings})}return null;default:return null}};function handleFocusEvent(event,state,dispatch){if(isUnlimitedGraphState(state)){if(event.target.classList.contains("mafs-graph")&&state.interactionMode==="mouse"){dispatch(actions.global.changeKeyboardInvitationVisibility(true));}}}function handleBlurEvent(_event,state,dispatch){if(isUnlimitedGraphState(state)){dispatch(actions.global.changeKeyboardInvitationVisibility(false));}}function handleKeyboardEvent(event,state,dispatch){if(isUnlimitedGraphState(state)){if(event.key==="Backspace"||event.key==="Delete"){if(document.activeElement?.classList.contains("movable-point__focusable-handle")){if(state.type==="point"||state.type==="polygon"&&!state.closedPolygon){dispatch(actions.global.deleteIntent());}}document.activeElement.blur();}else if(event.shiftKey&&event.key==="Enter"){dispatch(actions.global.changeInteractionMode("keyboard"));}else if(state.interactionMode==="keyboard"&&event.key==="a"){dispatch(actions.pointGraph.addPoint([0,0]));}}}const renderGraphElements=props=>{const{state,dispatch,i18n,markings}=props;const{type}=state;switch(type){case "angle":return renderAngleGraph(state,dispatch,i18n);case "segment":return renderSegmentGraph(state,dispatch,i18n);case "linear-system":return renderLinearSystemGraph(state,dispatch,i18n);case "linear":return renderLinearGraph(state,dispatch,i18n);case "ray":return renderRayGraph(state,dispatch,i18n);case "polygon":return renderPolygonGraph(state,dispatch,i18n,markings);case "point":return renderPointGraph(state,dispatch,i18n);case "circle":return renderCircleGraph(state,dispatch,i18n);case "quadratic":return renderQuadraticGraph(state,dispatch,i18n);case "sinusoid":return renderSinusoidGraph(state,dispatch,i18n);case "exponential":return renderExponentialGraph(state,dispatch,i18n);case "none":return {graph:null,interactiveElementsDescription:null};case "absolute-value":return renderAbsoluteValueGraph(state,dispatch,i18n);case "tangent":return renderTangentGraph(state,dispatch,i18n);case "logarithm":return renderLogarithmGraph(state,dispatch,i18n);case "vector":return renderVectorGraph(state,dispatch,i18n);default:throw new UnreachableCaseError(type)}};function describedByIds(...args){return args.filter(Boolean).join(" ")||undefined}
2001
+ const GRAPH_LEFT_MARGIN=20;const MafsGraph=props=>{const{state,dispatch,labels,labelLocation,readOnly,fullGraphAriaLabel,fullGraphAriaDescription,widgetId}=props;const{type}=state;const[width,height]=props.box;const tickStep=props.step;const uniqueId=React.useId();const descriptionId=`interactive-graph-description-${uniqueId}`;const interactiveElementsDescriptionId=`interactive-graph-interactive-elements-description-${uniqueId}`;const unlimitedGraphKeyboardPromptId=`unlimited-graph-keyboard-prompt-${uniqueId}`;const instructionsId=`instructions-${uniqueId}`;const graphRef=React.useRef(null);const{analytics}=useDependencies();const i18n=usePerseusI18n();const{strings}=i18n;const interactionPrompt=isUnlimitedGraphState(state)&&state.showKeyboardInteractionInvitation;useOnMountEffect(()=>{analytics.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:type,widgetType:"interactive-graph",widgetId:widgetId}});});const{graph,interactiveElementsDescription}=renderGraphElements({state,dispatch,i18n,markings:props.markings});const disableInteraction=readOnly||!!props.static;const graphInfo={range:state.range,width,height};const[xAxisLabelLocation,yAxisLabelLocation]=getLabelPosition(graphInfo,labelLocation,tickStep);const needsExtraMargin=labelLocation==="alongEdge"&&yAxisLabelLocation[0]<-14*fontSizeYAxisLabelMultiplier;const marginLabelDiff=GRAPH_LEFT_MARGIN-fontSize*fontSizeYAxisLabelMultiplier;const marginWithExtraOffset=-1*(yAxisLabelLocation[X]-marginLabelDiff);const showsAxisLabels=props.markings==="graph"||props.markings==="axes";const hasXAxisLabel=!!(labels[0]&&labels[0].trim());const graphMarginBottom=getGraphBottomMargin(xAxisLabelLocation[Y],height,hasXAxisLabel,showsAxisLabels);return jsxRuntimeExports.jsx(GraphConfigContext.Provider,{value:{range:state.range,snapStep:state.snapStep,markings:props.markings,tickStep:tickStep,gridStep:props.gridStep,showTooltips:!!props.showTooltips,showAxisArrows:props.showAxisArrows,showAxisTicks:props.showAxisTicks,graphDimensionsInPixels:props.box,width,height,labels,labelLocation,disableKeyboardInteraction:disableInteraction,interactiveColor:disableInteraction?"var(--static-gray)":"var(--mafs-blue)"},children:jsxRuntimeExports.jsxs(View,{className:"mafs-graph-container",children:[jsxRuntimeExports.jsxs(View,{className:"mafs-graph",role:"figure",style:{position:"relative",padding:"25px 25px 0 0",boxSizing:"content-box",marginLeft:needsExtraMargin?`${marginWithExtraOffset}px`:`${GRAPH_LEFT_MARGIN}px`,marginBottom:graphMarginBottom,pointerEvents:props.static?"none":"auto",userSelect:"none",width,height},onKeyUp:event=>{handleKeyboardEvent(event,state,dispatch);},"aria-label":fullGraphAriaLabel,"aria-describedby":describedByIds(fullGraphAriaDescription&&descriptionId,interactiveElementsDescription&&interactiveElementsDescriptionId,isUnlimitedGraphState(state)&&unlimitedGraphKeyboardPromptId,state.type!=="none"&&!disableInteraction&&instructionsId),ref:graphRef,tabIndex:0,onFocus:event=>{handleFocusEvent(event,state,dispatch);},onBlur:event=>{handleBlurEvent(event,state,dispatch);},children:[fullGraphAriaDescription&&jsxRuntimeExports.jsx(View,{id:descriptionId,tabIndex:-1,className:"mafs-sr-only",children:fullGraphAriaDescription}),interactiveElementsDescription&&jsxRuntimeExports.jsx(View,{id:interactiveElementsDescriptionId,tabIndex:-1,className:"mafs-sr-only",children:interactiveElementsDescription}),state.type!=="none"&&jsxRuntimeExports.jsx(View,{id:instructionsId,tabIndex:-1,className:"mafs-sr-only",children:isUnlimitedGraphState(state)?strings.srUnlimitedGraphInstructions:strings.srGraphInstructions}),jsxRuntimeExports.jsx(LegacyGrid,{box:props.box,backgroundImage:props.backgroundImage}),jsxRuntimeExports.jsxs(View,{style:{position:"absolute",bottom:0,left:0},children:[(props.markings==="graph"||props.markings==="axes")&&jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment,{children:jsxRuntimeExports.jsx(AxisLabels,{i18n:i18n,xAxisLabelLocation:xAxisLabelLocation,yAxisLabelLocation:yAxisLabelLocation})}),jsxRuntimeExports.jsx(View,{"aria-hidden":props.lockedFigures.length===0,children:jsxRuntimeExports.jsxs(Mafs,{preserveAspectRatio:false,viewBox:{x:state.range[X],y:state.range[Y],padding:0},pan:false,zoom:false,width:width,height:height,children:[jsxRuntimeExports.jsx(SvgDefs,{}),jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(Grid,{gridStep:props.gridStep,range:state.range,containerSizeClass:props.containerSizeClass,markings:props.markings,width:width,height:height})}),(props.markings==="graph"||props.markings==="axes")&&jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(AxisTicks,{}),jsxRuntimeExports.jsx(AxisArrows,{})]}),props.lockedFigures.length>0&&jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(GraphLockedLayer,{lockedFigures:props.lockedFigures,range:state.range})})]})}),jsxRuntimeExports.jsx(GraphLockedLabelsLayer,{lockedFigures:props.lockedFigures}),jsxRuntimeExports.jsx(View,{style:{position:"absolute"},children:jsxRuntimeExports.jsxs(Mafs,{preserveAspectRatio:false,viewBox:{x:state.range[X],y:state.range[Y],padding:0},pan:false,zoom:false,width:width,height:height,children:[props.showProtractor&&jsxRuntimeExports.jsx(ClipToGraphBounds,{children:jsxRuntimeExports.jsx(Protractor,{})}),graph]})})]}),interactionPrompt&&jsxRuntimeExports.jsx(View,{style:{display:interactionPrompt?undefined:"hidden",textAlign:"center",backgroundColor:semanticColor.core.background.base.default,border:`1px solid ${semanticColor.core.border.neutral.subtle}`,padding:"16px 0",boxShadow:"0px 8px 8px 0px #21242C14",top:"50%",transform:"translateY(-50%)"},children:jsxRuntimeExports.jsx(BodyText,{id:unlimitedGraphKeyboardPromptId,children:strings.graphKeyboardPrompt})})]}),renderGraphControls({state,dispatch,width,perseusStrings:strings})]})})};const renderPointGraphControls=props=>{const{interactionMode,showRemovePointButton,focusedPointIndex}=props.state;const{perseusStrings}=props;const shouldShowRemoveButton=showRemovePointButton&&focusedPointIndex!==null;return jsxRuntimeExports.jsxs(View,{style:{flexDirection:"row",width:props.width},children:[interactionMode==="keyboard"&&jsxRuntimeExports.jsx(Button,{kind:"secondary",style:{width:"100%",marginLeft:"20px"},tabIndex:0,onClick:()=>{props.dispatch(actions.pointGraph.addPoint([0,0]));},children:perseusStrings.addPoint}),interactionMode==="mouse"&&jsxRuntimeExports.jsx(Button,{id:REMOVE_BUTTON_ID,kind:"secondary",actionType:"destructive",tabIndex:-1,style:{width:"100%",marginLeft:"20px",visibility:shouldShowRemoveButton?"visible":"hidden"},onClick:_event=>{props.dispatch(actions.pointGraph.removePoint(props.state.focusedPointIndex));},children:perseusStrings.removePoint})]})};const renderPolygonGraphControls=props=>{const{interactionMode,showRemovePointButton,focusedPointIndex,closedPolygon,coords}=props.state;const{perseusStrings}=props;const shouldShowRemoveButton=showRemovePointButton&&focusedPointIndex!==null;const disableCloseButton=getArrayWithoutDuplicates(coords).length<3;const polygonButton=closedPolygon?jsxRuntimeExports.jsx(Button,{kind:"secondary",style:{width:"100%",marginLeft:"20px"},tabIndex:0,onClick:()=>{props.dispatch(actions.polygon.openPolygon());},children:perseusStrings.openPolygon}):jsxRuntimeExports.jsx(Button,{kind:"secondary",disabled:disableCloseButton,style:{width:"100%",marginLeft:"20px"},tabIndex:disableCloseButton?-1:0,onClick:()=>{props.dispatch(actions.polygon.closePolygon());},children:perseusStrings.closePolygon});return jsxRuntimeExports.jsx(jsxRuntimeExports.Fragment,{children:jsxRuntimeExports.jsxs(View,{style:{flexDirection:"row",width:props.width},children:[interactionMode==="keyboard"&&jsxRuntimeExports.jsx(Button,{kind:"secondary",style:{width:"100%",marginLeft:"20px"},disabled:closedPolygon,tabIndex:closedPolygon?-1:0,onClick:()=>{props.dispatch(actions.polygon.addPoint([0,0]));},children:perseusStrings.addPoint}),interactionMode==="mouse"&&jsxRuntimeExports.jsx(Button,{id:REMOVE_BUTTON_ID,kind:"secondary",actionType:"destructive",disabled:closedPolygon||!shouldShowRemoveButton,tabIndex:-1,style:{width:"100%",marginLeft:"20px"},onClick:_event=>{props.dispatch(actions.polygon.removePoint(props.state.focusedPointIndex));},children:perseusStrings.removePoint}),polygonButton]})})};const renderGraphControls=props=>{const{state,dispatch,width,perseusStrings}=props;const{type}=state;switch(type){case "point":if(state.numPoints==="unlimited"){return renderPointGraphControls({state,dispatch,width,perseusStrings})}return null;case "polygon":if(state.numSides==="unlimited"){return renderPolygonGraphControls({state,dispatch,width,perseusStrings})}return null;default:return null}};function handleFocusEvent(event,state,dispatch){if(isUnlimitedGraphState(state)){if(event.target.classList.contains("mafs-graph")&&state.interactionMode==="mouse"){dispatch(actions.global.changeKeyboardInvitationVisibility(true));}}}function handleBlurEvent(_event,state,dispatch){if(isUnlimitedGraphState(state)){dispatch(actions.global.changeKeyboardInvitationVisibility(false));}}function handleKeyboardEvent(event,state,dispatch){if(isUnlimitedGraphState(state)){if(event.key==="Backspace"||event.key==="Delete"){if(document.activeElement?.classList.contains("movable-point__focusable-handle")){if(state.type==="point"||state.type==="polygon"&&!state.closedPolygon){dispatch(actions.global.deleteIntent());}}document.activeElement.blur();}else if(event.shiftKey&&event.key==="Enter"){dispatch(actions.global.changeInteractionMode("keyboard"));}else if(state.interactionMode==="keyboard"&&event.key==="a"){dispatch(actions.pointGraph.addPoint([0,0]));}}}const renderGraphElements=props=>{const{state,dispatch,i18n,markings}=props;const{type}=state;switch(type){case "angle":return renderAngleGraph(state,dispatch,i18n);case "segment":return renderSegmentGraph(state,dispatch,i18n);case "linear-system":return renderLinearSystemGraph(state,dispatch,i18n);case "linear":return renderLinearGraph(state,dispatch,i18n);case "ray":return renderRayGraph(state,dispatch,i18n);case "polygon":return renderPolygonGraph(state,dispatch,i18n,markings);case "point":return renderPointGraph(state,dispatch,i18n);case "circle":return renderCircleGraph(state,dispatch,i18n);case "quadratic":return renderQuadraticGraph(state,dispatch,i18n);case "sinusoid":return renderSinusoidGraph(state,dispatch,i18n);case "exponential":return renderExponentialGraph(state,dispatch,i18n);case "none":return {graph:null,interactiveElementsDescription:null};case "absolute-value":return renderAbsoluteValueGraph(state,dispatch,i18n);case "tangent":return renderTangentGraph(state,dispatch,i18n);case "logarithm":return renderLogarithmGraph(state,dispatch,i18n);case "vector":return renderVectorGraph(state,dispatch,i18n);default:throw new UnreachableCaseError(type)}};function describedByIds(...args){return args.filter(Boolean).join(" ")||undefined}
1998
2002
 
1999
2003
  function mafsStateToInteractiveGraph(state,originalGraph){switch(state.type){case "angle":invariant(originalGraph.type==="angle");return {...originalGraph,coords:state.coords};case "quadratic":invariant(originalGraph.type==="quadratic");return {...originalGraph,coords:state.coords};case "circle":invariant(originalGraph.type==="circle");return {...originalGraph,center:state.center,radius:getRadius(state)};case "linear":invariant(originalGraph.type==="linear");return {...originalGraph,coords:state.coords};case "ray":invariant(originalGraph.type==="ray");return {...originalGraph,coords:state.coords};case "sinusoid":invariant(originalGraph.type==="sinusoid");return {...originalGraph,coords:state.coords};case "segment":invariant(originalGraph.type==="segment");return {...originalGraph,coords:state.coords};case "linear-system":invariant(originalGraph.type==="linear-system");return {...originalGraph,coords:state.coords};case "polygon":invariant(originalGraph.type==="polygon");return {...originalGraph,coords:state.coords};case "point":invariant(originalGraph.type==="point");return {...originalGraph,coords:state.coords};case "exponential":invariant(originalGraph.type==="exponential");return {...originalGraph,coords:state.coords,asymptote:state.asymptote};case "none":invariant(originalGraph.type==="none");return {...originalGraph};case "absolute-value":invariant(originalGraph.type==="absolute-value");return {...originalGraph,coords:state.coords};case "tangent":invariant(originalGraph.type==="tangent");return {...originalGraph,coords:state.coords};case "logarithm":invariant(originalGraph.type==="logarithm");return {...originalGraph,coords:state.coords,asymptote:state.asymptote};case "vector":invariant(originalGraph.type==="vector");return {...originalGraph,coords:state.coords};default:throw new UnreachableCaseError(state)}}
2000
2004
 
@@ -2016,7 +2020,7 @@ function shouldReduceMotion(){if(typeof window.matchMedia!=="function"){return t
2016
2020
 
2017
2021
  function isAnswerful(marker){return marker.answers!=null}function getComputedSelectedState(marker,userInputMarker,reviewMode,showSolutions){const shouldShowFeedback=showSolutions==="all"||reviewMode;if(!shouldShowFeedback){return userInputMarker}if(isAnswerful(marker)){return {...userInputMarker,selected:marker.answers}}else {return {...userInputMarker,selected:undefined}}}class LabelImage extends React.Component{static pointInTriangle(p,a,b,c){const sign=(p1,p2,p3)=>(p1.x-p3.x)*(p2.y-p3.y)-(p2.x-p3.x)*(p1.y-p3.y);const b1=sign(p,a,b)<0;const b2=sign(p,b,c)<0;const b3=sign(p,c,a)<0;return b1===b2&&b2===b3}static imageSideForMarkerPosition(x,y,preferredDirection){if(preferredDirection&&preferredDirection!=="NONE"){if(preferredDirection==="LEFT"&&x>20){return "right"}else if(preferredDirection==="RIGHT"&&x<80){return "left"}else if(preferredDirection==="UP"&&y>20){return "bottom"}else if(preferredDirection==="DOWN"&&y<80){return "top"}}if(x<20){return "left"}if(x>80){return "right"}const tl={x:20,y:0};const tr={x:80,y:0};const br={x:80,y:100};const bl={x:20,y:100};const cp={x:50,y:50};const sides=["top","right","bottom","left"];const triangles={top:[tl,tr,cp],right:[cp,tr,br],bottom:[bl,cp,br],left:[tl,cp,bl]};const p={x,y};for(const side of sides){const corners=triangles[side];if(LabelImage.pointInTriangle(p,...corners)){return side}}return "center"}static navigateToMarkerIndex(navigateDirection,markers,thisIndex){const thisMarker=markers[thisIndex];const sortedMarkers=markers.map((otherMarker,index)=>{const x=otherMarker.x-thisMarker.x;const y=otherMarker.y-thisMarker.y;const dist=Math.sqrt(x**2+y**2);return {index,dist,dir:{x:dist!==0?x/dist:0,y:dist!==0?y/dist:0}}}).filter(marker=>{if(marker.index===thisIndex){return false}return markers[marker.index].showCorrectness!=="correct"}).sort((a,b)=>{const distA=Math.round(a.dist*(navigateDirection.x*a.dir.x+navigateDirection.y*a.dir.y));const distB=Math.round(b.dist*(navigateDirection.x*b.dir.x+navigateDirection.y*b.dir.y));let dirA;let dirB;if(navigateDirection.x>0){dirA=a.dir.x>0&&distA!==0;dirB=b.dir.x>0&&distB!==0;}else if(navigateDirection.x<0){dirA=a.dir.x<0&&distA!==0;dirB=b.dir.x<0&&distB!==0;}else if(navigateDirection.y>0){dirA=a.dir.y>0&&distA!==0;dirB=b.dir.y>0&&distB!==0;}else if(navigateDirection.y<0){dirA=a.dir.y<0&&distA!==0;dirB=b.dir.y<0&&distB!==0;}if(dirA!==dirB){if(dirA){return -1}return 1}return distA-distB});return sortedMarkers.length>0?sortedMarkers[0].index:thisIndex}componentDidMount(){this.props.analytics?.onAnalyticsEvent({type:"perseus:widget:rendered:ti",payload:{widgetSubType:"null",widgetType:"label-image",widgetId:this.props.widgetId}});this._mounted=true;}componentWillUnmount(){this._mounted=false;}getPromptJSON(){return getPromptJSON$a(this.props)}handleMarkerChange(index,marker){const{userInput,handleUserInput}=this.props;const updatedUserInput=[...userInput.markers.slice(0,index),{label:marker.label,selected:marker.selected},...userInput.markers.slice(index+1)];handleUserInput({markers:updatedUserInput});}activateMarker(index,opened){this.props.analytics?.onAnalyticsEvent({type:"perseus:label-image:marker-interacted-with",payload:null});this.props.analytics?.onAnalyticsEvent({type:"perseus:label-image:marker-interacted-with:ti",payload:null});const{activeMarkerIndex}=this.state;if(activeMarkerIndex!==index&&opened){this.setState({activeMarkerIndex:index,markersInteracted:true});}else {this.setState({activeMarkerIndex:-1});}}handleMarkerKeyDown(index,e){const{markers}=this.props;if(markers.length<2){return}const directions={ArrowUp:{x:0,y:-1},ArrowRight:{x:1,y:0},ArrowDown:{x:0,y:1},ArrowLeft:{x:-1,y:0}};if(!(e.key in directions)){return}const navigateDirection=directions[e.key];e.preventDefault();const marker=this._markers[LabelImage.navigateToMarkerIndex(navigateDirection,markers,index)];if(marker){ReactDOM.findDOMNode(marker).focus();}}handleAnswerChoicesChangeForMarker(index,selection){const{choices,markers}=this.props;const selected=choices.filter((_,index)=>selection[index]);this.handleMarkerChange(index,{...markers[index],selected:selected.length>0?selected:undefined});}renderMarkers(){const{markers,preferredPopoverDirection,userInput}=this.props;const{markersInteracted,activeMarkerIndex}=this.state;const isNarrowPage=this._mounted&&window.matchMedia(mediaQueries.xsOrSmaller.replace("@media ","")).matches;const isWideImage=this.props.imageWidth/2>this.props.imageHeight;return markers.map((marker,index)=>{const userInputMarker=userInput.markers[index];let side;let markerPosition;if(isNarrowPage||isWideImage){side=marker.y>50?"top":"bottom";markerPosition=marker.y>50?"bottom":"top";}else {markerPosition=LabelImage.imageSideForMarkerPosition(marker.x,marker.y,preferredPopoverDirection);if(markerPosition==="center"){markerPosition="bottom";}side=({left:"right",top:"bottom",right:"left",bottom:"top"})[markerPosition];}const computedSelectedState=getComputedSelectedState(marker,userInputMarker,this.props.reviewMode,this.props.showSolutions);let score;if(isAnswerful(marker)){score=scoreLabelImageMarker(computedSelectedState.selected,marker.answers);}else {score={hasAnswers:false,isCorrect:false};}const shouldShowFeedback=this.props.showSolutions==="all"||this.props.reviewMode;const showCorrectness=shouldShowFeedback&&score.isCorrect?"correct":marker.showCorrectness;const disabled=shouldShowFeedback;const isActiveAnswerChoice=activeMarkerIndex===index;const showAnswerChoice=computedSelectedState.selected&&!this.state.hideAnswers&&!isActiveAnswerChoice;const adjustPillDistance={[`margin${markerPosition.charAt(0).toUpperCase()+markerPosition.slice(1)}`]:10};return jsxRuntimeExports.jsx(View,{style:{position:"absolute",left:`${marker.x}%`,top:`${marker.y}%`,zIndex:"unset"},children:jsxRuntimeExports.jsx(AnswerChoices,{choices:this.props.choices.map(choice=>({content:choice,checked:computedSelectedState.selected?computedSelectedState.selected.includes(choice):false})),multipleSelect:this.props.multipleAnswers,onChange:selection=>{this.props.analytics?.onAnalyticsEvent({type:"perseus:label-image:choiced-interacted-with",payload:null});this.props.analytics?.onAnalyticsEvent({type:"perseus:label-image:choiced-interacted-with:ti",payload:null});this.handleAnswerChoicesChangeForMarker(index,selection);},onToggle:opened=>this.activateMarker(index,opened),disabled:disabled,opener:({opened})=>jsxRuntimeExports.jsx(Clickable,{role:"button","aria-expanded":opened,children:({hovered,focused,pressed})=>jsxRuntimeExports.jsx(Marker,{label:marker.label,showCorrectness:showCorrectness,showSelected:opened,showPulsate:!markersInteracted,ref:node=>this._markers[index]=node,showAnswer:showAnswerChoice,answerSide:side,answerStyles:adjustPillDistance,focused:focused||pressed,hovered:hovered,selected:computedSelectedState.selected})},`marker-${marker.x}.${marker.y}`)},`answers-${marker.x}.${marker.y}`)},index)})}renderInstructions(){const{apiOptions:{isMobile},choices,multipleAnswers,hideChoicesFromInstructions:hideChoices}=this.props;const{strings}=this.context;const promptString=isMobile?multipleAnswers?strings.tapMultiple:strings.tapSingle:multipleAnswers?strings.clickMultiple:strings.clickSingle;const choicesString=strings.choices;return jsxRuntimeExports.jsxs("div",{className:classNames$2("perseus-label-image-widget-instructions",css(styles$9.instructions)),children:[jsxRuntimeExports.jsxs("div",{className:css(styles$9.instructionsCaption),children:[promptString," ",!hideChoices&&choicesString]}),!hideChoices&&jsxRuntimeExports.jsx("div",{className:css(styles$9.instructionsChoices),children:choices.map((choice,index)=>jsxRuntimeExports.jsx("div",{className:css(styles$9.instructionsChoice),children:jsxRuntimeExports.jsx(Renderer,{content:choice,strings:strings})},index))})]})}getSerializedState(){const{userInput,markers,...rest}=this.props;return {...rest,markers:markers.map((marker,index)=>({...marker,selected:userInput.markers[index].selected}))}}render(){const{imageAlt,imageUrl,imageWidth,imageHeight}=this.props;const{activeMarkerIndex}=this.state;return jsxRuntimeExports.jsxs("div",{children:[this.renderInstructions(),jsxRuntimeExports.jsxs("div",{className:css(styles$9.markersCanvas),style:{maxWidth:imageWidth,maxHeight:imageHeight},children:[jsxRuntimeExports.jsx("div",{className:css(styles$9.imageContainer,activeMarkerIndex!==-1&&styles$9.imageInteractionDisabled),children:jsxRuntimeExports.jsx(context$1.Consumer,{children:({setAssetStatus})=>jsxRuntimeExports.jsx(SvgImage,{alt:imageAlt,src:imageUrl,width:imageWidth,height:imageHeight,setAssetStatus:setAssetStatus,allowZoom:true})})}),this.renderMarkers()]}),jsxRuntimeExports.jsx(HideAnswersToggle,{areAnswersHidden:this.state.hideAnswers,onChange:hideAnswers=>{this.props.analytics?.onAnalyticsEvent({type:"perseus:label-image:toggle-answers-hidden",payload:null});this.props.analytics?.onAnalyticsEvent({type:"perseus:label-image:toggle-answers-hidden:ti",payload:null});this.setState({hideAnswers});}})]})}constructor(props){super(props),this._mounted=false;this._markers=[];this.state={activeMarkerIndex:-1,markersInteracted:false,hideAnswers:false};}}LabelImage.contextType=PerseusI18nContext;const LabelImageWithDependencies=React.forwardRef((props,ref)=>{const deps=useDependencies();return jsxRuntimeExports.jsx(LabelImage,{ref:ref,analytics:deps.analytics,...props})});function getStartUserInput$7(options){return {markers:options.markers.map(m=>({label:m.label}))}}function getUserInputFromSerializedState$7(serializedState){return {markers:serializedState.markers.map(m=>({label:m.label,selected:m.selected}))}}function getCorrectUserInput$3(options){return {markers:options.markers.map(marker=>({label:marker.label,selected:marker.answers}))}}var LabelImage$1 = {name:"label-image",displayName:"Label Image",widget:LabelImageWithDependencies,isLintable:true,getStartUserInput: getStartUserInput$7,getCorrectUserInput: getCorrectUserInput$3,getUserInputFromSerializedState: getUserInputFromSerializedState$7};const styles$9=StyleSheet.create({instructions:{paddingBottom:16},instructionsCaption:{...bodyXsmallBold,paddingBottom:16},instructionsChoices:{display:"flex",flexWrap:"wrap",margin:"-8px 0"},instructionsChoice:{display:"flex",alignItems:"center",margin:"8px 0",":not(:last-child)":{"::after":{content:"''",display:"inline-block",position:"relative",width:2,height:2,marginLeft:5,marginRight:5,background:semanticColor.core.border.neutral.default,borderRadius:2}}},markersCanvas:{position:"relative"},imageContainer:{display:"flex"},imageInteractionDisabled:{pointerEvents:"none"}});
2018
2022
 
2019
- const addOffsetParentScroll=($el,position)=>{const $offsetParent=$el.offsetParent();return {top:position.top+$offsetParent.scrollTop(),left:position.left+$offsetParent.scrollLeft()}};class Placeholder extends React.Component{render(){const{layout}=this.props;const className=css(styles$8.card,styles$8.placeholder,layout==="horizontal"&&styles$8.horizontalCard);const style={width:this.props.width,height:this.props.height};if(this.props.margin!=null){style.margin=this.props.margin;}return jsxRuntimeExports.jsx("li",{className:className,style:style})}}class Draggable extends React.Component{componentDidMount(){this._mounted=true;this.isMouseMoveUpBound=false;document.addEventListener("touchmove",this.onMouseMove,Util.supportsPassiveEvents()?{passive:false}:false);}componentDidUpdate(prevProps){if(this.props.state===prevProps.state){return}if(this.props.state==="animating"&&this.props.endPosition){const current=this.getCurrentPosition();const duration=15*Math.sqrt(Math.sqrt(Math.pow(this.props.endPosition.left-current.left,2)+Math.pow(this.props.endPosition.top-current.top,2)));$(ReactDOM__default.findDOMNode(this)).animate(this.props.endPosition,{duration:Math.max(duration,1),complete:this.props.onAnimationEnd});}else if(this.props.state==="static"){$(ReactDOM__default.findDOMNode(this)).finish();}}componentWillUnmount(){this._mounted=false;if(this.isMouseMoveUpBound){this.unbindMouseMoveUp();}if(this.animationFrameRequest){cancelAnimationFrame(this.animationFrameRequest);}document.removeEventListener("touchmove",this.onMouseMove);}render(){const{includePadding,layout,state:type}=this.props;let className=css(styles$8.card,styles$8.draggable,layout==="horizontal"&&styles$8.horizontalCard,layout==="vertical"&&styles$8.verticalCard,type==="dragging"&&styles$8.dragging,type==="disabled"&&styles$8.disabled,!includePadding&&styles$8.unpaddedCard)+" "+"perseus-sortable-draggable";if(!includePadding){className+=" perseus-sortable-draggable-unpadded";}const style={position:"static"};if(this.props.state==="dragging"||this.props.state==="animating"){_.extend(style,{position:"absolute"},this.getCurrentPosition());}if(this.props.width){style.width=this.props.width+1;}if(this.props.height){style.height=this.props.height;}if(this.props.margin!=null){style.margin=this.props.margin;}return jsxRuntimeExports.jsx("li",{className:className,style:style,onMouseDown:this.onMouseDown,onTouchStart:this.onMouseDown,onTouchMove:this.onMouseMove,onTouchEnd:this.onMouseUp,onTouchCancel:this.onMouseUp,children:jsxRuntimeExports.jsx(Renderer,{content:this.props.content,linterContext:PerseusLinter.pushContextStack(this.props.linterContext,"draggable"),onRender:this.props.onRender,strings:this.context.strings})})}constructor(...args){super(...args),this.animationFrameRequest=null,this.state={startPosition:{left:0,top:0},startMouse:{left:0,top:0},mouse:{left:0,top:0},dragging:false},this.getCurrentPosition=()=>{return {left:this.state.startPosition.left+this.state.mouse.left-this.state.startMouse.left,top:this.state.startPosition.top+this.state.mouse.top-this.state.startMouse.top}},this.bindMouseMoveUp=()=>{this.isMouseMoveUpBound=true;$(document).on("mousemove",this.onMouseMove);$(document).on("mouseup",this.onMouseUp);},this.unbindMouseMoveUp=()=>{this.isMouseMoveUpBound=false;$(document).off("mousemove",this.onMouseMove);$(document).off("mouseup",this.onMouseUp);},this.onMouseDown=event=>{if(this.props.state!=="static"){return}if(!(event.button===0||event.touches!=null&&event.touches.length===1)){return}event.preventDefault();const loc=Util.extractPointerLocation(event);this.animationFrameRequest=requestAnimationFrame(()=>{const $el=$(ReactDOM__default.findDOMNode(this));const position=$el.position();const startPosition=addOffsetParentScroll($el,position);if(loc&&this._mounted){this.setState({startPosition,startMouse:loc,mouse:loc,dragging:true},function(){this.bindMouseMoveUp();this.props.onMouseDown();});}});},this.onMouseMove=event=>{const notDragging=this.props.state!=="dragging"||!this.state.dragging;if(notDragging){return}event.preventDefault();const loc=Util.extractPointerLocation(event);if(loc){this.animationFrameRequest=requestAnimationFrame(()=>{this.setState({mouse:loc},this.props.onMouseMove);});}},this.onMouseUp=event=>{const notDragging=this.props.state!=="dragging"||!this.state.dragging;if(notDragging){return}event.preventDefault();const loc=Util.extractPointerLocation(event);if(loc){this.setState({dragging:false});this.unbindMouseMoveUp();this.props.onMouseUp();}};}}Draggable.contextType=PerseusI18nContext;Draggable.defaultProps={includePadding:true,type:"static",linterContext:PerseusLinter.linterContextDefault};class Sortable extends React.Component{static itemsFromProps(props){const state=props.disabled?"disabled":"static";return props.options.map((option,i)=>{return {option:option,key:i,state,width:0,height:0}})}static clearItemMeasurements(items){return items.map(item=>{return {...item,width:0,height:0}})}UNSAFE_componentWillReceiveProps(nextProps){const prevProps=this.props;if(!_.isEqual(nextProps.options,prevProps.options)){this.setState({items:Sortable.itemsFromProps(nextProps)});}else if(nextProps.layout!==prevProps.layout||nextProps.padding!==prevProps.padding||nextProps.disabled!==prevProps.disabled||!_.isEqual(nextProps.constraints,prevProps.constraints)){this.setState({items:Sortable.clearItemMeasurements(this.state.items)});}}componentDidUpdate(){if(this.state.items.length&&!this.state.items[0].width&&!this.state.items[0].height){setTimeout(()=>{this.measureItems();},0);}}measureItems(){let items=[...this.state.items];const $items=_.map(items,function(item){return $(ReactDOM__default.findDOMNode(this.refs[item.key]))},this);const widths=_.invoke($items,"outerWidth");const heights=_.invoke($items,"outerHeight");const{constraints,layout}=this.props;let syncWidth=null;if(constraints?.width){syncWidth=_.max(widths.concat(constraints.width));}else if(layout==="vertical"){syncWidth=_.max(widths);}let syncHeight=null;if(constraints?.height){syncHeight=_.max(heights.concat(constraints.height));}else if(layout==="horizontal"){syncHeight=_.max(heights);}items=_.map(items,function(item,i){item.width=syncWidth||widths[i];item.height=syncHeight||heights[i];return item});this.setState({items},()=>{this.props.onMeasure?.({widths:widths,heights:heights});});}onMouseDown(key){const items=_.map(this.state.items,function(item){if(item.key===key){item.state="dragging";}return item});this.setState({items:items});}moveOptionToIndex(option,index){const{items}=this.state;if(index<0||index>items.length){throw new Error(`index ${index} out of bounds`)}const nextItems=_.clone(items);const item=items.filter(item=>{return item.option===option})[0];if(item==null){throw new Error(`option ${option} not found`)}const currentIndex=items.findIndex(i=>{return i.key===item.key});nextItems.splice(currentIndex,1);nextItems.splice(index,0,item);this.setState({items:nextItems},()=>{this.props.onChange?.({});});}onMouseMove(key){const $draggable=$(ReactDOM__default.findDOMNode(this.refs[key]));const $sortable=$(ReactDOM__default.findDOMNode(this));const items=_.clone(this.state.items);const item=_.findWhere(this.state.items,{key:key});const margin=this.props.margin||0;const currentIndex=_.indexOf(items,item);let newIndex=0;items.splice(currentIndex,1);if(this.props.layout==="horizontal"){const midWidth=$draggable.offset().left-$sortable.offset().left;let sumWidth=0;let cardWidth;_.each(items,function(item){cardWidth=item.width;if(midWidth>sumWidth+cardWidth/2){newIndex+=1;}sumWidth+=cardWidth+margin;});}else {const midHeight=$draggable.offset().top-$sortable.offset().top;let sumHeight=0;let cardHeight;_.each(items,function(item){cardHeight=item.height;if(midHeight>sumHeight+cardHeight/2){newIndex+=1;}sumHeight+=cardHeight+margin;});}if(newIndex!==currentIndex){items.splice(newIndex,0,item);this.setState({items:items});}}onMouseUp(key){const nextAnimationFrame=requestAnimationFrame(()=>{const items=_.map(this.state.items,function(item){if(item.key===key){item.state="animating";const $placeholder=$(ReactDOM__default.findDOMNode(this.refs["placeholder_"+key]));const position=$placeholder.position();const endPosition=addOffsetParentScroll($placeholder,position);item.endPosition=endPosition;}return item},this);this.setState({items:items},()=>{this.props.onChange?.({});});});this.animationFrameRequest=nextAnimationFrame;}onAnimationEnd(key){const items=_.map(this.state.items,function(item){if(item.key===key){item.state="static";}return item});this.setState({items:items});}getOptions(){return _.pluck(this.state.items,"option")}render(){if(this.props.waitForTexRendererToLoad&&!this.state.texRendererLoaded){const{TeX}=getDependencies();return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(CircularSpinner,{}),jsxRuntimeExports.jsx("div",{style:{display:"none"},children:jsxRuntimeExports.jsx(TeX,{onRender:()=>this.setState({texRendererLoaded:true}),children:"1"})})]})}const cards=[];const{layout}=this.props;const className=css(styles$8.sortable)+" perseus-sortable";const syncWidth=this.props.constraints?.width||layout==="vertical";const syncHeight=this.props.constraints?.height||layout==="horizontal";_.each(this.state.items,function(item,i,items){const isLast=i===items.length-1;const isStatic=item.state==="static"||item.state==="disabled";let margin;if(this.props.layout==="horizontal"){margin="0 "+this.props.margin+"px 0 0";}else if(this.props.layout==="vertical"){margin="0 0 "+this.props.margin+"px 0";}cards.push(jsxRuntimeExports.jsx(Draggable,{content:item.option,state:item.state,ref:item.key,width:syncWidth?item.width:undefined,height:syncHeight?item.height:undefined,layout:layout,includePadding:this.props.padding,margin:isLast&&isStatic?0:margin,endPosition:item.endPosition,linterContext:PerseusLinter.pushContextStack(this.props.linterContext,"sortable"),onRender:this.remeasureItems,onMouseDown:this.onMouseDown.bind(this,item.key),onMouseMove:this.onMouseMove.bind(this,item.key),onMouseUp:this.onMouseUp.bind(this,item.key),onTouchMove:this.onMouseMove.bind(this,item.key),onTouchEnd:this.onMouseUp.bind(this,item.key),onTouchCancel:this.onMouseUp.bind(this,item.key),onAnimationEnd:this.onAnimationEnd.bind(this,item.key)},item.key));if(item.state==="dragging"||item.state==="animating"){cards.push(jsxRuntimeExports.jsx(Placeholder,{ref:"placeholder_"+item.key,width:item.width,height:item.height,layout:layout,margin:isLast?0:margin},"placeholder_"+item.key));}},this);return jsxRuntimeExports.jsx("ul",{className:className,children:cards})}constructor(props){super(props),this.remeasureItems=_.debounce(()=>{this.setState({items:Sortable.clearItemMeasurements(this.state.items)});},20);this.state={items:Sortable.itemsFromProps(this.props),texRendererLoaded:false};}}Sortable.defaultProps={layout:"horizontal",padding:true,disabled:false,constraints:{},onMeasure:function(){},margin:5,onChange:function(){},linterContext:PerseusLinter.linterContextDefault,waitForTexRendererToLoad:true};const styles$8=StyleSheet.create({sortable:{boxSizing:"border-box",float:"left",padding:0,margin:0},card:{boxSizing:"border-box",background:"#fff",border:"1px solid #ddd",borderRadius:4,cursor:"pointer",minWidth:25,minHeight:44,padding:10,listStyleType:"none",userSelect:"none",touchAction:"none"},placeholder:{background:"#ddd",border:"1px solid #ccc"},draggable:{textAlign:"center"},horizontalCard:{float:"left",cursor:"ew-resize"},verticalCard:{maxWidth:"100%",cursor:"ns-resize"},unpaddedCard:{padding:0},dragging:{background:"#ffedcd",opacity:.8},disabled:{backgroundColor:"inherit",border:"1px solid transparent",cursor:"default"}});
2023
+ const addOffsetParentScroll=($el,position)=>{const $offsetParent=$el.offsetParent();return {top:position.top+$offsetParent.scrollTop(),left:position.left+$offsetParent.scrollLeft()}};class Placeholder extends React.Component{render(){const{layout}=this.props;const className=css(styles$8.card,styles$8.placeholder,layout==="horizontal"&&styles$8.horizontalCard);const style={width:this.props.width,height:this.props.height};if(this.props.margin!=null){style.margin=this.props.margin;}return jsxRuntimeExports.jsx("li",{className:className,style:style})}}class Draggable extends React.Component{componentDidMount(){this._mounted=true;this.isMouseMoveUpBound=false;document.addEventListener("touchmove",this.onMouseMove,Util.supportsPassiveEvents()?{passive:false}:false);}componentDidUpdate(prevProps){if(this.props.state===prevProps.state){return}if(this.props.state==="animating"&&this.props.endPosition){const current=this.getCurrentPosition();const duration=15*Math.sqrt(Math.sqrt(Math.pow(this.props.endPosition.left-current.left,2)+Math.pow(this.props.endPosition.top-current.top,2)));$(ReactDOM__default.findDOMNode(this)).animate(this.props.endPosition,{duration:Math.max(duration,1),complete:this.props.onAnimationEnd});}else if(this.props.state==="static"){$(ReactDOM__default.findDOMNode(this)).finish();}}componentWillUnmount(){this._mounted=false;if(this.isMouseMoveUpBound){this.unbindMouseMoveUp();}if(this.animationFrameRequest){cancelAnimationFrame(this.animationFrameRequest);}document.removeEventListener("touchmove",this.onMouseMove);}render(){const{includePadding,layout,state:type}=this.props;let className=css(styles$8.card,styles$8.draggable,layout==="horizontal"&&styles$8.horizontalCard,layout==="vertical"&&styles$8.verticalCard,type==="dragging"&&styles$8.dragging,type==="disabled"&&styles$8.disabled,!includePadding&&styles$8.unpaddedCard)+" "+"perseus-sortable-draggable";if(!includePadding){className+=" perseus-sortable-draggable-unpadded";}const style={position:"static"};if(this.props.state==="dragging"||this.props.state==="animating"){_.extend(style,{position:"absolute"},this.getCurrentPosition());}if(this.props.width){style.width=this.props.width+1;}if(this.props.height){style.height=this.props.height;}if(this.props.margin!=null){style.margin=this.props.margin;}return jsxRuntimeExports.jsx("li",{className:className,style:style,onMouseDown:this.onMouseDown,onTouchStart:this.onMouseDown,onTouchMove:this.onMouseMove,onTouchEnd:this.onMouseUp,onTouchCancel:this.onMouseUp,children:jsxRuntimeExports.jsx(Renderer,{content:this.props.content,linterContext:PerseusLinter.pushContextStack(this.props.linterContext,"draggable"),onRender:this.props.onRender,strings:this.context.strings})})}constructor(...args){super(...args),this.animationFrameRequest=null,this.state={startPosition:{left:0,top:0},startMouse:{left:0,top:0},mouse:{left:0,top:0},dragging:false},this.getCurrentPosition=()=>{return {left:this.state.startPosition.left+this.state.mouse.left-this.state.startMouse.left,top:this.state.startPosition.top+this.state.mouse.top-this.state.startMouse.top}},this.bindMouseMoveUp=()=>{this.isMouseMoveUpBound=true;$(document).on("mousemove",this.onMouseMove);$(document).on("mouseup",this.onMouseUp);},this.unbindMouseMoveUp=()=>{this.isMouseMoveUpBound=false;$(document).off("mousemove",this.onMouseMove);$(document).off("mouseup",this.onMouseUp);},this.onMouseDown=event=>{if(this.props.state!=="static"){return}if(!(event.button===0||event.touches!=null&&event.touches.length===1)){return}event.preventDefault();const loc=Util.extractPointerLocation(event);this.animationFrameRequest=requestAnimationFrame(()=>{const $el=$(ReactDOM__default.findDOMNode(this));const position=$el.position();const startPosition=addOffsetParentScroll($el,position);if(loc&&this._mounted){this.setState({startPosition,startMouse:loc,mouse:loc,dragging:true},function(){this.bindMouseMoveUp();this.props.onMouseDown();});}});},this.onMouseMove=event=>{const notDragging=this.props.state!=="dragging"||!this.state.dragging;if(notDragging){return}event.preventDefault();const loc=Util.extractPointerLocation(event);if(loc){this.animationFrameRequest=requestAnimationFrame(()=>{this.setState({mouse:loc},this.props.onMouseMove);});}},this.onMouseUp=event=>{const notDragging=this.props.state!=="dragging"||!this.state.dragging;if(notDragging){return}event.preventDefault();const loc=Util.extractPointerLocation(event);if(loc){this.setState({dragging:false});this.unbindMouseMoveUp();this.props.onMouseUp();}};}}Draggable.contextType=PerseusI18nContext;Draggable.defaultProps={includePadding:true,type:"static",linterContext:PerseusLinter.linterContextDefault};class Sortable extends React.Component{static itemsFromProps(props){const state=props.disabled?"disabled":"static";return props.options.map((option,i)=>{return {option:option,key:i,state,width:0,height:0}})}static clearItemMeasurements(items){return items.map(item=>{return {...item,width:0,height:0}})}UNSAFE_componentWillReceiveProps(nextProps){const prevProps=this.props;if(!_.isEqual(nextProps.options,prevProps.options)){this.setState({items:Sortable.itemsFromProps(nextProps)});}else if(nextProps.layout!==prevProps.layout||nextProps.padding!==prevProps.padding||nextProps.disabled!==prevProps.disabled||!_.isEqual(nextProps.constraints,prevProps.constraints)){this.setState({items:Sortable.clearItemMeasurements(this.state.items)});}}componentDidUpdate(){if(this.state.items.length&&!this.state.items[0].width&&!this.state.items[0].height){setTimeout(()=>{this.measureItems();},0);}}measureItems(){let items=[...this.state.items];const $items=_.map(items,function(item){return $(ReactDOM__default.findDOMNode(this.refs[item.key]))},this);const widths=_.invoke($items,"outerWidth");const heights=_.invoke($items,"outerHeight");const{constraints,layout}=this.props;let syncWidth=null;if(constraints?.width){syncWidth=_.max(widths.concat(constraints.width));}else if(layout==="vertical"){syncWidth=_.max(widths);}let syncHeight=null;if(constraints?.height){syncHeight=_.max(heights.concat(constraints.height));}else if(layout==="horizontal"){syncHeight=_.max(heights);}items=_.map(items,function(item,i){item.width=syncWidth||widths[i];item.height=syncHeight||heights[i];return item});this.setState({items},()=>{this.props.onMeasure?.({widths:widths,heights:heights});});}onMouseDown(key){const items=_.map(this.state.items,function(item){if(item.key===key){item.state="dragging";}return item});this.setState({items:items});}moveOptionToIndex(option,index){const{items}=this.state;if(index<0||index>items.length){throw new Error(`index ${index} out of bounds`)}const nextItems=_.clone(items);const item=items.filter(item=>{return item.option===option})[0];if(item==null){throw new Error(`option ${option} not found`)}const currentIndex=items.findIndex(i=>{return i.key===item.key});nextItems.splice(currentIndex,1);nextItems.splice(index,0,item);this.setState({items:nextItems},()=>{this.props.onChange?.({});});}onMouseMove(key){const $draggable=$(ReactDOM__default.findDOMNode(this.refs[key]));const $sortable=$(ReactDOM__default.findDOMNode(this));const items=_.clone(this.state.items);const item=_.findWhere(this.state.items,{key:key});const margin=this.props.margin||0;const currentIndex=_.indexOf(items,item);let newIndex=0;items.splice(currentIndex,1);if(this.props.layout==="horizontal"){const midWidth=$draggable.offset().left-$sortable.offset().left;let sumWidth=0;let cardWidth;_.each(items,function(item){cardWidth=item.width;if(midWidth>sumWidth+cardWidth/2){newIndex+=1;}sumWidth+=cardWidth+margin;});}else {const midHeight=$draggable.offset().top-$sortable.offset().top;let sumHeight=0;let cardHeight;_.each(items,function(item){cardHeight=item.height;if(midHeight>sumHeight+cardHeight/2){newIndex+=1;}sumHeight+=cardHeight+margin;});}if(newIndex!==currentIndex){items.splice(newIndex,0,item);this.setState({items:items});}}onMouseUp(key){const nextAnimationFrame=requestAnimationFrame(()=>{const items=_.map(this.state.items,function(item){if(item.key===key){item.state="animating";const $placeholder=$(ReactDOM__default.findDOMNode(this.refs["placeholder_"+key]));const position=$placeholder.position();const endPosition=addOffsetParentScroll($placeholder,position);item.endPosition=endPosition;}return item},this);this.setState({items:items},()=>{this.props.onChange?.({});});});this.animationFrameRequest=nextAnimationFrame;}onAnimationEnd(key){const items=_.map(this.state.items,function(item){if(item.key===key){item.state="static";}return item});this.setState({items:items});}getOptions(){return _.pluck(this.state.items,"option")}render(){if(this.props.waitForTexRendererToLoad&&!this.state.texRendererLoaded){const{TeX}=getDependencies();return jsxRuntimeExports.jsxs(jsxRuntimeExports.Fragment,{children:[jsxRuntimeExports.jsx(CircularSpinner,{}),jsxRuntimeExports.jsx("div",{style:{display:"none"},children:jsxRuntimeExports.jsx(TeX,{onRender:()=>this.setState({texRendererLoaded:true}),children:"1"})})]})}const cards=[];const{layout}=this.props;const className=css(styles$8.sortable)+" perseus-sortable";const syncWidth=this.props.constraints?.width||layout==="vertical";const syncHeight=this.props.constraints?.height||layout==="horizontal";_.each(this.state.items,function(item,i,items){const isLast=i===items.length-1;const isStatic=item.state==="static"||item.state==="disabled";let margin;if(this.props.layout==="horizontal"){margin="0 "+this.props.margin+"px 0 0";}else if(this.props.layout==="vertical"){margin="0 0 "+this.props.margin+"px 0";}cards.push(jsxRuntimeExports.jsx(Draggable,{content:item.option,state:item.state,ref:item.key,width:syncWidth?item.width:undefined,height:syncHeight?item.height:undefined,layout:layout,includePadding:this.props.padding,margin:isLast&&isStatic?0:margin,endPosition:item.endPosition,linterContext:PerseusLinter.pushContextStack(this.props.linterContext,"sortable"),onRender:this.remeasureItems,onMouseDown:this.onMouseDown.bind(this,item.key),onMouseMove:this.onMouseMove.bind(this,item.key),onMouseUp:this.onMouseUp.bind(this,item.key),onTouchMove:this.onMouseMove.bind(this,item.key),onTouchEnd:this.onMouseUp.bind(this,item.key),onTouchCancel:this.onMouseUp.bind(this,item.key),onAnimationEnd:this.onAnimationEnd.bind(this,item.key)},item.key));if(item.state==="dragging"||item.state==="animating"){cards.push(jsxRuntimeExports.jsx(Placeholder,{ref:"placeholder_"+item.key,width:item.width,height:item.height,layout:layout,margin:isLast?0:margin},"placeholder_"+item.key));}},this);return jsxRuntimeExports.jsx("ul",{className:className,children:cards})}constructor(props){super(props),this.remeasureItems=_.debounce(()=>{this.setState({items:Sortable.clearItemMeasurements(this.state.items)});},20);this.state={items:Sortable.itemsFromProps(this.props),texRendererLoaded:false};}}Sortable.defaultProps={layout:"horizontal",padding:true,disabled:false,constraints:{},onMeasure:function(){},margin:5,onChange:function(){},linterContext:PerseusLinter.linterContextDefault,waitForTexRendererToLoad:true};const styles$8=StyleSheet.create({sortable:{boxSizing:"border-box",float:"left",padding:0,margin:0},card:{boxSizing:"border-box",background:semanticColor.core.background.base.default,border:`${border$1.width.thin} solid ${semanticColor.core.border.neutral.subtle}`,borderRadius:4,cursor:"pointer",minWidth:25,minHeight:44,padding:10,listStyleType:"none",userSelect:"none",touchAction:"none"},placeholder:{background:semanticColor.core.background.neutral.subtle,border:`${border$1.width.thin} solid ${semanticColor.core.border.neutral.subtle}`},draggable:{textAlign:"center"},horizontalCard:{float:"left",cursor:"ew-resize"},verticalCard:{maxWidth:"100%",cursor:"ns-resize"},unpaddedCard:{padding:0},dragging:{background:semanticColor.core.background.instructive.subtle,border:`${border$1.width.thin} solid ${semanticColor.core.border.instructive.default}`,opacity:.8},disabled:{backgroundColor:"inherit",border:`${border$1.width.thin} solid ${semanticColor.core.border.disabled.subtle}`,cursor:"default"}});
2020
2024
 
2021
2025
  const getPromptJSON$9=widgetData=>{const{userInput}=widgetData;return {type:"matcher",options:{labels:widgetData.labels,left:widgetData.left,right:widgetData.right,orderMatters:widgetData.orderMatters},userInput:{left:userInput.left,right:userInput.right}}};
2022
2026
 
@@ -2074,7 +2078,7 @@ var extraWidgets = [CSProgram$1,Categorizer$1,Definition$1,DeprecatedStandin$1,D
2074
2078
 
2075
2079
  const init=function(){registerWidgets(basicWidgets);registerWidgets(extraWidgets);replaceDeprecatedWidgets();};
2076
2080
 
2077
- const libName="@khanacademy/perseus";const libVersion="77.10.1";addLibraryVersionToPerseusDebug(libName,libVersion);
2081
+ const libName="@khanacademy/perseus";const libVersion="77.11.0";addLibraryVersionToPerseusDebug(libName,libVersion);
2078
2082
 
2079
2083
  const apiVersion={major:12,minor:0};
2080
2084