@khanacademy/perseus 54.1.0 → 55.0.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 (37) hide show
  1. package/LICENSE +18 -0
  2. package/dist/components/number-input.d.ts +9 -10
  3. package/dist/components/range-input.d.ts +5 -6
  4. package/dist/components/simple-keypad-input.d.ts +5 -0
  5. package/dist/components/stub-tag-editor.d.ts +3 -4
  6. package/dist/components/text-list-editor.d.ts +3 -4
  7. package/dist/es/index.css +16 -6
  8. package/dist/es/index.js +901 -985
  9. package/dist/es/index.js.map +1 -1
  10. package/dist/es/strings.js +40 -10
  11. package/dist/es/strings.js.map +1 -1
  12. package/dist/index.css +16 -6
  13. package/dist/index.js +1035 -1086
  14. package/dist/index.js.map +1 -1
  15. package/dist/mixins/changeable.d.ts +1 -2
  16. package/dist/perseus-api.d.ts +1 -56
  17. package/dist/shared-utils/add-library-version-to-perseus-debug.d.ts +9 -0
  18. package/dist/strings.d.ts +34 -4
  19. package/dist/strings.js +142 -97
  20. package/dist/strings.js.map +1 -1
  21. package/dist/widget-ai-utils/numeric-input/prompt-utils.d.ts +1 -1
  22. package/dist/widgets/dropdown/dropdown.d.ts +5 -4
  23. package/dist/widgets/interactive-graph.d.ts +4 -2
  24. package/dist/widgets/interactive-graphs/graphs/circle.d.ts +7 -0
  25. package/dist/widgets/interactive-graphs/graphs/components/movable-line.d.ts +6 -0
  26. package/dist/widgets/interactive-graphs/graphs/quadratic.d.ts +8 -0
  27. package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +7 -0
  28. package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +1 -1
  29. package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +6 -1
  30. package/dist/widgets/numeric-input/index.d.ts +1 -1
  31. package/dist/widgets/numeric-input/input-with-examples.d.ts +8 -0
  32. package/dist/widgets/numeric-input/numeric-input.class.d.ts +66 -0
  33. package/dist/widgets/numeric-input/numeric-input.d.ts +123 -77
  34. package/dist/widgets/numeric-input/utils.d.ts +25 -0
  35. package/package.json +120 -124
  36. package/dist/components/input-with-examples.d.ts +0 -51
  37. package/dist/components/tooltip.d.ts +0 -85
package/dist/es/index.js CHANGED
@@ -1,16 +1,17 @@
1
+ import _extends from '@babel/runtime/helpers/extends';
1
2
  import * as KAS from '@khanacademy/kas';
2
3
  import { MathInputI18nContext, CursorContext, getKeyTranslator, createMathField, getCursorContext, DesktopKeypad, convertDotToTimesByLocale, mathQuillInstance, KeypadInput, KeypadType, KeyArray, keypadElementPropType } from '@khanacademy/math-input';
3
- import { PerseusError, Errors, expressionLogic, getDecimalSeparator, approximateDeepEqual, pluck, CoreWidgetRegistry, getUpgradedWidgetOptions, mapObject, passageRefLogic, radioLogic, shuffle, random, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, shuffleMatcher, getMatrixSize, measurerLogic, addLibraryVersionToPerseusDebug, upgradeWidgetInfoToLatestVersion, getWidgetIdsFromContentByType } from '@khanacademy/perseus-core';
4
+ import { PerseusError, Errors, expressionLogic, getDecimalSeparator, approximateDeepEqual, pluck, CoreWidgetRegistry, getUpgradedWidgetOptions, mapObject, passageRefLogic, radioLogic, shuffle, random, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, shuffleMatcher, getMatrixSize, measurerLogic, upgradeWidgetInfoToLatestVersion, getWidgetIdsFromContentByType } from '@khanacademy/perseus-core';
4
5
  export { isFailure, isSuccess, parseAndMigratePerseusArticle, parseAndMigratePerseusItem, parsePerseusItem } from '@khanacademy/perseus-core';
5
6
  import * as PerseusLinter from '@khanacademy/perseus-linter';
6
7
  import { linterContextDefault, Rule } from '@khanacademy/perseus-linter';
7
8
  import { View, RenderStateRoot, Id, useOnMountEffect, useLatestRef, Text as Text$2 } from '@khanacademy/wonder-blocks-core';
8
- import Tooltip$1 from '@khanacademy/wonder-blocks-tooltip';
9
+ import Tooltip, { TooltipContent } from '@khanacademy/wonder-blocks-tooltip';
9
10
  import { HeadingMedium, LabelSmall, LabelLarge, Body, LabelMedium } from '@khanacademy/wonder-blocks-typography';
10
11
  import { StyleSheet, css } from 'aphrodite';
11
12
  import classNames$1 from 'classnames';
12
13
  import * as React from 'react';
13
- import React__default, { useContext, useState, useEffect, useRef, createContext, forwardRef, useLayoutEffect, useImperativeHandle, useId } from 'react';
14
+ import React__default, { useContext, forwardRef, useId, useImperativeHandle, useRef, useState, useEffect, createContext, useLayoutEffect } from 'react';
14
15
  import * as ReactDOM from 'react-dom';
15
16
  import ReactDOM__default from 'react-dom';
16
17
  import _, { debounce as debounce$1 } from 'underscore';
@@ -21,12 +22,16 @@ import $ from 'jquery';
21
22
  import { v4 } from 'uuid';
22
23
  import PropTypes from 'prop-types';
23
24
  import { KhanAnswerTypes, emptyWidgetsFunctional, scoreWidgetsFunctional, flattenScores, inputNumberAnswerTypes, scoreRadio, scoreLabelImageMarker } from '@khanacademy/perseus-score';
25
+ import _objectWithoutPropertiesLoose from '@babel/runtime/helpers/objectWithoutPropertiesLoose';
26
+ import { KeypadContext } from '@khanacademy/keypad-context';
27
+ import { TextField } from '@khanacademy/wonder-blocks-form';
24
28
  import { entries, UnreachableCaseError, keys } from '@khanacademy/wonder-stuff-core';
25
29
  import { CircularSpinner } from '@khanacademy/wonder-blocks-progress-spinner';
26
30
  import { point, KhanMath, number, vector as vector$3, line, geometry, angles, coefficients } from '@khanacademy/kmath';
31
+ import _classPrivateFieldLooseBase from '@babel/runtime/helpers/classPrivateFieldLooseBase';
32
+ import _classPrivateFieldLooseKey from '@babel/runtime/helpers/classPrivateFieldLooseKey';
27
33
  import SimpleMarkdown from '@khanacademy/simple-markdown';
28
34
  import { pureMarkdownRules } from '@khanacademy/pure-markdown';
29
- import { TextField } from '@khanacademy/wonder-blocks-form';
30
35
  import Button from '@khanacademy/wonder-blocks-button';
31
36
  import { Strut } from '@khanacademy/wonder-blocks-layout';
32
37
  import Banner from '@khanacademy/wonder-blocks-banner';
@@ -44,16 +49,6 @@ import IconButton from '@khanacademy/wonder-blocks-icon-button';
44
49
  import cornersOutIcon from '@phosphor-icons/core/regular/corners-out.svg';
45
50
  import Link from '@khanacademy/wonder-blocks-link';
46
51
 
47
- function _extends() {
48
- return _extends = Object.assign ? Object.assign.bind() : function (n) {
49
- for (var e = 1; e < arguments.length; e++) {
50
- var t = arguments[e];
51
- for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
52
- }
53
- return n;
54
- }, _extends.apply(null, arguments);
55
- }
56
-
57
52
  /**
58
53
  * The translated strings that are used to render Perseus.
59
54
  */
@@ -262,10 +257,14 @@ const mockStrings = {
262
257
  centerX,
263
258
  centerY
264
259
  }) => `Circle. The center point is at ${centerX} comma ${centerY}.`,
265
- srCircleRadiusPoint: ({
260
+ srCircleRadiusPointRight: ({
266
261
  radiusPointX,
267
262
  radiusPointY
268
- }) => `Radius point at ${radiusPointX} comma ${radiusPointY}.`,
263
+ }) => `Right radius endpoint at ${radiusPointX} comma ${radiusPointY}.`,
264
+ srCircleRadiusPointLeft: ({
265
+ radiusPointX,
266
+ radiusPointY
267
+ }) => `Left radius endpoint at ${radiusPointX} comma ${radiusPointY}.`,
269
268
  srCircleRadius: ({
270
269
  radius
271
270
  }) => `Circle radius is ${radius}.`,
@@ -300,13 +299,13 @@ const mockStrings = {
300
299
  xIntercept,
301
300
  yIntercept
302
301
  }) => `The line crosses the X-axis at ${xIntercept} comma 0 and the Y-axis at 0 comma ${yIntercept}.`,
303
- srLinearGraphOriginIntercept: "The line crosses the x and y axes at the graph's origin.",
302
+ srLinearGraphOriginIntercept: "The line crosses the X and Y axes at the graph's origin.",
304
303
  srLinearGrabHandle: ({
305
304
  point1X,
306
305
  point1Y,
307
306
  point2X,
308
307
  point2Y
309
- }) => `Line from ${point1X} comma ${point1Y} to ${point2X} comma ${point2Y}.`,
308
+ }) => `Line going through point ${point1X} comma ${point1Y} and point ${point2X} comma ${point2Y}.`,
310
309
  srAngleStartingSide: ({
311
310
  x,
312
311
  y
@@ -393,6 +392,18 @@ const mockStrings = {
393
392
  x,
394
393
  y
395
394
  }) => `Point ${pointSequence} on line ${lineNumber} at ${x} comma ${y}.`,
395
+ srLinearSystemGrabHandle: ({
396
+ lineNumber,
397
+ point1X,
398
+ point1Y,
399
+ point2X,
400
+ point2Y
401
+ }) => `Line ${lineNumber} going through point ${point1X} comma ${point1Y} and point ${point2X} comma ${point2Y}.`,
402
+ srLinearSystemIntersection: ({
403
+ x,
404
+ y
405
+ }) => `Line 1 and line 2 intersect at point ${x} comma ${y}.`,
406
+ srLinearSystemParallel: "Line 1 and line 2 are parallel.",
396
407
  srRayGraph: "A ray on a coordinate plane.",
397
408
  srRayPoints: ({
398
409
  point1X,
@@ -485,10 +496,18 @@ const mockStrings = {
485
496
  x,
486
497
  y
487
498
  }) => `Midline intersection at ${x} comma ${y}.`,
488
- srSinusoidPeakPoint: ({
499
+ srSinusoidMaxPoint: ({
500
+ x,
501
+ y
502
+ }) => `Maximum point at ${x} comma ${y}.`,
503
+ srSinusoidMinPoint: ({
489
504
  x,
490
505
  y
491
- }) => `Extremum point at ${x} comma ${y}.`,
506
+ }) => `Minimum point at ${x} comma ${y}.`,
507
+ srSinusoidFlatPoint: ({
508
+ x,
509
+ y
510
+ }) => `Line through point at ${x} comma ${y}.`,
492
511
  srSinusoidDescription: ({
493
512
  minValue,
494
513
  maxValue,
@@ -1021,11 +1040,11 @@ const useDependencies = () => {
1021
1040
  };
1022
1041
 
1023
1042
  var dependencies = /*#__PURE__*/Object.freeze({
1024
- __proto__: null,
1025
- setDependencies: setDependencies,
1026
- getDependencies: getDependencies,
1027
- DependenciesContext: DependenciesContext,
1028
- useDependencies: useDependencies
1043
+ __proto__: null,
1044
+ setDependencies: setDependencies,
1045
+ getDependencies: getDependencies,
1046
+ DependenciesContext: DependenciesContext,
1047
+ useDependencies: useDependencies
1029
1048
  });
1030
1049
 
1031
1050
  /**
@@ -1129,9 +1148,9 @@ const propTypes = {
1129
1148
  };
1130
1149
 
1131
1150
  var changeable = /*#__PURE__*/Object.freeze({
1132
- __proto__: null,
1133
- change: change,
1134
- propTypes: propTypes
1151
+ __proto__: null,
1152
+ change: change,
1153
+ propTypes: propTypes
1135
1154
  });
1136
1155
 
1137
1156
  /* eslint-disable @khanacademy/ts-no-error-suppressions */
@@ -1691,7 +1710,7 @@ class Expression extends React.Component {
1691
1710
  }, /*#__PURE__*/React.createElement(View, {
1692
1711
  style: a11y.srOnly,
1693
1712
  role: "alert"
1694
- }, this.state.showErrorTooltip && ERROR_TITLE + " " + ERROR_MESSAGE), /*#__PURE__*/React.createElement(Tooltip$1, {
1713
+ }, this.state.showErrorTooltip && ERROR_TITLE + " " + ERROR_MESSAGE), /*#__PURE__*/React.createElement(Tooltip, {
1695
1714
  forceAnchorFocusivity: false,
1696
1715
  opened: this.state.showErrorTooltip,
1697
1716
  title: ERROR_TITLE,
@@ -1847,6 +1866,182 @@ var Expression$1 = {
1847
1866
  }
1848
1867
  };
1849
1868
 
1869
+ const _excluded$4 = ["keypadElement", "onFocus", "value"];
1870
+ class SimpleKeypadInput extends React.Component {
1871
+ constructor(...args) {
1872
+ super(...args);
1873
+ this._isMounted = false;
1874
+ this.inputRef = /*#__PURE__*/React.createRef();
1875
+ }
1876
+ componentDidMount() {
1877
+ // TODO(scottgrant): This is a hack to remove the deprecated call to
1878
+ // this.isMounted() but is still considered an anti-pattern.
1879
+ this._isMounted = true;
1880
+ }
1881
+ componentWillUnmount() {
1882
+ this._isMounted = false;
1883
+ }
1884
+ focus() {
1885
+ var _this$inputRef$curren;
1886
+ // The inputRef is a ref to a MathInput, which
1887
+ // also controls the keypad state during focus events.
1888
+ (_this$inputRef$curren = this.inputRef.current) == null || _this$inputRef$curren.focus(this.context.setKeypadActive);
1889
+ }
1890
+ blur() {
1891
+ var _this$inputRef$curren2;
1892
+ (_this$inputRef$curren2 = this.inputRef.current) == null || _this$inputRef$curren2.blur();
1893
+ }
1894
+ getValue() {
1895
+ return this.props.value;
1896
+ }
1897
+ render() {
1898
+ const _this = this;
1899
+ // Intercept the `onFocus` prop, as we need to configure the keypad
1900
+ // before continuing with the default focus logic for Perseus inputs.
1901
+ // Intercept the `value` prop so as to map `null` to the empty string,
1902
+ // as the `KeypadInput` does not support `null` values.
1903
+ const _this$props = _this.props,
1904
+ {
1905
+ keypadElement,
1906
+ onFocus,
1907
+ value
1908
+ } = _this$props,
1909
+ rest = _objectWithoutPropertiesLoose(_this$props, _excluded$4);
1910
+ return (
1911
+ /*#__PURE__*/
1912
+ // @ts-expect-error - TS2769 - No overload matches this call.
1913
+ React.createElement(KeypadInput, _extends({
1914
+ ref: this.inputRef,
1915
+ keypadElement: keypadElement,
1916
+ onFocus: () => {
1917
+ if (keypadElement) {
1918
+ keypadElement.configure({
1919
+ keypadType: KeypadType.FRACTION
1920
+ }, () => {
1921
+ if (_this._isMounted) {
1922
+ onFocus == null || onFocus();
1923
+ }
1924
+ });
1925
+ } else {
1926
+ onFocus == null || onFocus();
1927
+ }
1928
+ },
1929
+ value: value == null ? "" : "" + value
1930
+ }, rest))
1931
+ );
1932
+ }
1933
+ }
1934
+
1935
+ // @ts-expect-error - TS2339 - Property 'propTypes' does not exist on type 'typeof SimpleKeypadInput'.
1936
+ SimpleKeypadInput.contextType = KeypadContext;
1937
+ SimpleKeypadInput.propTypes = {
1938
+ keypadElement: keypadElementPropType,
1939
+ onFocus: PropTypes.func,
1940
+ value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
1941
+ };
1942
+
1943
+ const getPromptJSON$s = (renderProps, userInput) => {
1944
+ return {
1945
+ type: "input-number",
1946
+ options: {
1947
+ simplify: renderProps.simplify,
1948
+ answerType: renderProps.answerType
1949
+ },
1950
+ userInput: {
1951
+ value: userInput.currentValue
1952
+ }
1953
+ };
1954
+ };
1955
+
1956
+ /* eslint-disable @khanacademy/ts-no-error-suppressions */
1957
+ let lastId = 0;
1958
+ function uniqueIdForInput(prefix = "input-") {
1959
+ lastId++;
1960
+ return `${prefix}${lastId}`;
1961
+ }
1962
+ class TextInput extends React.Component {
1963
+ constructor(props) {
1964
+ super(props);
1965
+ this.inputRef = /*#__PURE__*/React.createRef();
1966
+ this.id = void 0;
1967
+ this._getInput = () => {
1968
+ if (!this.inputRef.current) {
1969
+ throw new PerseusError("Input ref accessed before set", Errors.Internal);
1970
+ }
1971
+ return this.inputRef.current;
1972
+ };
1973
+ this.focus = () => {
1974
+ this._getInput().focus();
1975
+ };
1976
+ this.blur = () => {
1977
+ this._getInput().blur();
1978
+ };
1979
+ this.getValue = () => {
1980
+ var _this$inputRef$curren;
1981
+ return (_this$inputRef$curren = this.inputRef.current) == null ? void 0 : _this$inputRef$curren.value;
1982
+ };
1983
+ this.getStringValue = () => {
1984
+ var _this$inputRef$curren2;
1985
+ return (_this$inputRef$curren2 = this.inputRef.current) == null ? void 0 : _this$inputRef$curren2.value.toString();
1986
+ };
1987
+ this.setSelectionRange = (selectionStart, selectionEnd) => {
1988
+ this._getInput().setSelectionRange(selectionStart, selectionEnd);
1989
+ };
1990
+ this.getSelectionStart = () => {
1991
+ return this._getInput().selectionStart;
1992
+ };
1993
+ this.getSelectionEnd = () => {
1994
+ return this._getInput().selectionEnd;
1995
+ };
1996
+ if (props.id) {
1997
+ this.id = props.id;
1998
+ } else {
1999
+ this.id = uniqueIdForInput();
2000
+ }
2001
+ }
2002
+ render() {
2003
+ const {
2004
+ labelText,
2005
+ value,
2006
+ onFocus,
2007
+ onBlur,
2008
+ disabled,
2009
+ placeholder,
2010
+ onKeyDown,
2011
+ style
2012
+ } = this.props;
2013
+ const formattedValue = value === null ? "" : value.toString();
2014
+
2015
+ // Some of our content was saved with empty strings as the label text,
2016
+ // and we don't want to render an empty aria-label attribute.
2017
+ const ariaLabel = labelText || undefined;
2018
+ return /*#__PURE__*/React.createElement(TextField, {
2019
+ ref: this.inputRef,
2020
+ style: style,
2021
+ disabled: disabled,
2022
+ id: this.id,
2023
+ value: formattedValue,
2024
+ type: "text",
2025
+ "aria-label": ariaLabel,
2026
+ "aria-describedby": this.props["aria-describedby"],
2027
+ onChange: value => this.props.onChange(value),
2028
+ placeholder: placeholder,
2029
+ onFocus: onFocus,
2030
+ onBlur: onBlur,
2031
+ onKeyDown: onKeyDown
2032
+ // @ts-expect-error - TS2322 - Type '{ style: StyleType; disabled: boolean | undefined; id: string; value: string; type: "text"; "aria-label": string | undefined; onChange: (value: string) => void; placeholder: string | undefined; ... 5 more ...; autoComplete: string; }' is not assignable to type 'IntrinsicAttributes & ExportProps & RefAttributes<HTMLInputElement>'.
2033
+ ,
2034
+ autoCorrect: "off",
2035
+ autoCapitalize: "off",
2036
+ autoComplete: "off"
2037
+ });
2038
+ }
2039
+ }
2040
+ TextInput.defaultProps = {
2041
+ value: "",
2042
+ disabled: false
2043
+ };
2044
+
1850
2045
  /**
1851
2046
  * A React context for keeping track of whether an component is fully
1852
2047
  * loaded. This is used to know when SvgImage and (Zoomable)TeX are finished
@@ -2411,9 +2606,11 @@ const Util = {
2411
2606
  // TODO(LP-11481): Change the primary API we use for logging in Perseus to use
2412
2607
  // getDependencies() internally... simplify usage to just using a "singleton"
2413
2608
  // which get's the injected logger.
2609
+
2414
2610
  /**
2415
2611
  * Provides logging infrastructure for Perseus
2416
2612
  */
2613
+
2417
2614
  const Log = {
2418
2615
  // Logs a message
2419
2616
  log: (message, extra) => {
@@ -3087,40 +3284,40 @@ const articleMaxWidthInPx = 688;
3087
3284
  const articleMaxWidthTableInPx = 512;
3088
3285
 
3089
3286
  var constants = /*#__PURE__*/Object.freeze({
3090
- __proto__: null,
3091
- baseFontFamily: baseFontFamily,
3092
- boldFontFamily: boldFontFamily,
3093
- kaGreen: kaGreen,
3094
- white: white,
3095
- gray97: gray97,
3096
- gray85: gray85,
3097
- gray76: gray76,
3098
- gray68: gray68,
3099
- gray17: gray17,
3100
- pureSmMin: pureSmMin$1,
3101
- pureMdMin: pureMdMin$1,
3102
- pureLgMin: pureLgMin$1,
3103
- pureXlMin: pureXlMin$1,
3104
- pureXsMax: pureXsMax$1,
3105
- pureSmMax: pureSmMax$1,
3106
- pureMdMax: pureMdMax$1,
3107
- pureLgMax: pureLgMax$1,
3108
- tableBackgroundAccent: tableBackgroundAccent,
3109
- phoneMargin: phoneMargin,
3110
- negativePhoneMargin: negativePhoneMargin,
3111
- hintBorderWidth: hintBorderWidth,
3112
- baseUnitPx: baseUnitPx,
3113
- interactiveSizes: interactiveSizes$1,
3114
- circleSize: circleSize$1,
3115
- radioMarginWidth: radioMarginWidth$1,
3116
- warningColor: warningColor,
3117
- warningColorHover: warningColorHover,
3118
- warningColorActive: warningColorActive,
3119
- publishBlockingErrorColor: publishBlockingErrorColor,
3120
- radioBorderColor: radioBorderColor$1,
3121
- checkedColor: checkedColor$1,
3122
- articleMaxWidthInPx: articleMaxWidthInPx,
3123
- articleMaxWidthTableInPx: articleMaxWidthTableInPx
3287
+ __proto__: null,
3288
+ baseFontFamily: baseFontFamily,
3289
+ boldFontFamily: boldFontFamily,
3290
+ kaGreen: kaGreen,
3291
+ white: white,
3292
+ gray97: gray97,
3293
+ gray85: gray85,
3294
+ gray76: gray76,
3295
+ gray68: gray68,
3296
+ gray17: gray17,
3297
+ pureSmMin: pureSmMin$1,
3298
+ pureMdMin: pureMdMin$1,
3299
+ pureLgMin: pureLgMin$1,
3300
+ pureXlMin: pureXlMin$1,
3301
+ pureXsMax: pureXsMax$1,
3302
+ pureSmMax: pureSmMax$1,
3303
+ pureMdMax: pureMdMax$1,
3304
+ pureLgMax: pureLgMax$1,
3305
+ tableBackgroundAccent: tableBackgroundAccent,
3306
+ phoneMargin: phoneMargin,
3307
+ negativePhoneMargin: negativePhoneMargin,
3308
+ hintBorderWidth: hintBorderWidth,
3309
+ baseUnitPx: baseUnitPx,
3310
+ interactiveSizes: interactiveSizes$1,
3311
+ circleSize: circleSize$1,
3312
+ radioMarginWidth: radioMarginWidth$1,
3313
+ warningColor: warningColor,
3314
+ warningColorHover: warningColorHover,
3315
+ warningColorActive: warningColorActive,
3316
+ publishBlockingErrorColor: publishBlockingErrorColor,
3317
+ radioBorderColor: radioBorderColor$1,
3318
+ checkedColor: checkedColor$1,
3319
+ articleMaxWidthInPx: articleMaxWidthInPx,
3320
+ articleMaxWidthTableInPx: articleMaxWidthTableInPx
3124
3321
  });
3125
3322
 
3126
3323
  /**
@@ -3470,16 +3667,6 @@ const InteractiveUtil = {
3470
3667
  }
3471
3668
  };
3472
3669
 
3473
- function _classPrivateFieldBase(e, t) {
3474
- if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance");
3475
- return e;
3476
- }
3477
-
3478
- var id = 0;
3479
- function _classPrivateFieldKey(e) {
3480
- return "__private_" + id++ + "_" + e;
3481
- }
3482
-
3483
3670
  /*!
3484
3671
  * Raphael 1.5.2 - JavaScript Vector Library
3485
3672
  *
@@ -3536,7 +3723,7 @@ var Raphael$1 = (function () {
3536
3723
  appendChild = "appendChild",
3537
3724
  apply = "apply",
3538
3725
  concat = "concat",
3539
- supportsTouch = ("createTouch" in doc),
3726
+ supportsTouch = "createTouch" in doc,
3540
3727
  E = "",
3541
3728
  S = " ",
3542
3729
  Str = String,
@@ -5141,10 +5328,10 @@ var Raphael$1 = (function () {
5141
5328
  });
5142
5329
  break;
5143
5330
  case "gradient":
5144
- (({
5331
+ ({
5145
5332
  circle: 1,
5146
5333
  ellipse: 1
5147
- })[has](o.type) || Str(value).charAt() != "r") && _addGradientFill(node, value, o.paper);
5334
+ }[has](o.type) || Str(value).charAt() != "r") && _addGradientFill(node, value, o.paper);
5148
5335
  break;
5149
5336
  case "opacity":
5150
5337
  if (attrs.gradient && !attrs[has]("stroke-opacity")) {
@@ -7971,8 +8158,8 @@ function polar$2(r, th) {
7971
8158
  th = th * Math.PI / 180;
7972
8159
  return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
7973
8160
  }
7974
- var _bounds = /*#__PURE__*/_classPrivateFieldKey("bounds");
7975
- var _drawingTransform = /*#__PURE__*/_classPrivateFieldKey("drawingTransform");
8161
+ var _bounds = /*#__PURE__*/_classPrivateFieldLooseKey("bounds");
8162
+ var _drawingTransform = /*#__PURE__*/_classPrivateFieldLooseKey("drawingTransform");
7976
8163
  class Graphie$1 {
7977
8164
  constructor(el) {
7978
8165
  this.el = void 0;
@@ -8177,8 +8364,8 @@ class Graphie$1 {
8177
8364
  if (options.range == null) {
8178
8365
  throw new PerseusError("range should be specified in graph init", Errors.Internal);
8179
8366
  }
8180
- _classPrivateFieldBase(this, _bounds)[_bounds] = new GraphBounds(...options.range);
8181
- _classPrivateFieldBase(this, _drawingTransform)[_drawingTransform] = new DrawingTransform(this.raphael, scale, this.bounds());
8367
+ _classPrivateFieldLooseBase(this, _bounds)[_bounds] = new GraphBounds(...options.range);
8368
+ _classPrivateFieldLooseBase(this, _drawingTransform)[_drawingTransform] = new DrawingTransform(this.raphael, scale, this.bounds());
8182
8369
  const [w, h] = this.drawingTransform().canvasDimensions();
8183
8370
  $(this.el).css({
8184
8371
  width: w,
@@ -8413,16 +8600,16 @@ class Graphie$1 {
8413
8600
  }
8414
8601
  }
8415
8602
  drawingTransform() {
8416
- if (_classPrivateFieldBase(this, _drawingTransform)[_drawingTransform] == null) {
8603
+ if (_classPrivateFieldLooseBase(this, _drawingTransform)[_drawingTransform] == null) {
8417
8604
  throw new Error("Can't get drawingTransform of an uninitialized Graphie");
8418
8605
  }
8419
- return _classPrivateFieldBase(this, _drawingTransform)[_drawingTransform];
8606
+ return _classPrivateFieldLooseBase(this, _drawingTransform)[_drawingTransform];
8420
8607
  }
8421
8608
  bounds() {
8422
- if (_classPrivateFieldBase(this, _bounds)[_bounds] == null) {
8609
+ if (_classPrivateFieldLooseBase(this, _bounds)[_bounds] == null) {
8423
8610
  throw new Error("Can't get bounds of an uninitialized Graphie");
8424
8611
  }
8425
- return _classPrivateFieldBase(this, _bounds)[_bounds];
8612
+ return _classPrivateFieldLooseBase(this, _bounds)[_bounds];
8426
8613
  }
8427
8614
  style(attrs, fn) {
8428
8615
  const processed = this.processAttributes(attrs);
@@ -8973,8 +9160,10 @@ const labelDirections = {
8973
9160
  // is equivalent to [[-5, 5], [-10, 10]].
8974
9161
  // - A single magnitude to be used for both x and y. E.g. 10 is equivalent to
8975
9162
  // [[-10, 10], [-10, 10]]
9163
+
8976
9164
  // Overload normalizeRange to specify that it only returns undefined if its
8977
9165
  // argument is undefined.
9166
+
8978
9167
  // normalizeRange converts a RangeSpecifier into a pair of Intervals that give
8979
9168
  // the x and y ranges, respectively
8980
9169
  function normalizeRange(range) {
@@ -9167,7 +9356,7 @@ const GraphUtils = {
9167
9356
  clickBlockList = [],
9168
9357
  blockMouseTriggers = false,
9169
9358
  blockTouchTriggers = false,
9170
- eventCaptureSupported = ("addEventListener" in document),
9359
+ eventCaptureSupported = "addEventListener" in document,
9171
9360
  $document = $(document),
9172
9361
  nextTouchID = 1,
9173
9362
  lastTouchID = 0;
@@ -9862,7 +10051,6 @@ function scaledPolarRad(radius, radians) {
9862
10051
  return [radius * Math.cos(radians), radius * Math.sin(radians) * -1 // SVG flips y axis
9863
10052
  ];
9864
10053
  }
9865
-
9866
10054
  function scaledPolarDeg(radius, degrees) {
9867
10055
  const radians = degrees * Math.PI / 180;
9868
10056
  return scaledPolarRad(radius, radians);
@@ -11680,7 +11868,6 @@ _.extend(GraphUtils.Graphie.prototype, {
11680
11868
  if (hideArrow) {
11681
11869
  return; // Don't draw anything!
11682
11870
  }
11683
-
11684
11871
  const handleVec = vector$3.subtract(handleCoord, rotatePoint.coord);
11685
11872
  const handlePolar = vector$3.polarDegFromCart(handleVec);
11686
11873
  const angle = handlePolar[1];
@@ -12644,7 +12831,6 @@ _.extend(MovableAngle.prototype, {
12644
12831
  // clockwise to the first point
12645
12832
  coords[1] // the vertex parameter is last
12646
12833
  );
12647
-
12648
12834
  const clockwiseAngle = rawAngle + 360 % 360;
12649
12835
  return clockwiseAngle;
12650
12836
  },
@@ -14018,7 +14204,7 @@ class MovablePoint$5 {
14018
14204
  const showTrashTooltip = () => {
14019
14205
  this._showTooltip(container => {
14020
14206
  // eslint-disable-next-line import/no-deprecated
14021
- render( /*#__PURE__*/React.createElement("span", {
14207
+ render(/*#__PURE__*/React.createElement("span", {
14022
14208
  style: {
14023
14209
  fontSize: "2em"
14024
14210
  }
@@ -14817,7 +15003,6 @@ const DEFAULT_PROPS = {
14817
15003
  // likewise
14818
15004
  labelStyle: null // likewise
14819
15005
  };
14820
-
14821
15006
  const DEFAULT_STATE = {
14822
15007
  added: false,
14823
15008
  hasMoved: false,
@@ -17468,7 +17653,7 @@ class TranslationLinter {
17468
17653
  * Preprocess TeX code to convert things that MathJax doesn't know how to handle
17469
17654
  * to things is does.
17470
17655
  */
17471
- var preprocessTex = (texCode => texCode
17656
+ var preprocessTex = texCode => texCode
17472
17657
  // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
17473
17658
  // support (yet) with \begin{aligned}...\end{aligned} which renders
17474
17659
  // the same is supported by KaTeX. It does the same for align*.
@@ -17478,7 +17663,7 @@ var preprocessTex = (texCode => texCode
17478
17663
  // "aligned").
17479
17664
  .replace(/\{align[*]?\}/g, "{aligned}")
17480
17665
  // Replace non-breaking spaces with regular spaces.
17481
- .replace(/[\u00a0]/g, " "));
17666
+ .replace(/[\u00a0]/g, " ");
17482
17667
 
17483
17668
  const {
17484
17669
  interactiveSizes
@@ -17740,29 +17925,29 @@ const isLintable = type => {
17740
17925
  };
17741
17926
 
17742
17927
  var widgets$1 = /*#__PURE__*/Object.freeze({
17743
- __proto__: null,
17744
- registerWidget: registerWidget,
17745
- registerWidgets: registerWidgets,
17746
- replaceWidget: replaceWidget,
17747
- replaceDeprecatedWidgets: replaceDeprecatedWidgets,
17748
- registerEditors: registerEditors,
17749
- replaceEditor: replaceEditor,
17750
- replaceDeprecatedEditors: replaceDeprecatedEditors,
17751
- getWidget: getWidget,
17752
- getWidgetExport: getWidgetExport,
17753
- getEditor: getEditor,
17754
- getTransform: getTransform,
17755
- getVersion: getVersion,
17756
- getVersionVector: getVersionVector,
17757
- getPublicWidgets: getPublicWidgets,
17758
- isAccessible: isAccessible,
17759
- getAllWidgetTypes: getAllWidgetTypes,
17760
- getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
17761
- traverseChildWidgets: traverseChildWidgets$3,
17762
- supportsStaticMode: supportsStaticMode,
17763
- getStaticTransform: getStaticTransform,
17764
- getTracking: getTracking,
17765
- isLintable: isLintable
17928
+ __proto__: null,
17929
+ registerWidget: registerWidget,
17930
+ registerWidgets: registerWidgets,
17931
+ replaceWidget: replaceWidget,
17932
+ replaceDeprecatedWidgets: replaceDeprecatedWidgets,
17933
+ registerEditors: registerEditors,
17934
+ replaceEditor: replaceEditor,
17935
+ replaceDeprecatedEditors: replaceDeprecatedEditors,
17936
+ getWidget: getWidget,
17937
+ getWidgetExport: getWidgetExport,
17938
+ getEditor: getEditor,
17939
+ getTransform: getTransform,
17940
+ getVersion: getVersion,
17941
+ getVersionVector: getVersionVector,
17942
+ getPublicWidgets: getPublicWidgets,
17943
+ isAccessible: isAccessible,
17944
+ getAllWidgetTypes: getAllWidgetTypes,
17945
+ getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
17946
+ traverseChildWidgets: traverseChildWidgets$3,
17947
+ supportsStaticMode: supportsStaticMode,
17948
+ getStaticTransform: getStaticTransform,
17949
+ getTracking: getTracking,
17950
+ isLintable: isLintable
17766
17951
  });
17767
17952
 
17768
17953
  class WidgetContainer extends React.Component {
@@ -17805,7 +17990,6 @@ class WidgetContainer extends React.Component {
17805
17990
  /* eslint-enable react/no-did-mount-set-state */
17806
17991
  }
17807
17992
  }
17808
-
17809
17993
  UNSAFE_componentWillReceiveProps(nextProps) {
17810
17994
  if (this.props.type !== nextProps.type) {
17811
17995
  throw new Error("WidgetContainer can't change widget type; set a different " + "key instead to recreate the container.");
@@ -19259,620 +19443,123 @@ const styles$A = {
19259
19443
  }
19260
19444
  };
19261
19445
 
19262
- /* eslint-disable @khanacademy/ts-no-error-suppressions */
19263
- let lastId = 0;
19264
- function uniqueIdForInput(prefix = "input-") {
19265
- lastId++;
19266
- return `${prefix}${lastId}`;
19267
- }
19268
- class TextInput extends React.Component {
19269
- constructor(props) {
19270
- super(props);
19271
- this.inputRef = /*#__PURE__*/React.createRef();
19272
- this.id = void 0;
19273
- this._getInput = () => {
19274
- if (!this.inputRef.current) {
19275
- throw new PerseusError("Input ref accessed before set", Errors.Internal);
19446
+ const {
19447
+ captureScratchpadTouchStart: captureScratchpadTouchStart$2
19448
+ } = Util;
19449
+ // [LEMS-2411](Jan 2025) Third: This component has been moved to the NumericInput
19450
+ // folder as we are actively working towards removing the InputNumber widget.
19451
+ // This comment can be removed as part of LEMS-2411.
19452
+
19453
+ /**
19454
+ * The InputWithExamples component is a child component of the NumericInput
19455
+ * and InputNumber components. It is responsible for rendering the UI elements
19456
+ * for the desktop versions of these widgets, and displays a tooltip with
19457
+ * examples of how to input the selected answer forms.
19458
+ */
19459
+ const InputWithExamples = /*#__PURE__*/forwardRef((props, ref) => {
19460
+ // Desctructure the props to set default values
19461
+ const {
19462
+ shouldShowExamples = true,
19463
+ onFocus = () => {},
19464
+ onBlur = () => {},
19465
+ disabled = false,
19466
+ linterContext = PerseusLinter.linterContextDefault,
19467
+ className = ""
19468
+ } = props;
19469
+ const context = React.useContext(PerseusI18nContext);
19470
+ const inputRef = React.useRef(null);
19471
+ const [inputFocused, setInputFocused] = React.useState(false);
19472
+ const id = useId();
19473
+ const ariaId = `aria-for-${id}`;
19474
+ useImperativeHandle(ref, () => ({
19475
+ current: inputRef.current,
19476
+ focus: () => {
19477
+ if (inputRef.current) {
19478
+ inputRef.current.focus();
19479
+ }
19480
+ },
19481
+ blur: () => {
19482
+ if (inputRef.current) {
19483
+ inputRef.current.blur();
19276
19484
  }
19277
- return this.inputRef.current;
19278
- };
19279
- this.focus = () => {
19280
- this._getInput().focus();
19281
- };
19282
- this.blur = () => {
19283
- this._getInput().blur();
19284
- };
19285
- this.getValue = () => {
19286
- var _this$inputRef$curren;
19287
- return (_this$inputRef$curren = this.inputRef.current) == null ? void 0 : _this$inputRef$curren.value;
19288
- };
19289
- this.getStringValue = () => {
19290
- var _this$inputRef$curren2;
19291
- return (_this$inputRef$curren2 = this.inputRef.current) == null ? void 0 : _this$inputRef$curren2.value.toString();
19292
- };
19293
- this.setSelectionRange = (selectionStart, selectionEnd) => {
19294
- this._getInput().setSelectionRange(selectionStart, selectionEnd);
19295
- };
19296
- this.getSelectionStart = () => {
19297
- return this._getInput().selectionStart;
19298
- };
19299
- this.getSelectionEnd = () => {
19300
- return this._getInput().selectionEnd;
19301
- };
19302
- if (props.id) {
19303
- this.id = props.id;
19304
- } else {
19305
- this.id = uniqueIdForInput();
19306
19485
  }
19307
- }
19308
- render() {
19309
- const {
19310
- labelText,
19311
- value,
19312
- onFocus,
19313
- onBlur,
19314
- disabled,
19315
- placeholder,
19316
- onKeyDown,
19317
- style
19318
- } = this.props;
19319
- const formattedValue = value === null ? "" : value.toString();
19320
- return /*#__PURE__*/React.createElement(TextField, {
19321
- ref: this.inputRef,
19322
- style: style,
19486
+ }));
19487
+ const getInputClassName = () => {
19488
+ let inputClassName = ClassNames.INPUT;
19489
+ if (inputFocused) {
19490
+ inputClassName += " " + ClassNames.FOCUSED;
19491
+ }
19492
+ if (className) {
19493
+ inputClassName += " " + className;
19494
+ }
19495
+ return inputClassName;
19496
+ };
19497
+ const handleFocus = () => {
19498
+ onFocus();
19499
+ setInputFocused(true);
19500
+ };
19501
+ const handleBlur = () => {
19502
+ onBlur();
19503
+ setInputFocused(false);
19504
+ };
19505
+ const renderInput = () => {
19506
+ // Generate the provided examples in simple language for screen readers.
19507
+ const examplesAria = props.shouldShowExamples ? `${props.examples[0]}
19508
+ ${props.examples.slice(1).join(", or\n")}`
19509
+ // @ts-expect-error TS2550: Property replaceAll does not exist on type string.
19510
+ .replaceAll("*", "").replaceAll("$", "").replaceAll("\\ \\text{pi}", " pi").replaceAll("\\ ", " and ") : "";
19511
+ const inputProps = {
19512
+ id: id,
19513
+ // If we have examples, we want to provide the aria-describedby attribute
19514
+ "aria-describedby": props.shouldShowExamples ? ariaId : undefined,
19515
+ ref: inputRef,
19516
+ className: getInputClassName(),
19517
+ labelText: props.labelText,
19518
+ value: props.value,
19519
+ onFocus: handleFocus,
19520
+ onBlur: handleBlur,
19323
19521
  disabled: disabled,
19324
- id: this.id,
19325
- value: formattedValue,
19326
- type: "text",
19327
- "aria-label": labelText,
19328
- "aria-describedby": this.props["aria-describedby"],
19329
- onChange: value => this.props.onChange(value),
19330
- placeholder: placeholder,
19331
- onFocus: onFocus,
19332
- onBlur: onBlur,
19333
- onKeyDown: onKeyDown
19334
- // @ts-expect-error - TS2322 - Type '{ style: StyleType; disabled: boolean | undefined; id: string; value: string; type: "text"; "aria-label": string | undefined; onChange: (value: string) => void; placeholder: string | undefined; ... 5 more ...; autoComplete: string; }' is not assignable to type 'IntrinsicAttributes & ExportProps & RefAttributes<HTMLInputElement>'.
19335
- ,
19336
- autoCorrect: "off",
19522
+ style: props.style,
19523
+ onChange: props.onChange,
19524
+ onTouchStart: captureScratchpadTouchStart$2,
19337
19525
  autoCapitalize: "off",
19338
- autoComplete: "off"
19339
- });
19340
- }
19341
- }
19342
- TextInput.defaultProps = {
19343
- value: "",
19344
- disabled: false
19345
- };
19346
-
19347
- function _objectWithoutPropertiesLoose(r, e) {
19348
- if (null == r) return {};
19349
- var t = {};
19350
- for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
19351
- if (-1 !== e.indexOf(n)) continue;
19352
- t[n] = r[n];
19353
- }
19354
- return t;
19355
- }
19356
-
19357
- const _excluded$4 = ["position", "visibility", "left", "top"];
19358
- const zIndex = 10;
19359
- let HorizontalDirection = /*#__PURE__*/function (HorizontalDirection) {
19360
- HorizontalDirection["Left"] = "left";
19361
- HorizontalDirection["Right"] = "right";
19362
- return HorizontalDirection;
19363
- }({});
19364
- let VerticalDirection = /*#__PURE__*/function (VerticalDirection) {
19365
- VerticalDirection["Top"] = "top";
19366
- VerticalDirection["Bottom"] = "bottom";
19367
- return VerticalDirection;
19368
- }({});
19369
- const Triangle = props => {
19370
- let borderLeft;
19371
- let borderRight;
19372
- let borderTop;
19373
- let borderBottom;
19374
- const hBorder = `${props.width}px solid transparent`;
19375
- if (props.horizontalDirection === "right") {
19376
- borderLeft = hBorder;
19377
- } else {
19378
- borderRight = hBorder;
19379
- }
19380
- const vBorder = `${props.height}px solid ${props.color}`;
19381
- if (props.verticalDirection === "top") {
19382
- borderTop = vBorder;
19383
- } else {
19384
- borderBottom = vBorder;
19385
- }
19386
- return /*#__PURE__*/React.createElement("div", {
19387
- style: {
19388
- display: "block",
19389
- height: 0,
19390
- width: 0,
19391
- position: "absolute",
19392
- left: props.left,
19393
- top: props.top,
19394
- borderLeft: borderLeft,
19395
- borderRight: borderRight,
19396
- borderTop: borderTop,
19397
- borderBottom: borderBottom
19398
- }
19399
- });
19400
- };
19401
- const TooltipArrow = _ref => {
19402
- let {
19403
- position = "relative",
19404
- visibility = "visible",
19405
- left = 100,
19406
- top = 0
19407
- } = _ref,
19408
- props = _objectWithoutPropertiesLoose(_ref, _excluded$4);
19409
- // TODO(aria): Think about adding a box-shadow to the triangle here
19410
- // See http://css-tricks.com/triangle-with-shadow/
19411
-
19412
- //const isRight = (this.props.horizontalDirection === "right");
19413
- const isTop = props.verticalDirection === "top";
19414
- const frontTopOffset = isTop ? 0 : 1;
19415
- const borderTopOffset = isTop ? 0 : -1;
19416
- return /*#__PURE__*/React.createElement("div", {
19417
- style: {
19418
- display: "block",
19419
- position: position,
19420
- visibility: visibility,
19421
- left: left,
19422
- top: top,
19423
- width: props.width + 2,
19424
- height: props.height + 1,
19425
- marginTop: -1,
19426
- marginBottom: -2,
19427
- zIndex: zIndex
19428
- }
19429
- }, /*#__PURE__*/React.createElement(Triangle, {
19430
- horizontalDirection: props.horizontalDirection,
19431
- verticalDirection: props.verticalDirection,
19432
- color: props.border,
19433
- left: 0,
19434
- top: borderTopOffset,
19435
- width: props.width + 2 // one extra for the diagonal
19436
- ,
19437
- height: props.height + 2
19438
- }), /*#__PURE__*/React.createElement(Triangle, {
19439
- horizontalDirection: props.horizontalDirection,
19440
- verticalDirection: props.verticalDirection,
19441
- color: props.color,
19442
- left: 1,
19443
- top: frontTopOffset,
19444
- width: props.width,
19445
- height: props.height
19446
- }));
19447
- };
19448
- const VERTICAL_CORNERS = {
19449
- top: {
19450
- top: "-100%"
19451
- },
19452
- bottom: {
19453
- top: 0
19454
- }
19455
- };
19456
- const HORIZONTAL_CORNERS = {
19457
- left: {
19458
- targetLeft: 0
19459
- },
19460
- right: {
19461
- targetLeft: "100%"
19462
- }
19463
- };
19464
- const HORIZONTAL_ALIGNMNENTS = {
19465
- left: {
19466
- tooltipLeft: 0,
19467
- arrowLeft: arrowSize => 0
19468
- },
19469
- right: {
19470
- tooltipLeft: "-100%",
19471
- arrowLeft: arrowSize => -arrowSize - 2
19472
- }
19473
- };
19474
- /**
19475
- * DEPRECATED! Use Wonder Blocks tooltip instead.
19476
- *
19477
- * A generic tooltip library for React.js
19478
- *
19479
- * ```
19480
- * import Tooltip from "./tooltip";
19481
- * <Tooltip
19482
- * className="class-for-tooltip-contents"
19483
- * horizontalPosition={HoriziontalDirection.Left}
19484
- * horizontalAlign={HoriziontalDirection.Left}
19485
- * verticalPosition={VerticalDirection.Top}
19486
- * arrowSize={10} // arrow size in pixels
19487
- * borderColor="#ccc" // color of the border for the tooltip
19488
- * show={true} // whether the tooltip should currently be visible
19489
- * targetContainerStyle={targetContainerStyle}
19490
- * >
19491
- * <TargetElementOfTheTooltip />
19492
- * <TooltipContents1 />
19493
- * <TooltipContents2 />
19494
- * </Tooltip>
19495
- * ```
19496
- *
19497
- * To show/hide the tooltip, the parent component should call the
19498
- * `.show()` and `.hide()` methods of the tooltip when appropriate.
19499
- * (These are usually set up as handlers of events on the target element.)
19500
- *
19501
- * Notes:
19502
- * `className` should not specify a border; that is handled by `borderColor`
19503
- * so that the arrow and tooltip match
19504
- *
19505
- * ```
19506
- * __,,--``\\
19507
- * _,,-''`` \\ ,
19508
- * '----------_.------'-.___|\__
19509
- * _.--''`` `)__ )__ @\__
19510
- * ( .. ''---/___,,E/__,E'------`
19511
- * `-''`''
19512
- * Here be dragons.
19513
- * ```
19514
- */
19515
- class Tooltip extends React.Component {
19516
- constructor(...args) {
19517
- super(...args);
19518
- this.tooltipContainerRef = /*#__PURE__*/React.createRef();
19519
- this.state = {
19520
- height: null // used for offsetting "top" positioned tooltips
19526
+ autoComplete: "off",
19527
+ autoCorrect: "off",
19528
+ spellCheck: "false"
19521
19529
  };
19522
- }
19523
-
19524
- componentDidMount() {
19525
- this._updateHeight();
19526
- }
19527
- UNSAFE_componentWillReceiveProps() {
19528
- // If the contents have changed, reset our measure of the height
19529
- this.setState({
19530
- height: null
19531
- });
19532
- }
19533
- componentDidUpdate() {
19534
- this._updateHeight();
19535
- }
19536
- _renderToolTipDiv(isTooltipAbove) {
19537
- const settings = Object.assign({}, HORIZONTAL_CORNERS[this.props.horizontalPosition], HORIZONTAL_ALIGNMNENTS[this.props.horizontalAlign], VERTICAL_CORNERS[this.props.verticalPosition]);
19538
- let arrowAbove;
19539
- let arrowBelow;
19540
- if (isTooltipAbove) {
19541
- // We put an absolutely positioned arrow in the correct place
19542
- arrowAbove = /*#__PURE__*/React.createElement(TooltipArrow, {
19543
- verticalDirection: VerticalDirection.Top,
19544
- horizontalDirection: this.props.horizontalAlign,
19545
- position: "absolute",
19546
- color: "white",
19547
- border: this.props.borderColor,
19548
- left: settings.arrowLeft(this.props.arrowSize),
19549
- top: -this.props.arrowSize + 2,
19550
- width: this.props.arrowSize,
19551
- height: this.props.arrowSize,
19552
- zIndex: zIndex
19553
- });
19554
-
19555
- // And we use a visibility: hidden arrow below to shift up the
19556
- // content by the correct amount
19557
- arrowBelow = /*#__PURE__*/React.createElement(TooltipArrow, {
19558
- verticalDirection: VerticalDirection.Top,
19559
- horizontalDirection: this.props.horizontalAlign,
19560
- visibility: "hidden",
19561
- color: "white",
19562
- border: this.props.borderColor,
19563
- left: settings.arrowLeft(this.props.arrowSize),
19564
- top: -1,
19565
- width: this.props.arrowSize,
19566
- height: this.props.arrowSize,
19567
- zIndex: zIndex
19568
- });
19569
- } else {
19570
- arrowAbove = /*#__PURE__*/React.createElement(TooltipArrow, {
19571
- verticalDirection: VerticalDirection.Bottom,
19572
- horizontalDirection: this.props.horizontalAlign,
19573
- color: "white",
19574
- border: this.props.borderColor,
19575
- left: settings.arrowLeft(this.props.arrowSize),
19576
- top: -1,
19577
- width: this.props.arrowSize,
19578
- height: this.props.arrowSize,
19579
- zIndex: zIndex
19580
- });
19581
- arrowBelow = null;
19582
- }
19583
-
19584
- /* A positioned div below the input to be the parent for our
19585
- tooltip */
19586
- return /*#__PURE__*/React.createElement("div", {
19530
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TextInput, inputProps), /*#__PURE__*/React.createElement("span", {
19531
+ id: ariaId,
19587
19532
  style: {
19588
- position: "relative",
19589
- height: 0,
19590
- display: this.props.show ? "block" : "none"
19533
+ display: "none"
19591
19534
  }
19592
- }, /*#__PURE__*/React.createElement("div", {
19593
- ref: this.tooltipContainerRef,
19594
- className: "tooltipContainer",
19595
- role: "tooltip",
19596
- style: {
19597
- position: "absolute",
19598
- // height must start out undefined, not null, so that
19599
- // we can measure the actual height with jquery.
19600
- // This is used to position the tooltip with top: -100%
19601
- // when in verticalPosition: "top" mode
19602
- height: this.state.height || undefined,
19603
- left: settings.targetLeft
19604
- }
19605
- }, arrowAbove, /*#__PURE__*/React.createElement("div", {
19606
- className: this.props.className
19607
- // eslint-disable-next-line react/no-string-refs
19608
- ,
19609
- ref: "tooltipContent",
19610
- style: {
19611
- position: "relative",
19612
- top: settings["top"],
19613
- left: settings.tooltipLeft,
19614
- border: "1px solid " + this.props.borderColor,
19615
- boxShadow: "0 1px 3px " + this.props.borderColor,
19616
- zIndex: zIndex - 1
19617
- }
19618
- }, this.props.children.slice(1)), arrowBelow));
19619
- }
19620
- _updateHeight() {
19621
- const tooltipContainer = this.tooltipContainerRef.current;
19622
- const height = (tooltipContainer == null ? void 0 : tooltipContainer.offsetHeight) || 0;
19623
- if (height !== this.state.height) {
19624
- this.setState({
19625
- height
19626
- });
19627
- }
19628
- }
19629
- render() {
19630
- const isTooltipAbove = this.props.verticalPosition === "top";
19631
-
19632
- /* We wrap the entire output in a span so that it displays inline */
19633
- return /*#__PURE__*/React.createElement("span", null, isTooltipAbove && this._renderToolTipDiv(isTooltipAbove), /*#__PURE__*/React.createElement("div", {
19634
- style: this.props.targetContainerStyle
19635
- }, this.props.children[0]), !isTooltipAbove && this._renderToolTipDiv());
19636
- }
19637
- }
19638
-
19639
- // Sorry. // Apology-Oriented-Programming
19640
- Tooltip.defaultProps = {
19641
- className: "",
19642
- arrowSize: 10,
19643
- borderColor: "#ccc",
19644
- verticalPosition: VerticalDirection.Bottom,
19645
- horizontalPosition: HorizontalDirection.Left,
19646
- horizontalAlign: HorizontalDirection.Left,
19647
- targetContainerStyle: {}
19648
- };
19649
-
19650
- const {
19651
- captureScratchpadTouchStart: captureScratchpadTouchStart$2
19652
- } = Util;
19653
- class InputWithExamples extends React.Component {
19654
- constructor(...args) {
19655
- super(...args);
19656
- this.state = {
19657
- focused: false,
19658
- showExamples: false
19659
- };
19660
- this._getUniqueId = () => {
19661
- return `input-with-examples-${btoa(this.props.id).replace(/=/g, "")}`;
19662
- };
19663
- this._getInputClassName = () => {
19664
- // Otherwise, we need to add these INPUT and FOCUSED tags here.
19665
- let className = ClassNames.INPUT;
19666
- if (this.state.focused) {
19667
- className += " " + ClassNames.FOCUSED;
19668
- }
19669
- if (this.props.className) {
19670
- className += " " + this.props.className;
19671
- }
19672
- return className;
19673
- };
19674
- this._renderInput = () => {
19675
- const id = this._getUniqueId();
19676
- const ariaId = `aria-for-${id}`;
19677
- // Generate text from a known set of format options that will read well in a screen reader
19678
- const examplesAria = this.props.examples.length === 7 ? "" : `${this.props.examples[0]}
19679
- ${this.props.examples.slice(1).join(", or\n")}`
19680
- // @ts-expect-error TS2550: Property replaceAll does not exist on type string.
19681
- .replaceAll("*", "").replaceAll("$", "").replaceAll("\\ \\text{pi}", " pi").replaceAll("\\ ", " and ");
19682
- const inputProps = {
19683
- id: id,
19684
- "aria-describedby": ariaId,
19685
- ref: "input",
19686
- className: this._getInputClassName(),
19687
- labelText: this.props.labelText,
19688
- value: this.props.value,
19689
- onFocus: this._handleFocus,
19690
- onBlur: this._handleBlur,
19691
- disabled: this.props.disabled,
19692
- style: this.props.style,
19693
- onChange: this.props.onChange,
19694
- onTouchStart: captureScratchpadTouchStart$2,
19695
- autoCapitalize: "off",
19696
- autoComplete: "off",
19697
- autoCorrect: "off",
19698
- spellCheck: "false"
19699
- };
19700
- return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TextInput, inputProps), /*#__PURE__*/React.createElement("span", {
19701
- id: ariaId,
19702
- style: {
19703
- display: "none"
19704
- }
19705
- }, examplesAria));
19706
- };
19707
- this._handleFocus = () => {
19708
- this.props.onFocus();
19709
- this.setState({
19710
- focused: true,
19711
- showExamples: true
19712
- });
19713
- };
19714
- this.show = () => {
19715
- this.setState({
19716
- showExamples: true
19717
- });
19718
- };
19719
- this.hide = () => {
19720
- this.setState({
19721
- showExamples: false
19722
- });
19723
- };
19724
- this._handleBlur = () => {
19725
- this.props.onBlur();
19726
- this.setState({
19727
- focused: false,
19728
- showExamples: false
19729
- });
19730
- };
19731
- this.focus = () => {
19732
- // eslint-disable-next-line react/no-string-refs
19733
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
19734
- this.refs.input.focus();
19735
- };
19736
- this.blur = () => {
19737
- // eslint-disable-next-line react/no-string-refs
19738
- // @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
19739
- this.refs.input.blur();
19740
- };
19741
- this.handleChange = e => {
19742
- this.props.onChange(e.target.value);
19743
- };
19744
- }
19745
- render() {
19746
- const input = this._renderInput();
19747
- const examplesContent = this.props.examples.length <= 2 ? this.props.examples.join(" ") // A single item (with or without leading text) is not a "list"
19748
- : this.props.examples // 2 or more items should display as a list
19749
- .map((example, index) => {
19750
- // If the first example is bold, then it is most likely a heading/leading text.
19751
- // So, it shouldn't be part of the list.
19752
- return index === 0 && example.startsWith("**") ? `${example}\n` : `- ${example}`;
19753
- }).join("\n");
19754
- const showExamples = this.props.shouldShowExamples && this.state.showExamples;
19755
- return /*#__PURE__*/React.createElement(Tooltip, {
19756
- className: "perseus-formats-tooltip preview-measure",
19757
- horizontalPosition: HorizontalDirection.Left,
19758
- horizontalAlign: HorizontalDirection.Left,
19759
- verticalPosition: VerticalDirection.Bottom,
19760
- arrowSize: 10,
19761
- borderColor: "#ccc",
19762
- show: showExamples
19763
- }, input, /*#__PURE__*/React.createElement("div", {
19764
- id: this._getUniqueId()
19535
+ }, examplesAria));
19536
+ };
19537
+ const renderTooltipContent = () => {
19538
+ return /*#__PURE__*/React.createElement(TooltipContent, null, /*#__PURE__*/React.createElement("div", {
19539
+ id: id,
19540
+ className: "input-with-examples-tooltip"
19765
19541
  }, /*#__PURE__*/React.createElement(Renderer, {
19766
19542
  content: examplesContent,
19767
- linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "input-with-examples"),
19768
- strings: this.context.strings
19543
+ linterContext: PerseusLinter.pushContextStack(linterContext, "input-with-examples"),
19544
+ strings: context.strings
19769
19545
  })));
19770
- }
19771
- }
19772
- InputWithExamples.contextType = PerseusI18nContext;
19773
- InputWithExamples.defaultProps = {
19774
- shouldShowExamples: true,
19775
- onFocus: function () {},
19776
- onBlur: function () {},
19777
- disabled: false,
19778
- linterContext: PerseusLinter.linterContextDefault,
19779
- className: ""
19780
- };
19781
-
19782
- const _excluded$3 = ["keypadElement", "onFocus", "value"];
19783
- class SimpleKeypadInput extends React.Component {
19784
- constructor(...args) {
19785
- super(...args);
19786
- this._isMounted = false;
19787
- }
19788
- componentDidMount() {
19789
- // TODO(scottgrant): This is a hack to remove the deprecated call to
19790
- // this.isMounted() but is still considered an anti-pattern.
19791
- this._isMounted = true;
19792
- }
19793
- componentWillUnmount() {
19794
- this._isMounted = false;
19795
- }
19796
- focus() {
19797
- // @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
19798
- this.refs.input.focus(); // eslint-disable-line react/no-string-refs
19799
- }
19800
-
19801
- blur() {
19802
- var _this$refs$input;
19803
- // eslint-disable-next-line react/no-string-refs
19804
- // @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
19805
- if (typeof ((_this$refs$input = this.refs.input) == null ? void 0 : _this$refs$input.blur) === "function") {
19806
- var _this$refs$input2;
19807
- // eslint-disable-next-line react/no-string-refs
19808
- // @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
19809
- (_this$refs$input2 = this.refs.input) == null || _this$refs$input2.blur();
19810
- }
19811
- }
19812
- getValue() {
19813
- return this.props.value;
19814
- }
19815
- render() {
19816
- const _this = this;
19817
- // Intercept the `onFocus` prop, as we need to configure the keypad
19818
- // before continuing with the default focus logic for Perseus inputs.
19819
- // Intercept the `value` prop so as to map `null` to the empty string,
19820
- // as the `KeypadInput` does not support `null` values.
19821
- const _this$props = _this.props,
19822
- {
19823
- keypadElement,
19824
- onFocus,
19825
- value
19826
- } = _this$props,
19827
- rest = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
19828
- return (
19829
- /*#__PURE__*/
19830
- // @ts-expect-error - TS2769 - No overload matches this call.
19831
- React.createElement(KeypadInput
19832
- // eslint-disable-next-line react/no-string-refs
19833
- , _extends({
19834
- ref: "input",
19835
- keypadElement: keypadElement,
19836
- onFocus: () => {
19837
- if (keypadElement) {
19838
- keypadElement.configure({
19839
- keypadType: KeypadType.FRACTION
19840
- }, () => {
19841
- if (_this._isMounted) {
19842
- onFocus == null || onFocus();
19843
- }
19844
- });
19845
- } else {
19846
- onFocus == null || onFocus();
19847
- }
19848
- },
19849
- value: value == null ? "" : "" + value
19850
- }, rest))
19851
- );
19852
- }
19853
- }
19854
-
19855
- // @ts-expect-error - TS2339 - Property 'propTypes' does not exist on type 'typeof SimpleKeypadInput'.
19856
- SimpleKeypadInput.propTypes = {
19857
- keypadElement: keypadElementPropType,
19858
- onFocus: PropTypes.func,
19859
- value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
19860
- };
19861
-
19862
- const getPromptJSON$s = (renderProps, userInput) => {
19863
- return {
19864
- type: "input-number",
19865
- options: {
19866
- simplify: renderProps.simplify,
19867
- answerType: renderProps.answerType
19868
- },
19869
- userInput: {
19870
- value: userInput.currentValue
19871
- }
19872
19546
  };
19873
- };
19547
+ // Display the examples as a string when there are less than or equal to 2 examples.
19548
+ // Otherwise, display the examples as a list.
19549
+ const examplesContent = props.examples.length <= 2 ? props.examples.join(" ") : props.examples.map((example, index) => {
19550
+ return index === 0 && example.startsWith("**") ? `${example}\n` : `- ${example}`;
19551
+ }).join("\n");
19552
+
19553
+ // Display the examples when they are enabled (shouldShowExamples) and the input is focused.
19554
+ const showExamplesTooltip = shouldShowExamples && inputFocused;
19555
+ return /*#__PURE__*/React.createElement(Tooltip, {
19556
+ content: renderTooltipContent(),
19557
+ opened: showExamplesTooltip,
19558
+ placement: "bottom"
19559
+ }, renderInput());
19560
+ });
19874
19561
 
19875
- const formExamples$1 = {
19562
+ const formExamples = {
19876
19563
  integer: function (options, strings) {
19877
19564
  return strings.integerExample;
19878
19565
  },
@@ -19975,7 +19662,7 @@ class InputNumber extends React.Component {
19975
19662
  } = this.context;
19976
19663
  const type = this.props.answerType;
19977
19664
  const forms = inputNumberAnswerTypes[type].forms.split(/\s*,\s*/);
19978
- const examples = _.map(forms, form => formExamples$1[form](this.props, strings));
19665
+ const examples = _.map(forms, form => formExamples[form](this.props, strings));
19979
19666
  return [strings.yourAnswer].concat(examples);
19980
19667
  }
19981
19668
  render() {
@@ -20100,7 +19787,11 @@ const getPromptJSON$r = (renderProps, userInput) => {
20100
19787
  };
20101
19788
  };
20102
19789
 
20103
- const formExamples = {
19790
+ /**
19791
+ * The full list of available strings for the numeric input widget,
19792
+ * based on whether the Content Creator has specified that the answer must be simplified.
19793
+ */
19794
+ const NumericExampleStrings = {
20104
19795
  integer: (form, strings) => strings.integerExample,
20105
19796
  proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
20106
19797
  improper: (form, strings) => form.simplify === "optional" ? strings.improperExample : strings.simplifiedImproperExample,
@@ -20108,38 +19799,200 @@ const formExamples = {
20108
19799
  decimal: (form, strings) => strings.decimalExample,
20109
19800
  pi: (form, strings) => strings.piExample
20110
19801
  };
19802
+
19803
+ /**
19804
+ * Generates the specific set of examples for the current question.
19805
+ * This string is shown as examples to the user in a tooltip.
19806
+ */
19807
+ const generateExamples = (answerForms, strings) => {
19808
+ // If the Content Creator has not specified any answer forms,
19809
+ // we do not need to show any examples.
19810
+ if (answerForms.length === 0) {
19811
+ return [];
19812
+ }
19813
+
19814
+ // Generate a list of the unique answer forms.
19815
+ const uniqueForms = getUniqueAnswerForms(answerForms);
19816
+
19817
+ // Generate the example strings for each unique form.
19818
+ const examples = uniqueForms.map(form => {
19819
+ return NumericExampleStrings[form.name](form, strings);
19820
+ });
19821
+
19822
+ // Add the "Your answer" string to the beginning of the examples list.
19823
+ return [strings.yourAnswer].concat(examples);
19824
+ };
19825
+
19826
+ /**
19827
+ * Determines whether to show examples of how to input the various supported answer forms.
19828
+ * We do not show examples if all forms are accepted or if no forms are accepted.
19829
+ */
19830
+ const shouldShowExamples = answerForms => {
19831
+ // If the Content Creator has not specified any answer forms,
19832
+ // we do not need to show any examples.
19833
+ if (answerForms.length === 0) {
19834
+ return false;
19835
+ }
19836
+
19837
+ // Generate a list of the unique names of the selected answer forms.
19838
+ const answerFormNames = getUniqueAnswerForms(answerForms).map(form => form.name);
19839
+
19840
+ // If all forms are accepted, we do not need to show any examples.
19841
+ const allFormsAccepted = answerFormNames.length >= Object.keys(NumericExampleStrings).length;
19842
+ return !allFormsAccepted;
19843
+ };
19844
+
19845
+ /**
19846
+ * uniqueAnswerForms takes a list of answer forms and returns a list of unique
19847
+ * answer forms. This is useful for ensuring that we don't show duplicate examples
19848
+ * to the user.
19849
+ */
19850
+ const getUniqueAnswerForms = function getUniqueAnswerForms(list) {
19851
+ // We use a Set to keep track of the forms we've already seen.
19852
+ const foundForms = new Set();
19853
+ return list.filter(form => {
19854
+ // If we've already seen this form, skip it.
19855
+ if (foundForms.has(form.name)) {
19856
+ return false;
19857
+ }
19858
+ // Otherwise, add it to the set and return true.
19859
+ foundForms.add(form.name);
19860
+ return true;
19861
+ });
19862
+ };
19863
+
19864
+ /**
19865
+ * Takes a list of lists of answer forms, and returns a list of the forms
19866
+ * in each of these lists in the same order that they're listed in the
19867
+ * `formExamples` forms from above.
19868
+ */
19869
+ const unionAnswerForms = function unionAnswerForms(answerFormsList) {
19870
+ // Pull out all of the forms from the different lists.
19871
+ const allForms = answerFormsList.flat();
19872
+ // Pull out the unique forms using getUniqueAnswerForms.
19873
+ const uniqueForms = getUniqueAnswerForms(allForms);
19874
+ // Sort them by the order they appear in the `formExamples` list.
19875
+ const formExampleKeys = Object.keys(NumericExampleStrings);
19876
+ return uniqueForms.sort((a, b) => {
19877
+ return formExampleKeys.indexOf(a.name) - formExampleKeys.indexOf(b.name);
19878
+ });
19879
+ };
19880
+
19881
+ /**
19882
+ * The NumericInputComponent is a child component of the NumericInput class
19883
+ * component. It is responsible for rendering the UI elements of the Numeric
19884
+ * Input widget.
19885
+ */
19886
+ const NumericInputComponent = /*#__PURE__*/forwardRef((props, ref) => {
19887
+ const context = useContext(PerseusI18nContext);
19888
+ const inputRef = useRef(null);
19889
+ const [isFocused, setIsFocused] = useState(false);
19890
+
19891
+ // Pass the focus and blur methods to the Numeric Input Class component
19892
+ useImperativeHandle(ref, () => ({
19893
+ current: inputRef.current,
19894
+ focus: () => {
19895
+ if (inputRef.current) {
19896
+ var _inputRef$current;
19897
+ (_inputRef$current = inputRef.current) == null || _inputRef$current.focus();
19898
+ setIsFocused(true);
19899
+ }
19900
+ },
19901
+ blur: () => {
19902
+ if (inputRef.current) {
19903
+ var _inputRef$current2;
19904
+ (_inputRef$current2 = inputRef.current) == null || _inputRef$current2.blur();
19905
+ setIsFocused(false);
19906
+ }
19907
+ }
19908
+ }));
19909
+ const handleChange = (newValue, cb) => {
19910
+ props.onChange({
19911
+ currentValue: newValue
19912
+ }, cb);
19913
+ props.trackInteraction();
19914
+ };
19915
+ const handleFocus = () => {
19916
+ props.onFocus([]);
19917
+ setIsFocused(true);
19918
+ };
19919
+ const handleBlur = () => {
19920
+ props.onBlur([]);
19921
+ setIsFocused(false);
19922
+ };
19923
+
19924
+ // Styles for the InputWithExamples
19925
+ const styles = StyleSheet.create({
19926
+ inputWithExamples: {
19927
+ borderRadius: "3px",
19928
+ borderWidth: isFocused ? "2px" : "1px",
19929
+ display: "inline-block",
19930
+ fontFamily: `Symbola, "Times New Roman", serif`,
19931
+ fontSize: "18px",
19932
+ height: "32px",
19933
+ lineHeight: "18px",
19934
+ padding: isFocused ? "4px" : "4px 5px",
19935
+ textAlign: props.rightAlign ? "right" : "left",
19936
+ width: props.size === "small" ? 40 : 80
19937
+ }
19938
+ });
19939
+
19940
+ // (mobile-only) If the custom keypad is enabled, use the SimpleKeypadInput component
19941
+ if (props.apiOptions.customKeypad) {
19942
+ const alignmentClass = props.rightAlign ? "perseus-input-right-align" : undefined;
19943
+ return /*#__PURE__*/React.createElement("div", {
19944
+ className: alignmentClass
19945
+ }, /*#__PURE__*/React.createElement(SimpleKeypadInput, {
19946
+ ref: inputRef,
19947
+ value: props.currentValue,
19948
+ keypadElement: props.keypadElement,
19949
+ onChange: handleChange,
19950
+ onFocus: handleFocus,
19951
+ onBlur: handleBlur
19952
+ }));
19953
+ }
19954
+ // (desktop-only) Otherwise, use the InputWithExamples component
19955
+ return /*#__PURE__*/React.createElement(InputWithExamples, {
19956
+ ref: inputRef,
19957
+ value: props.currentValue,
19958
+ onChange: handleChange,
19959
+ labelText: props.labelText,
19960
+ examples: generateExamples(props.answerForms, context.strings),
19961
+ shouldShowExamples: shouldShowExamples(props.answerForms),
19962
+ onFocus: handleFocus,
19963
+ onBlur: handleBlur,
19964
+ id: props.widgetId,
19965
+ disabled: props.apiOptions.readOnly,
19966
+ style: styles.inputWithExamples
19967
+ });
19968
+ });
19969
+
19970
+ const _excluded$3 = ["answers"];
19971
+
19972
+ /**
19973
+ * The NumericInput widget is a numeric input field that supports a variety of
19974
+ * answer forms, including integers, decimals, fractions, and mixed numbers.
19975
+ *
19976
+ * [Jan 2025] We're currenly migrating from class-based components to functional
19977
+ * components. While we cannot fully migrate this component yet, we can start
19978
+ * by using the functional component for the rendering the UI of the widget.
19979
+ */
20111
19980
  class NumericInput extends React.Component {
20112
19981
  constructor(...args) {
20113
19982
  super(...args);
20114
- this.inputRef = void 0;
20115
- this.state = {
20116
- // keeps track of the other set of values when switching
20117
- // between 0 and finite solutions
20118
- previousValues: [""],
20119
- isFocused: false
20120
- };
20121
- this.shouldShowExamples = () => {
20122
- var _this$props$answerFor, _this$props$answerFor2;
20123
- const noFormsAccepted = ((_this$props$answerFor = this.props.answerForms) == null ? void 0 : _this$props$answerFor.length) === 0;
20124
- // To check if all answer forms are accepted, we must first
20125
- // find the *names* of all accepted forms, and see if they are
20126
- // all present, ignoring duplicates
20127
- const answerFormNames = _.uniq((_this$props$answerFor2 = this.props.answerForms) == null ? void 0 : _this$props$answerFor2.map(form => form.name));
20128
- const allFormsAccepted = answerFormNames.length >= Object.keys(formExamples).length;
20129
- return !noFormsAccepted && !allFormsAccepted;
20130
- };
19983
+ this.inputRef = /*#__PURE__*/React.createRef();
20131
19984
  this.focus = () => {
20132
- var _this$inputRef;
20133
- (_this$inputRef = this.inputRef) == null || _this$inputRef.focus();
19985
+ var _this$inputRef$curren;
19986
+ (_this$inputRef$curren = this.inputRef.current) == null || _this$inputRef$curren.focus();
20134
19987
  return true;
20135
19988
  };
20136
19989
  this.focusInputPath = () => {
20137
- var _this$inputRef2;
20138
- (_this$inputRef2 = this.inputRef) == null || _this$inputRef2.focus();
19990
+ var _this$inputRef$curren2;
19991
+ (_this$inputRef$curren2 = this.inputRef.current) == null || _this$inputRef$curren2.focus();
20139
19992
  };
20140
19993
  this.blurInputPath = () => {
20141
- var _this$inputRef3;
20142
- (_this$inputRef3 = this.inputRef) == null || _this$inputRef3.blur();
19994
+ var _this$inputRef$curren3;
19995
+ (_this$inputRef$curren3 = this.inputRef.current) == null || _this$inputRef$curren3.blur();
20143
19996
  };
20144
19997
  this.getInputPaths = () => {
20145
19998
  // The widget itself is an input, so we return a single empty list to
@@ -20147,33 +20000,13 @@ class NumericInput extends React.Component {
20147
20000
  /* c8 ignore next */
20148
20001
  return [[]];
20149
20002
  };
20003
+ /**
20004
+ * Sets the value of the input at the given path.
20005
+ */
20150
20006
  this.setInputValue = (path, newValue, cb) => {
20151
- /* c8 ignore next */
20152
- this.props.onChange({
20153
- currentValue: newValue
20154
- }, cb);
20155
- };
20156
- this.handleChange = (newValue, cb) => {
20157
20007
  this.props.onChange({
20158
20008
  currentValue: newValue
20159
20009
  }, cb);
20160
- this.props.trackInteraction();
20161
- };
20162
- this._handleFocus = () => {
20163
- this.props.onFocus([]);
20164
- this.setState(currentState => {
20165
- return _extends({}, currentState, {
20166
- isFocused: true
20167
- });
20168
- });
20169
- };
20170
- this._handleBlur = () => {
20171
- this.props.onBlur([]);
20172
- this.setState(currentState => {
20173
- return _extends({}, currentState, {
20174
- isFocused: false
20175
- });
20176
- });
20177
20010
  };
20178
20011
  }
20179
20012
  static getUserInputFromProps(props) {
@@ -20182,97 +20015,26 @@ class NumericInput extends React.Component {
20182
20015
  };
20183
20016
  }
20184
20017
  /**
20185
- * Generates a string that demonstrates how to input the various supported
20186
- * answer forms.
20018
+ * Returns the value the user has currently input for this widget.
20187
20019
  */
20188
- examples() {
20189
- var _this$props$answerFor3;
20190
- // if the set of specified forms are empty, allow all forms
20191
- const forms = ((_this$props$answerFor3 = this.props.answerForms) == null ? void 0 : _this$props$answerFor3.length) !== 0 ? this.props.answerForms : Object.keys(formExamples).map(name => {
20192
- return {
20193
- name: name,
20194
- simplify: "required"
20195
- };
20196
- });
20197
- let examples = _.map(forms, form => {
20198
- return formExamples[form.name](form, this.context.strings);
20199
- });
20200
- // Ensure no duplicate tooltip text from simplified and unsimplified
20201
- // versions of the same format
20202
- examples = _.uniq(examples);
20203
- return [this.context.strings.yourAnswer].concat(examples);
20204
- }
20205
20020
  getUserInput() {
20206
20021
  return NumericInput.getUserInputFromProps(this.props);
20207
20022
  }
20023
+
20024
+ /**
20025
+ * Returns the JSON representation of the prompt for this widget.
20026
+ * This is used by the AI to determine the prompt for the widget.
20027
+ */
20208
20028
  getPromptJSON() {
20209
20029
  return getPromptJSON$r(this.props, this.getUserInput());
20210
20030
  }
20211
20031
  render() {
20212
- let labelText = this.props.labelText;
20213
- if (labelText == null || labelText === "") {
20214
- labelText = this.context.strings.yourAnswerLabel;
20215
- }
20216
-
20217
- // To right align a custom keypad we need to wrap it.
20218
- const maybeRightAlignKeypadInput = keypadInput => {
20219
- return this.props.rightAlign ? /*#__PURE__*/React.createElement("div", {
20220
- className: "perseus-input-right-align"
20221
- }, keypadInput) : keypadInput;
20222
- };
20223
- if (this.props.apiOptions.customKeypad) {
20224
- // TODO(charlie): Support "Review Mode".
20225
- return maybeRightAlignKeypadInput( /*#__PURE__*/React.createElement(SimpleKeypadInput, {
20226
- ref: ref => this.inputRef = ref,
20227
- value: this.props.currentValue,
20228
- keypadElement: this.props.keypadElement,
20229
- onChange: this.handleChange,
20230
- onFocus: this._handleFocus,
20231
- onBlur: this._handleBlur
20232
- }));
20233
- }
20234
-
20235
- // Note: This is _very_ similar to what `input-number.jsx` does. If
20236
- // you modify this, double-check if you also need to modify that
20237
- // component.
20238
- const styles = StyleSheet.create({
20239
- input: {
20240
- borderRadius: "3px",
20241
- borderWidth: this.state.isFocused ? "2px" : "1px",
20242
- display: "inline-block",
20243
- fontFamily: `Symbola, "Times New Roman", serif`,
20244
- fontSize: "18px",
20245
- height: "32px",
20246
- lineHeight: "18px",
20247
- padding: this.state.isFocused ? "4px" : "4px 5px",
20248
- // account for added focus border thickness
20249
- textAlign: this.props.rightAlign ? "right" : "left",
20250
- width: this.props.size === "small" ? 40 : 80
20251
- }
20252
- });
20253
- return /*#__PURE__*/React.createElement(InputWithExamples, {
20254
- ref: ref => this.inputRef = ref,
20255
- value: this.props.currentValue,
20256
- onChange: this.handleChange,
20257
- labelText: labelText,
20258
- examples: this.examples(),
20259
- shouldShowExamples: this.shouldShowExamples(),
20260
- onFocus: this._handleFocus,
20261
- onBlur: this._handleBlur,
20262
- id: this.props.widgetId,
20263
- disabled: this.props.apiOptions.readOnly,
20264
- style: styles.input
20265
- });
20032
+ return /*#__PURE__*/React.createElement(NumericInputComponent, _extends({}, this.props, {
20033
+ ref: this.inputRef
20034
+ }));
20266
20035
  }
20267
20036
  }
20268
-
20269
- // TODO(thomas): Currently we receive a list of lists of acceptable answer types
20270
- // and union them down into a single set. It's worth considering whether it
20271
- // wouldn't make more sense to have a single set of acceptable answer types for
20272
- // a given *problem* rather than for each possible [correct/wrong] *answer*.
20273
- // When should two answers to a problem take different answer types?
20274
- // See D27790 for more discussion.
20275
- NumericInput.contextType = PerseusI18nContext;
20037
+ // Transforms the widget options to the props used to render the widget.
20276
20038
  NumericInput.defaultProps = {
20277
20039
  currentValue: "",
20278
20040
  size: "normal",
@@ -20283,52 +20045,23 @@ NumericInput.defaultProps = {
20283
20045
  labelText: "",
20284
20046
  linterContext: linterContextDefault
20285
20047
  };
20286
- const unionAnswerForms = function unionAnswerForms(answerFormsList) {
20287
- // Takes a list of lists of answer forms, and returns a list of the forms
20288
- // in each of these lists in the same order that they're listed in the
20289
- // `formExamples` forms from above.
20290
-
20291
- // uniqueBy takes a list of elements and a function which compares whether
20292
- // two elements are equal, and returns a list of unique elements. This is
20293
- // just a helper function here, but works generally.
20294
- const uniqueBy = function uniqueBy(list, iteratee) {
20295
- // @ts-expect-error - TS2347 - Untyped function calls may not accept type arguments.
20296
- return list.reduce((uniqueList, element) => {
20297
- // For each element, decide whether it's already in the list of
20298
- // unique items.
20299
- const inList = _.find(uniqueList, iteratee.bind(null, element));
20300
- if (inList) {
20301
- return uniqueList;
20302
- }
20303
- return uniqueList.concat([element]);
20304
- }, []);
20305
- };
20306
-
20307
- // Pull out all of the forms from the different lists.
20308
- const allForms = answerFormsList.flat();
20309
- // Pull out the unique forms using uniqueBy.
20310
- const uniqueForms = uniqueBy(allForms, _.isEqual);
20311
- // Sort them by the order they appear in the `formExamples` list.
20312
- const formExampleKeys = Object.keys(formExamples);
20313
- return _.sortBy(uniqueForms, form => {
20314
- return formExampleKeys.indexOf(form.name);
20315
- });
20316
- };
20317
20048
  const propsTransform = function propsTransform(widgetOptions) {
20318
- const rendererProps = _.extend(_.omit(widgetOptions, "answers"), {
20319
- answerForms: unionAnswerForms(
20320
- // Pull out the name of each form and whether that form has
20321
- // required simplification.
20322
- widgetOptions.answers.map(answer => {
20323
- // @ts-expect-error - TS2345 - Argument of type 'readonly MathFormat[] | undefined' is not assignable to parameter of type 'Collection<any>'.
20324
- return _.map(answer.answerForms, form => {
20325
- return {
20326
- simplify: answer.simplify,
20327
- name: form
20328
- };
20329
- });
20330
- }))
20331
- });
20049
+ // Omit the answers from the widget options since they are
20050
+ // not needed for rendering the widget.
20051
+ const _widgetOptions$answer = _extends({}, widgetOptions, {
20052
+ answerForms: unionAnswerForms(
20053
+ // Filter out the correct answers and map them to the answer forms
20054
+ // so that we can generate the examples for the widget.
20055
+ widgetOptions.answers.filter(answer => answer.status === "correct").map(answer => {
20056
+ return (answer.answerForms || []).map(form => {
20057
+ return {
20058
+ simplify: answer.simplify,
20059
+ name: form
20060
+ };
20061
+ });
20062
+ }))
20063
+ }),
20064
+ rendererProps = _objectWithoutPropertiesLoose(_widgetOptions$answer, _excluded$3);
20332
20065
  return rendererProps;
20333
20066
  };
20334
20067
  var NumericInput$1 = {
@@ -20343,23 +20076,11 @@ var NumericInput$1 = {
20343
20076
  getOneCorrectAnswerFromRubric(rubric) {
20344
20077
  const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
20345
20078
  const answerStrings = correctAnswers.map(answer => {
20346
- // Figure out how this answer is supposed to be
20347
- // displayed
20348
- let format = "decimal";
20349
- if (answer.answerForms && answer.answerForms[0]) {
20350
- // NOTE(johnsullivan): This isn't exactly ideal, but
20351
- // it does behave well for all the currently known
20352
- // problems. See D14742 for some discussion on
20353
- // alternate strategies.
20354
- format = answer.answerForms[0];
20355
- }
20356
-
20357
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
20079
+ // Either get the first answer form or default to decimal
20080
+ const format = answer.answerForms && answer.answerForms[0] ? answer.answerForms[0] : "decimal";
20358
20081
  let answerString = KhanMath.toNumericString(answer.value, format);
20359
20082
  if (answer.maxError) {
20360
- answerString += " \u00B1 " +
20361
- // @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
20362
- KhanMath.toNumericString(answer.maxError, format);
20083
+ answerString += " \u00B1 " + KhanMath.toNumericString(answer.maxError, format);
20363
20084
  }
20364
20085
  return answerString;
20365
20086
  });
@@ -21163,7 +20884,7 @@ class HighlightTooltip extends React.PureComponent {
21163
20884
  style: style,
21164
20885
  onMouseEnter: this.props.onMouseEnter,
21165
20886
  onMouseLeave: this.props.onMouseLeave
21166
- }, /*#__PURE__*/React.createElement(Tooltip$1, {
20887
+ }, /*#__PURE__*/React.createElement(Tooltip, {
21167
20888
  content: content,
21168
20889
  opened: true
21169
20890
  }, /*#__PURE__*/React.createElement("div", null)))
@@ -22886,6 +22607,7 @@ const BASE_ICON_SIZE = 10;
22886
22607
  // If you pass in an object you need to specify an SVG path to
22887
22608
  // render along with the width and height of the path (to ensure
22888
22609
  // that it gets scaled correctly).
22610
+
22889
22611
  /**
22890
22612
  * An SVG Icon
22891
22613
  *
@@ -23971,7 +23693,6 @@ const styles$o = StyleSheet.create({
23971
23693
  paddingLeft: phoneMargin
23972
23694
  // paddingRight is handled by responsiveFieldset
23973
23695
  },
23974
-
23975
23696
  responsiveFieldset: {
23976
23697
  paddingRight: phoneMargin,
23977
23698
  minWidth: "auto"
@@ -24377,7 +24098,7 @@ const _choiceTransform = (widgetOptions, strings, problemNum) => {
24377
24098
  // 1) we randomize the order first
24378
24099
  _maybeRandomize(choices)));
24379
24100
  };
24380
- const transform = (widgetOptions, strings, problemNum) => {
24101
+ const transform$1 = (widgetOptions, strings, problemNum) => {
24381
24102
  const choices = _choiceTransform(widgetOptions, strings, problemNum);
24382
24103
  const numCorrect = _.reduce(widgetOptions.choices, function (memo, choice) {
24383
24104
  return choice.correct ? memo + 1 : memo;
@@ -24403,8 +24124,8 @@ var Radio = {
24403
24124
  displayName: "Radio / Multiple choice",
24404
24125
  accessible: true,
24405
24126
  widget: Radio$1,
24406
- transform: transform,
24407
- staticTransform: transform,
24127
+ transform: transform$1,
24128
+ staticTransform: transform$1,
24408
24129
  version: radioLogic.version,
24409
24130
  propUpgrades: radioLogic.widgetOptionsUpgrades,
24410
24131
  isLintable: true
@@ -24975,6 +24696,7 @@ var Definition$1 = {
24975
24696
 
24976
24697
  // There are no required props, but this component can receive props from any
24977
24698
  // deprecated widget
24699
+
24978
24700
  class DeprecatedStandin extends React__default.Component {
24979
24701
  constructor(...args) {
24980
24702
  super(...args);
@@ -25101,20 +24823,20 @@ Dropdown.defaultProps = {
25101
24823
  placeholder: "",
25102
24824
  apiOptions: ApiOptions.defaults
25103
24825
  };
25104
- const optionsTransform = widgetOptions => {
24826
+ function transform(widgetOptions) {
25105
24827
  return {
25106
24828
  placeholder: widgetOptions.placeholder,
25107
24829
  visibleLabel: widgetOptions.visibleLabel,
25108
24830
  ariaLabel: widgetOptions.ariaLabel,
25109
24831
  choices: widgetOptions.choices.map(choice => choice.content)
25110
24832
  };
25111
- };
24833
+ }
25112
24834
  var Dropdown$1 = {
25113
24835
  name: "dropdown",
25114
24836
  displayName: "Drop down",
25115
24837
  accessible: true,
25116
24838
  widget: Dropdown,
25117
- transform: optionsTransform
24839
+ transform
25118
24840
  };
25119
24841
 
25120
24842
  const getPromptJSON$j = renderProps => {
@@ -25178,7 +24900,6 @@ class Explanation extends React.Component {
25178
24900
  // adds space between the right border and any text that follows
25179
24901
  paddingLeft: "2px" // adds space between the left border and the button text
25180
24902
  };
25181
-
25182
24903
  const labelStyle = {
25183
24904
  fontSize: "18px",
25184
24905
  lineHeight: "inherit",
@@ -27502,7 +27223,6 @@ class Interaction extends React.Component {
27502
27223
  }
27503
27224
  });
27504
27225
  }
27505
-
27506
27226
  if (element.type === "parametric") {
27507
27227
  const fn = t => {
27508
27228
  return [
@@ -27555,7 +27275,6 @@ class Interaction extends React.Component {
27555
27275
  }
27556
27276
  });
27557
27277
  }
27558
-
27559
27278
  if (element.type === "label") {
27560
27279
  const coord = [
27561
27280
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
@@ -27900,7 +27619,6 @@ function scaleCommandBy(scaleFactor) {
27900
27619
  command.args[6] * scaleFactor // end y
27901
27620
  ]
27902
27621
  });
27903
-
27904
27622
  default:
27905
27623
  return _extends({}, command, {
27906
27624
  args: command.args.map(c => c * scaleFactor)
@@ -28642,7 +28360,6 @@ const styles$f = StyleSheet.create({
28642
28360
  userSelect: "none",
28643
28361
  borderWidth: 0 // <button> gives us a border by default
28644
28362
  },
28645
-
28646
28363
  // We want to render the element in-place in our fixtures, so allow us to
28647
28364
  // render in the bottom corner in production and in-place in testing.
28648
28365
  hudFixedPosition: {
@@ -28672,7 +28389,6 @@ const styles$f = StyleSheet.create({
28672
28389
  ":hover": {
28673
28390
  backgroundColor: "#a1a5a9" // in between those two grays
28674
28391
  },
28675
-
28676
28392
  ":active": {
28677
28393
  backgroundColor: gray68
28678
28394
  }
@@ -28680,7 +28396,7 @@ const styles$f = StyleSheet.create({
28680
28396
  });
28681
28397
 
28682
28398
  const InfoTipBase = props => {
28683
- return /*#__PURE__*/React.createElement(Tooltip$1, {
28399
+ return /*#__PURE__*/React.createElement(Tooltip, {
28684
28400
  content: /*#__PURE__*/React.createElement(Body, {
28685
28401
  style: styles$e.body
28686
28402
  }, props.children),
@@ -28720,7 +28436,6 @@ class InfoTip extends React.Component {
28720
28436
  });
28721
28437
  /* eslint-enable react/no-did-mount-set-state */
28722
28438
  }
28723
-
28724
28439
  render() {
28725
28440
  if (this.state.didMount) {
28726
28441
  return /*#__PURE__*/React.createElement(InfoTipBase, this.props);
@@ -28952,7 +28667,6 @@ class NumberInput extends React.Component {
28952
28667
  if (val !== Math.floor(val)) {
28953
28668
  return; // bail if not an integer
28954
28669
  }
28955
-
28956
28670
  if (e.key === "ArrowUp") {
28957
28671
  val = val + 1;
28958
28672
  } else if (e.key === "ArrowDown") {
@@ -29086,20 +28800,20 @@ RangeInput.defaultProps = {
29086
28800
  };
29087
28801
 
29088
28802
  var components = /*#__PURE__*/Object.freeze({
29089
- __proto__: null,
29090
- ButtonGroup: ButtonGroup,
29091
- Graphie: Graphie,
29092
- HUD: HUD,
29093
- Icon: Icon,
29094
- InfoTip: InfoTip,
29095
- InlineIcon: InlineIcon,
29096
- MathInput: MathInput,
29097
- MultiButtonGroup: MultiButtonGroup,
29098
- NumberInput: NumberInput,
29099
- RangeInput: RangeInput,
29100
- SvgImage: SvgImage,
29101
- TextInput: TextInput,
29102
- TextListEditor: TextListEditor
28803
+ __proto__: null,
28804
+ ButtonGroup: ButtonGroup,
28805
+ Graphie: Graphie,
28806
+ HUD: HUD,
28807
+ Icon: Icon,
28808
+ InfoTip: InfoTip,
28809
+ InlineIcon: InlineIcon,
28810
+ MathInput: MathInput,
28811
+ MultiButtonGroup: MultiButtonGroup,
28812
+ NumberInput: NumberInput,
28813
+ RangeInput: RangeInput,
28814
+ SvgImage: SvgImage,
28815
+ TextInput: TextInput,
28816
+ TextListEditor: TextListEditor
29103
28817
  });
29104
28818
 
29105
28819
  /**
@@ -29228,9 +28942,9 @@ const LockedFunction = props => {
29228
28942
  const {
29229
28943
  color,
29230
28944
  strokeStyle,
29231
- directionalAxis
28945
+ directionalAxis,
28946
+ domain
29232
28947
  } = props;
29233
- const domain = props.domain ? [Number.isFinite(props.domain[0]) ? props.domain[0] : -Infinity, Number.isFinite(props.domain[1]) ? props.domain[1] : Infinity] : undefined;
29234
28948
  const plotProps = {
29235
28949
  color: lockedFigureColors[color],
29236
28950
  style: strokeStyle,
@@ -30642,7 +30356,7 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
30642
30356
  }));
30643
30357
  return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && /*#__PURE__*/React.createElement(Hairlines, {
30644
30358
  point: point
30645
- }), showTooltips ? /*#__PURE__*/React.createElement(Tooltip$1, {
30359
+ }), showTooltips ? /*#__PURE__*/React.createElement(Tooltip, {
30646
30360
  autoUpdate: true,
30647
30361
  backgroundColor: wbColorName,
30648
30362
  content: `(${point[X]}, ${point[Y]})`,
@@ -30775,6 +30489,9 @@ const MovableLine = props => {
30775
30489
  onMoveLine = () => {},
30776
30490
  onMovePoint = () => {}
30777
30491
  } = props;
30492
+ const {
30493
+ snapStep
30494
+ } = useGraphConfig();
30778
30495
 
30779
30496
  // Aria live states for (0) point 1, (1) point 2, and (2) grab handle.
30780
30497
  // When moving an element, set its aria live to "polite" and the others
@@ -30806,7 +30523,8 @@ const MovableLine = props => {
30806
30523
  onMove: p => {
30807
30524
  setAriaLives(["polite", "off", "off"]);
30808
30525
  onMovePoint(0, p);
30809
- }
30526
+ },
30527
+ constrain: getMovableLineKeyboardConstraint([start, end], snapStep, 0)
30810
30528
  });
30811
30529
  const {
30812
30530
  visiblePoint: visiblePoint2,
@@ -30821,7 +30539,8 @@ const MovableLine = props => {
30821
30539
  onMove: p => {
30822
30540
  setAriaLives(["off", "polite", "off"]);
30823
30541
  onMovePoint(1, p);
30824
- }
30542
+ },
30543
+ constrain: getMovableLineKeyboardConstraint([start, end], snapStep, 1)
30825
30544
  });
30826
30545
  const line = /*#__PURE__*/React.createElement(Line$1, {
30827
30546
  ariaLabel: ariaLabels == null ? void 0 : ariaLabels.grabHandleAriaLabel,
@@ -30927,6 +30646,34 @@ const Line$1 = props => {
30927
30646
  color: stroke
30928
30647
  }));
30929
30648
  };
30649
+ const getMovableLineKeyboardConstraint = (line, snapStep, pointIndex) => {
30650
+ // Separate the two points into their own variables, and determine which point is being moved
30651
+ const coordToBeMoved = line[pointIndex];
30652
+ const otherPoint = line[1 - pointIndex];
30653
+
30654
+ // Create a helper function that moves the point and then checks
30655
+ // if it overlaps with the other point in the line after the move.
30656
+ const movePointWithConstraint = moveFunc => {
30657
+ // Move the point
30658
+ let movedCoord = moveFunc(coordToBeMoved);
30659
+ // If the moved point overlaps with the other point in the line,
30660
+ // move the point again.
30661
+ if (vec.dist(movedCoord, otherPoint) === 0) {
30662
+ movedCoord = moveFunc(movedCoord);
30663
+ }
30664
+ return movedCoord;
30665
+ };
30666
+
30667
+ // Check if the new point will overlap the other point.
30668
+ // If it will, we need to snap the point to the left or right an additional
30669
+ // snapStep to avoid overlap.
30670
+ return {
30671
+ up: movePointWithConstraint(coord => vec.add(coord, [0, snapStep[1]])),
30672
+ down: movePointWithConstraint(coord => vec.sub(coord, [0, snapStep[1]])),
30673
+ left: movePointWithConstraint(coord => vec.sub(coord, [snapStep[0], 0])),
30674
+ right: movePointWithConstraint(coord => vec.add(coord, [snapStep[0], 0]))
30675
+ };
30676
+ };
30930
30677
  function trimRange(range, graphDimensionsInPixels) {
30931
30678
  const pixelsToTrim = 4;
30932
30679
  const [xRange, yRange] = range;
@@ -31268,7 +31015,8 @@ function CircleGraph(props) {
31268
31015
  } = props;
31269
31016
  const {
31270
31017
  center,
31271
- radiusPoint
31018
+ radiusPoint,
31019
+ snapStep
31272
31020
  } = graphState;
31273
31021
  const {
31274
31022
  strings,
@@ -31331,7 +31079,8 @@ function CircleGraph(props) {
31331
31079
  onMove: newRadiusPoint => {
31332
31080
  setRadiusPointAriaLive("polite");
31333
31081
  dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
31334
- }
31082
+ },
31083
+ constrain: getCircleKeyboardConstraint(center, radiusPoint, snapStep)
31335
31084
  }), /*#__PURE__*/React.createElement("g", {
31336
31085
  id: radiusId,
31337
31086
  style: a11y.srOnly
@@ -31442,6 +31191,30 @@ function getCircleGraphDescription(state, i18n) {
31442
31191
  const strings = describeCircleGraph(state, i18n);
31443
31192
  return strings.srCircleInteractiveElement;
31444
31193
  }
31194
+ const getCircleKeyboardConstraint = (center, radiusPoint, snapStep) => {
31195
+ // Create a helper function that moves the point and then checks
31196
+ // if it overlaps with the center point after the move.
31197
+ const movePointWithConstraint = moveFunc => {
31198
+ // Move the point
31199
+ let movedCoord = moveFunc(radiusPoint);
31200
+ // If the moved point overlaps with the center point,
31201
+ // move the point again.
31202
+ if (vec.dist(movedCoord, center) === 0) {
31203
+ movedCoord = moveFunc(movedCoord);
31204
+ }
31205
+ return movedCoord;
31206
+ };
31207
+
31208
+ // Check if the new point overlaps the center point.
31209
+ // If it does, we need to snap the point to the left
31210
+ // or right an additional snapStep to avoid the overlap.
31211
+ return {
31212
+ up: movePointWithConstraint(coord => vec.add(coord, [0, snapStep[1]])),
31213
+ down: movePointWithConstraint(coord => vec.sub(coord, [0, snapStep[1]])),
31214
+ left: movePointWithConstraint(coord => vec.sub(coord, [snapStep[0], 0])),
31215
+ right: movePointWithConstraint(coord => vec.add(coord, [snapStep[0], 0]))
31216
+ };
31217
+ };
31445
31218
 
31446
31219
  // Exported for testing
31447
31220
  function describeCircleGraph(state, i18n) {
@@ -31454,6 +31227,7 @@ function describeCircleGraph(state, i18n) {
31454
31227
  radiusPoint
31455
31228
  } = state;
31456
31229
  const radius = getRadius(state);
31230
+ const isRadiusOnRight = radiusPoint[X] >= center[X];
31457
31231
 
31458
31232
  // Aria label strings
31459
31233
  const srCircleGraph = strings.srCircleGraph;
@@ -31461,7 +31235,10 @@ function describeCircleGraph(state, i18n) {
31461
31235
  centerX: srFormatNumber(center[0], locale),
31462
31236
  centerY: srFormatNumber(center[1], locale)
31463
31237
  });
31464
- const srCircleRadiusPoint = strings.srCircleRadiusPoint({
31238
+ const srCircleRadiusPoint = isRadiusOnRight ? strings.srCircleRadiusPointRight({
31239
+ radiusPointX: srFormatNumber(radiusPoint[0], locale),
31240
+ radiusPointY: srFormatNumber(radiusPoint[1], locale)
31241
+ }) : strings.srCircleRadiusPointLeft({
31465
31242
  radiusPointX: srFormatNumber(radiusPoint[0], locale),
31466
31243
  radiusPointY: srFormatNumber(radiusPoint[1], locale)
31467
31244
  });
@@ -31627,6 +31404,12 @@ const LinearSystemGraph = props => {
31627
31404
  locale
31628
31405
  } = usePerseusI18n();
31629
31406
  const id = React.useId();
31407
+ const intersectionId = `${id}-intersection`;
31408
+ const intersectionPoint = geometry.getLineIntersection(lines[0], lines[1]);
31409
+ const intersectionDescription = intersectionPoint ? strings.srLinearSystemIntersection({
31410
+ x: srFormatNumber(intersectionPoint[0], locale),
31411
+ y: srFormatNumber(intersectionPoint[1], locale)
31412
+ }) : strings.srLinearSystemParallel;
31630
31413
  const linesAriaInfo = lines.map((line, i) => {
31631
31414
  return {
31632
31415
  pointsDescriptionId: `${id}-line${i + 1}-points`,
@@ -31643,13 +31426,14 @@ const LinearSystemGraph = props => {
31643
31426
  slopeDescription: getSlopeStringForLine(line, strings)
31644
31427
  };
31645
31428
  });
31429
+ const individualLineDescriptions = linesAriaInfo.map(({
31430
+ pointsDescriptionId,
31431
+ interceptDescriptionId,
31432
+ slopeDescriptionId
31433
+ }) => `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`).join(" ");
31646
31434
  return /*#__PURE__*/React.createElement("g", {
31647
31435
  "aria-label": strings.srLinearSystemGraph,
31648
- "aria-describedby": linesAriaInfo.map(({
31649
- pointsDescriptionId,
31650
- interceptDescriptionId,
31651
- slopeDescriptionId
31652
- }) => `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`).join(" ")
31436
+ "aria-describedby": `${individualLineDescriptions} ${intersectionId}`
31653
31437
  }, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(MovableLine, {
31654
31438
  key: i,
31655
31439
  points: line,
@@ -31666,9 +31450,15 @@ const LinearSystemGraph = props => {
31666
31450
  x: srFormatNumber(line[1][0], locale),
31667
31451
  y: srFormatNumber(line[1][1], locale)
31668
31452
  }),
31669
- grabHandleAriaLabel: `${linesAriaInfo[i].interceptDescription} ${linesAriaInfo[i].slopeDescription}`
31453
+ grabHandleAriaLabel: strings.srLinearSystemGrabHandle({
31454
+ lineNumber: i + 1,
31455
+ point1X: srFormatNumber(line[0][0], locale),
31456
+ point1Y: srFormatNumber(line[0][1], locale),
31457
+ point2X: srFormatNumber(line[1][0], locale),
31458
+ point2Y: srFormatNumber(line[1][1], locale)
31459
+ })
31670
31460
  },
31671
- ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId}`,
31461
+ ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId} ${intersectionId}`,
31672
31462
  onMoveLine: delta => {
31673
31463
  dispatch(actions.linearSystem.moveLine(i, delta));
31674
31464
  },
@@ -31697,7 +31487,10 @@ const LinearSystemGraph = props => {
31697
31487
  key: slopeDescriptionId,
31698
31488
  id: slopeDescriptionId,
31699
31489
  style: a11y.srOnly
31700
- }, slopeDescription))));
31490
+ }, slopeDescription))), /*#__PURE__*/React.createElement("g", {
31491
+ id: intersectionId,
31492
+ style: a11y.srOnly
31493
+ }, intersectionDescription));
31701
31494
  };
31702
31495
 
31703
31496
  // Exported for testing
@@ -32358,7 +32151,8 @@ function QuadraticGraph(props) {
32358
32151
  graphState
32359
32152
  } = props;
32360
32153
  const {
32361
- coords
32154
+ coords,
32155
+ snapStep
32362
32156
  } = graphState;
32363
32157
  const {
32364
32158
  strings,
@@ -32411,6 +32205,7 @@ function QuadraticGraph(props) {
32411
32205
  ariaLabel: `${srQuadraticPoint}${srVertex}`,
32412
32206
  point: coord,
32413
32207
  sequenceNumber: i + 1,
32208
+ constrain: getQuadraticKeyboardConstraint(coords, snapStep, i),
32414
32209
  onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
32415
32210
  });
32416
32211
  }), srQuadraticDirection && /*#__PURE__*/React.createElement("g", {
@@ -32499,6 +32294,60 @@ function describeQuadraticGraph(state, i18n) {
32499
32294
  srQuadraticInteractiveElements
32500
32295
  };
32501
32296
  }
32297
+ const getQuadraticKeyboardConstraint = (coords, snapStep, pointMoved) => {
32298
+ // Make newCoords mutable
32299
+ const newCoords = [coords[0], coords[1], coords[2]];
32300
+
32301
+ // Get the point that is being moved
32302
+ const coordToBeMoved = newCoords[pointMoved];
32303
+
32304
+ // Create a function to validate and adjust the movement of the point
32305
+ const movePointWithConstraint = moveFunc => {
32306
+ // Move the point the desired direction
32307
+ let movedCoord = moveFunc(coordToBeMoved);
32308
+ newCoords[pointMoved] = movedCoord;
32309
+
32310
+ // If these new coordinates are valid, return the moved coord
32311
+ if (areCoordsValid(newCoords)) {
32312
+ return movedCoord;
32313
+ }
32314
+
32315
+ // If the new coordinates are invalid, we need to move the point an additional
32316
+ // snapStep to avoid creating an invalid quadratic graph.
32317
+ movedCoord = moveFunc(movedCoord);
32318
+ newCoords[pointMoved] = movedCoord;
32319
+
32320
+ // If these updated coordinates are valid now, we can return the new coordinates.
32321
+ if (areCoordsValid(newCoords)) {
32322
+ return movedCoord;
32323
+ }
32324
+
32325
+ // Otherwise, if the new coordinates are still invalid, we need to move the point one final snapStep.
32326
+ // This is to support the edge case where all of the points are each exactly one snapStep away
32327
+ // from each other.
32328
+ // Eg. When coords = [[0, 0], [1, 0], [2, 0]], snapStep = [1, 1], and we're moving coords[0] to the right.
32329
+ return moveFunc(movedCoord);
32330
+ };
32331
+ return {
32332
+ up: vec.add(coordToBeMoved, [0, snapStep[1]]),
32333
+ down: vec.sub(coordToBeMoved, [0, snapStep[1]]),
32334
+ // For horizontal movement, we need to ensure that the points are not on the same vertical line.
32335
+ left: movePointWithConstraint(coord => vec.sub(coord, [snapStep[0], 0])),
32336
+ right: movePointWithConstraint(coord => vec.add(coord, [snapStep[0], 0]))
32337
+ };
32338
+ };
32339
+ const areCoordsValid = coords => {
32340
+ const p1 = coords[0];
32341
+ const p2 = coords[1];
32342
+ const p3 = coords[2];
32343
+
32344
+ // If any of the points share the same x-coordinate,
32345
+ // we are unable to calculate the coefficients, and the graph is invalid.
32346
+ if (p1[0] === p2[0] || p2[0] === p3[0] || p1[0] === p3[0]) {
32347
+ return false;
32348
+ }
32349
+ return true;
32350
+ };
32502
32351
 
32503
32352
  function renderRayGraph(state, dispatch, i18n) {
32504
32353
  return {
@@ -32749,7 +32598,8 @@ function SinusoidGraph(props) {
32749
32598
  // Note: The order of the coordinates is important:
32750
32599
  // The coords[0] is the root and the coords[1] is the first peak
32751
32600
  const {
32752
- coords
32601
+ coords,
32602
+ snapStep
32753
32603
  } = graphState;
32754
32604
 
32755
32605
  // The coefficients are used to calculate the sinusoid equation, plot the graph, and to indicate
@@ -32788,11 +32638,37 @@ function SinusoidGraph(props) {
32788
32638
  key: "point-" + i,
32789
32639
  point: coord,
32790
32640
  sequenceNumber: i + 1,
32641
+ constrain: getSinusoidKeyboardConstraint(coords, snapStep, i),
32791
32642
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
32792
32643
  })), /*#__PURE__*/React.createElement("g", {
32793
32644
  id: descriptionId
32794
32645
  }, srSinusoidDescription));
32795
32646
  }
32647
+ const getSinusoidKeyboardConstraint = (coords, snapStep, pointIndex) => {
32648
+ // Separate the two points into their own variables, and determine which point is being moved
32649
+ const coordToBeMoved = coords[pointIndex];
32650
+ const otherPoint = coords[1 - pointIndex];
32651
+
32652
+ // Create a helper function that checks if the new point is on the same
32653
+ // vertical line as the other point. If it is, we need to move the point
32654
+ // an additional snapStep.
32655
+ const movePointWithConstraint = moveFunc => {
32656
+ // Move the point
32657
+ let movedCoord = moveFunc(coordToBeMoved);
32658
+ // If the moved point overlaps with the other point in the line,
32659
+ // move the point again.
32660
+ if (movedCoord[X] === otherPoint[X]) {
32661
+ movedCoord = moveFunc(movedCoord);
32662
+ }
32663
+ return movedCoord;
32664
+ };
32665
+ return {
32666
+ up: movePointWithConstraint(coord => vec.add(coord, [0, snapStep[1]])),
32667
+ down: movePointWithConstraint(coord => vec.sub(coord, [0, snapStep[1]])),
32668
+ left: movePointWithConstraint(coord => vec.sub(coord, [snapStep[0], 0])),
32669
+ right: movePointWithConstraint(coord => vec.add(coord, [snapStep[0], 0]))
32670
+ };
32671
+ };
32796
32672
 
32797
32673
  // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
32798
32674
  const computeSine = function computeSine(x,
@@ -32844,6 +32720,14 @@ function describeSinusoidGraph(state, i18n) {
32844
32720
  const [root, peak] = coords;
32845
32721
  const diffX = Math.abs(peak[X] - root[X]);
32846
32722
  const diffY = Math.abs(peak[Y] - root[Y]);
32723
+ const formattedRoot = {
32724
+ x: srFormatNumber(root[X], locale),
32725
+ y: srFormatNumber(root[Y], locale)
32726
+ };
32727
+ const formattedPeak = {
32728
+ x: srFormatNumber(peak[X], locale),
32729
+ y: srFormatNumber(peak[Y], locale)
32730
+ };
32847
32731
  const srSinusoidGraph = strings.srSinusoidGraph;
32848
32732
  const srSinusoidDescription = strings.srSinusoidDescription({
32849
32733
  minValue: srFormatNumber(root[Y] - diffY, locale),
@@ -32851,14 +32735,8 @@ function describeSinusoidGraph(state, i18n) {
32851
32735
  cycleStart: srFormatNumber(root[X] - 2 * diffX, locale),
32852
32736
  cycleEnd: srFormatNumber(root[X] + 2 * diffX, locale)
32853
32737
  });
32854
- const srSinusoidRootPoint = strings.srSinusoidRootPoint({
32855
- x: srFormatNumber(root[X], locale),
32856
- y: srFormatNumber(root[Y], locale)
32857
- });
32858
- const srSinusoidPeakPoint = strings.srSinusoidPeakPoint({
32859
- x: srFormatNumber(peak[X], locale),
32860
- y: srFormatNumber(peak[Y], locale)
32861
- });
32738
+ const srSinusoidRootPoint = strings.srSinusoidRootPoint(formattedRoot);
32739
+ const srSinusoidPeakPoint = peak[Y] === root[Y] ? strings.srSinusoidFlatPoint(formattedPeak) : peak[Y] > root[Y] ? strings.srSinusoidMaxPoint(formattedPeak) : strings.srSinusoidMinPoint(formattedPeak);
32862
32740
  const srSinusoidInteractiveElements = strings.srInteractiveElements({
32863
32741
  elements: strings.srSinusoidInteractiveElements({
32864
32742
  point1X: srFormatNumber(root[X], locale),
@@ -32880,16 +32758,17 @@ const {
32880
32758
  calculateAngleInDegrees,
32881
32759
  convertDegreesToRadians
32882
32760
  } = angles;
32883
- const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
32761
+ const protractorImage = "https://cdn.kastatic.org/images/perseus/protractor.svg";
32884
32762
 
32885
32763
  // The vector from the center of the protractor to the top left corner of the
32886
32764
  // protractor image, in pixels. Used for positioning.
32887
- const centerToTopLeft = [-180, -170];
32765
+ const centerToTopLeft = [-195, -190];
32888
32766
 
32889
32767
  // The vector from the center of the protractor to the center of the rotation
32890
32768
  // handle.
32891
- const centerToRotationHandle = [-176, -15];
32769
+ const centerToRotationHandle = [-201, -15];
32892
32770
  function Protractor() {
32771
+ const staticUrl = getDependencies().staticUrl;
32893
32772
  const {
32894
32773
  range,
32895
32774
  snapStep
@@ -32901,7 +32780,9 @@ function Protractor() {
32901
32780
  const [center, setCenter] = useState(initialCenter);
32902
32781
  const [rotationHandleOffset, setRotationHandleOffset] = useState(centerToRotationHandle);
32903
32782
  const draggableRef = useRef(null);
32904
- useDraggable({
32783
+ const {
32784
+ dragging
32785
+ } = useDraggable({
32905
32786
  gestureTarget: draggableRef,
32906
32787
  onMove: setCenter,
32907
32788
  point: center,
@@ -32925,10 +32806,11 @@ function Protractor() {
32925
32806
  ref: draggableRef,
32926
32807
  transform: `translate(${topLeftPx[X]}, ${topLeftPx[Y]}), rotate(${angle})`,
32927
32808
  style: {
32928
- transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`
32809
+ transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`,
32810
+ cursor: dragging ? "grabbing" : "grab"
32929
32811
  }
32930
32812
  }, /*#__PURE__*/React.createElement("image", {
32931
- href: protractorImage
32813
+ href: staticUrl(protractorImage)
32932
32814
  }), /*#__PURE__*/React.createElement("g", {
32933
32815
  transform: `translate(5, ${-centerToTopLeft[1]})`,
32934
32816
  ref: rotationHandleRef
@@ -34803,7 +34685,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
34803
34685
 
34804
34686
  // If the graph is static, it always displays the correct answer. This is
34805
34687
  // standard behavior for Perseus widgets (e.g. compare the Radio widget).
34806
- if (props.static) {
34688
+ if (props.static && props.correct) {
34807
34689
  return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
34808
34690
  state: initializeGraphState(_extends({}, props, {
34809
34691
  graph: props.correct
@@ -34822,7 +34704,7 @@ const {
34822
34704
  } = angles;
34823
34705
  const {
34824
34706
  getLineEquation,
34825
- getLineIntersection,
34707
+ getLineIntersectionString,
34826
34708
  magnitude,
34827
34709
  vector
34828
34710
  } = geometry;
@@ -35180,7 +35062,7 @@ class InteractiveGraph extends React.Component {
35180
35062
  }
35181
35063
  static getLinearSystemEquationString(props) {
35182
35064
  const coords = InteractiveGraph.getLinearSystemCoords(props.graph, props);
35183
- return "\n" + getLineEquation(coords[0][0], coords[0][1]) + "\n" + getLineEquation(coords[1][0], coords[1][1]) + "\n" + getLineIntersection(coords[0], coords[1]);
35065
+ return "\n" + getLineEquation(coords[0][0], coords[0][1]) + "\n" + getLineEquation(coords[1][0], coords[1][1]) + "\n" + getLineIntersectionString(coords[0], coords[1]);
35184
35066
  }
35185
35067
  static getPointEquationString(props) {
35186
35068
  if (props.graph.type !== "point") {
@@ -35628,7 +35510,6 @@ const styles$a = StyleSheet.create({
35628
35510
  markerCorrect: {
35629
35511
  background: "#00880b" // WB green darkened by 18%
35630
35512
  },
35631
-
35632
35513
  markerIncorrect: {
35633
35514
  background: color.offBlack64
35634
35515
  }
@@ -36021,7 +35902,6 @@ class LabelImage extends React.Component {
36021
35902
  const adjustPillDistance = {
36022
35903
  [`margin${markerPosition.charAt(0).toUpperCase() + markerPosition.slice(1)}`]: 10 // move pill further from marker
36023
35904
  };
36024
-
36025
35905
  const answerChoicesActive = index === activeMarkerIndex;
36026
35906
  const showAnswerChoice = marker.selected && !answerChoicesActive && !this.state.hideAnswers;
36027
35907
  return /*#__PURE__*/React.createElement(View, {
@@ -36466,7 +36346,6 @@ class Draggable extends React.Component {
36466
36346
  if (this.props.width) {
36467
36347
  style.width = this.props.width + 1; // Fix for non-integer widths
36468
36348
  }
36469
-
36470
36349
  if (this.props.height) {
36471
36350
  style.height = this.props.height;
36472
36351
  }
@@ -36808,8 +36687,7 @@ class Sortable extends React.Component {
36808
36687
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
36809
36688
  margin = "0 0 " + this.props.margin + "px 0"; // bottom
36810
36689
  }
36811
-
36812
- cards.push( /*#__PURE__*/React.createElement(Draggable, {
36690
+ cards.push(/*#__PURE__*/React.createElement(Draggable, {
36813
36691
  content: item.option,
36814
36692
  key: item.key,
36815
36693
  state: item.state
@@ -36862,7 +36740,7 @@ class Sortable extends React.Component {
36862
36740
  this, item.key)
36863
36741
  }));
36864
36742
  if (item.state === ItemState.DRAGGING || item.state === ItemState.ANIMATING) {
36865
- cards.push( /*#__PURE__*/React.createElement(Placeholder, {
36743
+ cards.push(/*#__PURE__*/React.createElement(Placeholder, {
36866
36744
  key: "placeholder_" + item.key,
36867
36745
  ref: "placeholder_" + item.key,
36868
36746
  width: item.width,
@@ -38652,7 +38530,6 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
38652
38530
  if (!_.isFinite(props.tickStep) || props.tickStep <= 0) {
38653
38531
  return []; // this has screwed me for the last time!
38654
38532
  }
38655
-
38656
38533
  const results = [];
38657
38534
 
38658
38535
  // For convenience, extract some props into separate variables
@@ -38723,6 +38600,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
38723
38600
 
38724
38601
  // TODO: most widgets use some like Widget<Something, PerseusNumberLineWidgetOptions>
38725
38602
  // should this one?
38603
+
38726
38604
  class NumberLine extends React.Component {
38727
38605
  constructor(..._args) {
38728
38606
  super(..._args);
@@ -39088,7 +38966,6 @@ const numberLineTransform = editorProps => {
39088
38966
  } else {
39089
38967
  numDivisions = undefined; // send to getDefaultProps()
39090
38968
  }
39091
-
39092
38969
  _.extend(props, {
39093
38970
  numLinePosition: numLinePosition,
39094
38971
  numDivisions: numDivisions,
@@ -39113,7 +38990,6 @@ const staticTransform$1 = editorProps => {
39113
38990
  } else {
39114
38991
  numDivisions = undefined; // send to getDefaultProps()
39115
38992
  }
39116
-
39117
38993
  _.extend(props, {
39118
38994
  numLinePosition: numLinePosition,
39119
38995
  numDivisions: numDivisions,
@@ -40102,7 +39978,6 @@ class Plotter extends React.Component {
40102
39978
  const initialY = isMobile ? c.scaleY : 0;
40103
39979
  for (let y = initialY; y <= c.dimY; y += c.scaleY) {
40104
39980
  graphie.label([0, y], KhanMath.roundToApprox(y, 2), "left", /* isTeX */true /* for the \approx symbol */);
40105
-
40106
39981
  graphie.style({
40107
39982
  stroke: isMobile ? "#e9ebec" : "#000",
40108
39983
  strokeWidth: 1,
@@ -41312,9 +41187,51 @@ const init = function init() {
41312
41187
  replaceDeprecatedWidgets();
41313
41188
  };
41314
41189
 
41190
+ /**
41191
+ * Adds the given perseus library version information to the __perseus_debug__
41192
+ * object and ensures that the object is attached to `globalThis` (`window` in
41193
+ * browser environments).
41194
+ *
41195
+ * This allows each library to provide runtime version information to assist in
41196
+ * debugging in production environments.
41197
+ */
41198
+ const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
41199
+ // If the library version is the default value, then we don't want to
41200
+ // prefix it with a "v" to indicate that it is a version number.
41201
+ let prefix = "v";
41202
+ if (libraryVersion === "__lib_version__") {
41203
+ prefix = "";
41204
+ }
41205
+ const formattedVersion = `${prefix}${libraryVersion}`;
41206
+ if (typeof globalThis !== "undefined") {
41207
+ var _globalThis$__perseus;
41208
+ globalThis.__perseus_debug__ = (_globalThis$__perseus = globalThis.__perseus_debug__) != null ? _globalThis$__perseus : {};
41209
+ const existingVersionEntry = globalThis.__perseus_debug__[libraryName];
41210
+ if (existingVersionEntry) {
41211
+ // If we already have an entry and it doesn't match the registered
41212
+ // version, we morph the entry into an array and log a warning.
41213
+ if (existingVersionEntry !== formattedVersion) {
41214
+ // Existing entry might be an array already (oops, at least 2
41215
+ // versions of the library already loaded!).
41216
+ const allVersions = Array.isArray(existingVersionEntry) ? existingVersionEntry : [existingVersionEntry];
41217
+ allVersions.push(formattedVersion);
41218
+ globalThis.__perseus_debug__[libraryName] = allVersions;
41219
+
41220
+ // eslint-disable-next-line no-console
41221
+ console.warn(`Multiple versions of ${libraryName} loaded on this page: ${allVersions.sort().join(", ")}`);
41222
+ }
41223
+ } else {
41224
+ globalThis.__perseus_debug__[libraryName] = formattedVersion;
41225
+ }
41226
+ } else {
41227
+ // eslint-disable-next-line no-console
41228
+ console.warn(`globalThis not found found (${formattedVersion})`);
41229
+ }
41230
+ };
41231
+
41315
41232
  // This file is processed by a Rollup plugin (replace) to inject the production
41316
41233
  const libName = "@khanacademy/perseus";
41317
- const libVersion = "54.1.0";
41234
+ const libVersion = "55.0.0";
41318
41235
  addLibraryVersionToPerseusDebug(libName, libVersion);
41319
41236
 
41320
41237
  const apiVersion = {
@@ -41708,7 +41625,6 @@ class HintsRenderer extends React.Component {
41708
41625
  ReactDOM__default.findDOMNode(this.refs["hintRenderer" + pos]).focus(); // eslint-disable-line react/no-string-refs
41709
41626
  }
41710
41627
  }
41711
-
41712
41628
  render() {
41713
41629
  const apiOptions = this.getApiOptions();
41714
41630
  const hintsVisible = this._hintsVisible();