@khanacademy/perseus 54.1.0 → 55.0.1

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 +902 -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 +1036 -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,183 @@ 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
+ testId: "input-with-examples",
2030
+ onFocus: onFocus,
2031
+ onBlur: onBlur,
2032
+ onKeyDown: onKeyDown
2033
+ // @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>'.
2034
+ ,
2035
+ autoCorrect: "off",
2036
+ autoCapitalize: "off",
2037
+ autoComplete: "off"
2038
+ });
2039
+ }
2040
+ }
2041
+ TextInput.defaultProps = {
2042
+ value: "",
2043
+ disabled: false
2044
+ };
2045
+
1850
2046
  /**
1851
2047
  * A React context for keeping track of whether an component is fully
1852
2048
  * loaded. This is used to know when SvgImage and (Zoomable)TeX are finished
@@ -2411,9 +2607,11 @@ const Util = {
2411
2607
  // TODO(LP-11481): Change the primary API we use for logging in Perseus to use
2412
2608
  // getDependencies() internally... simplify usage to just using a "singleton"
2413
2609
  // which get's the injected logger.
2610
+
2414
2611
  /**
2415
2612
  * Provides logging infrastructure for Perseus
2416
2613
  */
2614
+
2417
2615
  const Log = {
2418
2616
  // Logs a message
2419
2617
  log: (message, extra) => {
@@ -3087,40 +3285,40 @@ const articleMaxWidthInPx = 688;
3087
3285
  const articleMaxWidthTableInPx = 512;
3088
3286
 
3089
3287
  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
3288
+ __proto__: null,
3289
+ baseFontFamily: baseFontFamily,
3290
+ boldFontFamily: boldFontFamily,
3291
+ kaGreen: kaGreen,
3292
+ white: white,
3293
+ gray97: gray97,
3294
+ gray85: gray85,
3295
+ gray76: gray76,
3296
+ gray68: gray68,
3297
+ gray17: gray17,
3298
+ pureSmMin: pureSmMin$1,
3299
+ pureMdMin: pureMdMin$1,
3300
+ pureLgMin: pureLgMin$1,
3301
+ pureXlMin: pureXlMin$1,
3302
+ pureXsMax: pureXsMax$1,
3303
+ pureSmMax: pureSmMax$1,
3304
+ pureMdMax: pureMdMax$1,
3305
+ pureLgMax: pureLgMax$1,
3306
+ tableBackgroundAccent: tableBackgroundAccent,
3307
+ phoneMargin: phoneMargin,
3308
+ negativePhoneMargin: negativePhoneMargin,
3309
+ hintBorderWidth: hintBorderWidth,
3310
+ baseUnitPx: baseUnitPx,
3311
+ interactiveSizes: interactiveSizes$1,
3312
+ circleSize: circleSize$1,
3313
+ radioMarginWidth: radioMarginWidth$1,
3314
+ warningColor: warningColor,
3315
+ warningColorHover: warningColorHover,
3316
+ warningColorActive: warningColorActive,
3317
+ publishBlockingErrorColor: publishBlockingErrorColor,
3318
+ radioBorderColor: radioBorderColor$1,
3319
+ checkedColor: checkedColor$1,
3320
+ articleMaxWidthInPx: articleMaxWidthInPx,
3321
+ articleMaxWidthTableInPx: articleMaxWidthTableInPx
3124
3322
  });
3125
3323
 
3126
3324
  /**
@@ -3470,16 +3668,6 @@ const InteractiveUtil = {
3470
3668
  }
3471
3669
  };
3472
3670
 
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
3671
  /*!
3484
3672
  * Raphael 1.5.2 - JavaScript Vector Library
3485
3673
  *
@@ -3536,7 +3724,7 @@ var Raphael$1 = (function () {
3536
3724
  appendChild = "appendChild",
3537
3725
  apply = "apply",
3538
3726
  concat = "concat",
3539
- supportsTouch = ("createTouch" in doc),
3727
+ supportsTouch = "createTouch" in doc,
3540
3728
  E = "",
3541
3729
  S = " ",
3542
3730
  Str = String,
@@ -5141,10 +5329,10 @@ var Raphael$1 = (function () {
5141
5329
  });
5142
5330
  break;
5143
5331
  case "gradient":
5144
- (({
5332
+ ({
5145
5333
  circle: 1,
5146
5334
  ellipse: 1
5147
- })[has](o.type) || Str(value).charAt() != "r") && _addGradientFill(node, value, o.paper);
5335
+ }[has](o.type) || Str(value).charAt() != "r") && _addGradientFill(node, value, o.paper);
5148
5336
  break;
5149
5337
  case "opacity":
5150
5338
  if (attrs.gradient && !attrs[has]("stroke-opacity")) {
@@ -7971,8 +8159,8 @@ function polar$2(r, th) {
7971
8159
  th = th * Math.PI / 180;
7972
8160
  return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
7973
8161
  }
7974
- var _bounds = /*#__PURE__*/_classPrivateFieldKey("bounds");
7975
- var _drawingTransform = /*#__PURE__*/_classPrivateFieldKey("drawingTransform");
8162
+ var _bounds = /*#__PURE__*/_classPrivateFieldLooseKey("bounds");
8163
+ var _drawingTransform = /*#__PURE__*/_classPrivateFieldLooseKey("drawingTransform");
7976
8164
  class Graphie$1 {
7977
8165
  constructor(el) {
7978
8166
  this.el = void 0;
@@ -8177,8 +8365,8 @@ class Graphie$1 {
8177
8365
  if (options.range == null) {
8178
8366
  throw new PerseusError("range should be specified in graph init", Errors.Internal);
8179
8367
  }
8180
- _classPrivateFieldBase(this, _bounds)[_bounds] = new GraphBounds(...options.range);
8181
- _classPrivateFieldBase(this, _drawingTransform)[_drawingTransform] = new DrawingTransform(this.raphael, scale, this.bounds());
8368
+ _classPrivateFieldLooseBase(this, _bounds)[_bounds] = new GraphBounds(...options.range);
8369
+ _classPrivateFieldLooseBase(this, _drawingTransform)[_drawingTransform] = new DrawingTransform(this.raphael, scale, this.bounds());
8182
8370
  const [w, h] = this.drawingTransform().canvasDimensions();
8183
8371
  $(this.el).css({
8184
8372
  width: w,
@@ -8413,16 +8601,16 @@ class Graphie$1 {
8413
8601
  }
8414
8602
  }
8415
8603
  drawingTransform() {
8416
- if (_classPrivateFieldBase(this, _drawingTransform)[_drawingTransform] == null) {
8604
+ if (_classPrivateFieldLooseBase(this, _drawingTransform)[_drawingTransform] == null) {
8417
8605
  throw new Error("Can't get drawingTransform of an uninitialized Graphie");
8418
8606
  }
8419
- return _classPrivateFieldBase(this, _drawingTransform)[_drawingTransform];
8607
+ return _classPrivateFieldLooseBase(this, _drawingTransform)[_drawingTransform];
8420
8608
  }
8421
8609
  bounds() {
8422
- if (_classPrivateFieldBase(this, _bounds)[_bounds] == null) {
8610
+ if (_classPrivateFieldLooseBase(this, _bounds)[_bounds] == null) {
8423
8611
  throw new Error("Can't get bounds of an uninitialized Graphie");
8424
8612
  }
8425
- return _classPrivateFieldBase(this, _bounds)[_bounds];
8613
+ return _classPrivateFieldLooseBase(this, _bounds)[_bounds];
8426
8614
  }
8427
8615
  style(attrs, fn) {
8428
8616
  const processed = this.processAttributes(attrs);
@@ -8973,8 +9161,10 @@ const labelDirections = {
8973
9161
  // is equivalent to [[-5, 5], [-10, 10]].
8974
9162
  // - A single magnitude to be used for both x and y. E.g. 10 is equivalent to
8975
9163
  // [[-10, 10], [-10, 10]]
9164
+
8976
9165
  // Overload normalizeRange to specify that it only returns undefined if its
8977
9166
  // argument is undefined.
9167
+
8978
9168
  // normalizeRange converts a RangeSpecifier into a pair of Intervals that give
8979
9169
  // the x and y ranges, respectively
8980
9170
  function normalizeRange(range) {
@@ -9167,7 +9357,7 @@ const GraphUtils = {
9167
9357
  clickBlockList = [],
9168
9358
  blockMouseTriggers = false,
9169
9359
  blockTouchTriggers = false,
9170
- eventCaptureSupported = ("addEventListener" in document),
9360
+ eventCaptureSupported = "addEventListener" in document,
9171
9361
  $document = $(document),
9172
9362
  nextTouchID = 1,
9173
9363
  lastTouchID = 0;
@@ -9862,7 +10052,6 @@ function scaledPolarRad(radius, radians) {
9862
10052
  return [radius * Math.cos(radians), radius * Math.sin(radians) * -1 // SVG flips y axis
9863
10053
  ];
9864
10054
  }
9865
-
9866
10055
  function scaledPolarDeg(radius, degrees) {
9867
10056
  const radians = degrees * Math.PI / 180;
9868
10057
  return scaledPolarRad(radius, radians);
@@ -11680,7 +11869,6 @@ _.extend(GraphUtils.Graphie.prototype, {
11680
11869
  if (hideArrow) {
11681
11870
  return; // Don't draw anything!
11682
11871
  }
11683
-
11684
11872
  const handleVec = vector$3.subtract(handleCoord, rotatePoint.coord);
11685
11873
  const handlePolar = vector$3.polarDegFromCart(handleVec);
11686
11874
  const angle = handlePolar[1];
@@ -12644,7 +12832,6 @@ _.extend(MovableAngle.prototype, {
12644
12832
  // clockwise to the first point
12645
12833
  coords[1] // the vertex parameter is last
12646
12834
  );
12647
-
12648
12835
  const clockwiseAngle = rawAngle + 360 % 360;
12649
12836
  return clockwiseAngle;
12650
12837
  },
@@ -14018,7 +14205,7 @@ class MovablePoint$5 {
14018
14205
  const showTrashTooltip = () => {
14019
14206
  this._showTooltip(container => {
14020
14207
  // eslint-disable-next-line import/no-deprecated
14021
- render( /*#__PURE__*/React.createElement("span", {
14208
+ render(/*#__PURE__*/React.createElement("span", {
14022
14209
  style: {
14023
14210
  fontSize: "2em"
14024
14211
  }
@@ -14817,7 +15004,6 @@ const DEFAULT_PROPS = {
14817
15004
  // likewise
14818
15005
  labelStyle: null // likewise
14819
15006
  };
14820
-
14821
15007
  const DEFAULT_STATE = {
14822
15008
  added: false,
14823
15009
  hasMoved: false,
@@ -17468,7 +17654,7 @@ class TranslationLinter {
17468
17654
  * Preprocess TeX code to convert things that MathJax doesn't know how to handle
17469
17655
  * to things is does.
17470
17656
  */
17471
- var preprocessTex = (texCode => texCode
17657
+ var preprocessTex = texCode => texCode
17472
17658
  // Replace uses of \begin{align}...\end{align} which KaTeX doesn't
17473
17659
  // support (yet) with \begin{aligned}...\end{aligned} which renders
17474
17660
  // the same is supported by KaTeX. It does the same for align*.
@@ -17478,7 +17664,7 @@ var preprocessTex = (texCode => texCode
17478
17664
  // "aligned").
17479
17665
  .replace(/\{align[*]?\}/g, "{aligned}")
17480
17666
  // Replace non-breaking spaces with regular spaces.
17481
- .replace(/[\u00a0]/g, " "));
17667
+ .replace(/[\u00a0]/g, " ");
17482
17668
 
17483
17669
  const {
17484
17670
  interactiveSizes
@@ -17740,29 +17926,29 @@ const isLintable = type => {
17740
17926
  };
17741
17927
 
17742
17928
  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
17929
+ __proto__: null,
17930
+ registerWidget: registerWidget,
17931
+ registerWidgets: registerWidgets,
17932
+ replaceWidget: replaceWidget,
17933
+ replaceDeprecatedWidgets: replaceDeprecatedWidgets,
17934
+ registerEditors: registerEditors,
17935
+ replaceEditor: replaceEditor,
17936
+ replaceDeprecatedEditors: replaceDeprecatedEditors,
17937
+ getWidget: getWidget,
17938
+ getWidgetExport: getWidgetExport,
17939
+ getEditor: getEditor,
17940
+ getTransform: getTransform,
17941
+ getVersion: getVersion,
17942
+ getVersionVector: getVersionVector,
17943
+ getPublicWidgets: getPublicWidgets,
17944
+ isAccessible: isAccessible,
17945
+ getAllWidgetTypes: getAllWidgetTypes,
17946
+ getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
17947
+ traverseChildWidgets: traverseChildWidgets$3,
17948
+ supportsStaticMode: supportsStaticMode,
17949
+ getStaticTransform: getStaticTransform,
17950
+ getTracking: getTracking,
17951
+ isLintable: isLintable
17766
17952
  });
17767
17953
 
17768
17954
  class WidgetContainer extends React.Component {
@@ -17805,7 +17991,6 @@ class WidgetContainer extends React.Component {
17805
17991
  /* eslint-enable react/no-did-mount-set-state */
17806
17992
  }
17807
17993
  }
17808
-
17809
17994
  UNSAFE_componentWillReceiveProps(nextProps) {
17810
17995
  if (this.props.type !== nextProps.type) {
17811
17996
  throw new Error("WidgetContainer can't change widget type; set a different " + "key instead to recreate the container.");
@@ -19259,620 +19444,123 @@ const styles$A = {
19259
19444
  }
19260
19445
  };
19261
19446
 
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);
19447
+ const {
19448
+ captureScratchpadTouchStart: captureScratchpadTouchStart$2
19449
+ } = Util;
19450
+ // [LEMS-2411](Jan 2025) Third: This component has been moved to the NumericInput
19451
+ // folder as we are actively working towards removing the InputNumber widget.
19452
+ // This comment can be removed as part of LEMS-2411.
19453
+
19454
+ /**
19455
+ * The InputWithExamples component is a child component of the NumericInput
19456
+ * and InputNumber components. It is responsible for rendering the UI elements
19457
+ * for the desktop versions of these widgets, and displays a tooltip with
19458
+ * examples of how to input the selected answer forms.
19459
+ */
19460
+ const InputWithExamples = /*#__PURE__*/forwardRef((props, ref) => {
19461
+ // Desctructure the props to set default values
19462
+ const {
19463
+ shouldShowExamples = true,
19464
+ onFocus = () => {},
19465
+ onBlur = () => {},
19466
+ disabled = false,
19467
+ linterContext = PerseusLinter.linterContextDefault,
19468
+ className = ""
19469
+ } = props;
19470
+ const context = React.useContext(PerseusI18nContext);
19471
+ const inputRef = React.useRef(null);
19472
+ const [inputFocused, setInputFocused] = React.useState(false);
19473
+ const id = useId();
19474
+ const ariaId = `aria-for-${id}`;
19475
+ useImperativeHandle(ref, () => ({
19476
+ current: inputRef.current,
19477
+ focus: () => {
19478
+ if (inputRef.current) {
19479
+ inputRef.current.focus();
19480
+ }
19481
+ },
19482
+ blur: () => {
19483
+ if (inputRef.current) {
19484
+ inputRef.current.blur();
19276
19485
  }
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
19486
  }
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,
19487
+ }));
19488
+ const getInputClassName = () => {
19489
+ let inputClassName = ClassNames.INPUT;
19490
+ if (inputFocused) {
19491
+ inputClassName += " " + ClassNames.FOCUSED;
19492
+ }
19493
+ if (className) {
19494
+ inputClassName += " " + className;
19495
+ }
19496
+ return inputClassName;
19497
+ };
19498
+ const handleFocus = () => {
19499
+ onFocus();
19500
+ setInputFocused(true);
19501
+ };
19502
+ const handleBlur = () => {
19503
+ onBlur();
19504
+ setInputFocused(false);
19505
+ };
19506
+ const renderInput = () => {
19507
+ // Generate the provided examples in simple language for screen readers.
19508
+ const examplesAria = props.shouldShowExamples ? `${props.examples[0]}
19509
+ ${props.examples.slice(1).join(", or\n")}`
19510
+ // @ts-expect-error TS2550: Property replaceAll does not exist on type string.
19511
+ .replaceAll("*", "").replaceAll("$", "").replaceAll("\\ \\text{pi}", " pi").replaceAll("\\ ", " and ") : "";
19512
+ const inputProps = {
19513
+ id: id,
19514
+ // If we have examples, we want to provide the aria-describedby attribute
19515
+ "aria-describedby": props.shouldShowExamples ? ariaId : undefined,
19516
+ ref: inputRef,
19517
+ className: getInputClassName(),
19518
+ labelText: props.labelText,
19519
+ value: props.value,
19520
+ onFocus: handleFocus,
19521
+ onBlur: handleBlur,
19323
19522
  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",
19523
+ style: props.style,
19524
+ onChange: props.onChange,
19525
+ onTouchStart: captureScratchpadTouchStart$2,
19337
19526
  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
19527
+ autoComplete: "off",
19528
+ autoCorrect: "off",
19529
+ spellCheck: "false"
19521
19530
  };
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", {
19531
+ return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TextInput, inputProps), /*#__PURE__*/React.createElement("span", {
19532
+ id: ariaId,
19587
19533
  style: {
19588
- position: "relative",
19589
- height: 0,
19590
- display: this.props.show ? "block" : "none"
19534
+ display: "none"
19591
19535
  }
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()
19536
+ }, examplesAria));
19537
+ };
19538
+ const renderTooltipContent = () => {
19539
+ return /*#__PURE__*/React.createElement(TooltipContent, null, /*#__PURE__*/React.createElement("div", {
19540
+ id: id,
19541
+ className: "input-with-examples-tooltip"
19765
19542
  }, /*#__PURE__*/React.createElement(Renderer, {
19766
19543
  content: examplesContent,
19767
- linterContext: PerseusLinter.pushContextStack(this.props.linterContext, "input-with-examples"),
19768
- strings: this.context.strings
19544
+ linterContext: PerseusLinter.pushContextStack(linterContext, "input-with-examples"),
19545
+ strings: context.strings
19769
19546
  })));
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
19547
  };
19873
- };
19548
+ // Display the examples as a string when there are less than or equal to 2 examples.
19549
+ // Otherwise, display the examples as a list.
19550
+ const examplesContent = props.examples.length <= 2 ? props.examples.join(" ") : props.examples.map((example, index) => {
19551
+ return index === 0 && example.startsWith("**") ? `${example}\n` : `- ${example}`;
19552
+ }).join("\n");
19553
+
19554
+ // Display the examples when they are enabled (shouldShowExamples) and the input is focused.
19555
+ const showExamplesTooltip = shouldShowExamples && inputFocused;
19556
+ return /*#__PURE__*/React.createElement(Tooltip, {
19557
+ content: renderTooltipContent(),
19558
+ opened: showExamplesTooltip,
19559
+ placement: "bottom"
19560
+ }, renderInput());
19561
+ });
19874
19562
 
19875
- const formExamples$1 = {
19563
+ const formExamples = {
19876
19564
  integer: function (options, strings) {
19877
19565
  return strings.integerExample;
19878
19566
  },
@@ -19975,7 +19663,7 @@ class InputNumber extends React.Component {
19975
19663
  } = this.context;
19976
19664
  const type = this.props.answerType;
19977
19665
  const forms = inputNumberAnswerTypes[type].forms.split(/\s*,\s*/);
19978
- const examples = _.map(forms, form => formExamples$1[form](this.props, strings));
19666
+ const examples = _.map(forms, form => formExamples[form](this.props, strings));
19979
19667
  return [strings.yourAnswer].concat(examples);
19980
19668
  }
19981
19669
  render() {
@@ -20100,7 +19788,11 @@ const getPromptJSON$r = (renderProps, userInput) => {
20100
19788
  };
20101
19789
  };
20102
19790
 
20103
- const formExamples = {
19791
+ /**
19792
+ * The full list of available strings for the numeric input widget,
19793
+ * based on whether the Content Creator has specified that the answer must be simplified.
19794
+ */
19795
+ const NumericExampleStrings = {
20104
19796
  integer: (form, strings) => strings.integerExample,
20105
19797
  proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
20106
19798
  improper: (form, strings) => form.simplify === "optional" ? strings.improperExample : strings.simplifiedImproperExample,
@@ -20108,38 +19800,200 @@ const formExamples = {
20108
19800
  decimal: (form, strings) => strings.decimalExample,
20109
19801
  pi: (form, strings) => strings.piExample
20110
19802
  };
19803
+
19804
+ /**
19805
+ * Generates the specific set of examples for the current question.
19806
+ * This string is shown as examples to the user in a tooltip.
19807
+ */
19808
+ const generateExamples = (answerForms, strings) => {
19809
+ // If the Content Creator has not specified any answer forms,
19810
+ // we do not need to show any examples.
19811
+ if (answerForms.length === 0) {
19812
+ return [];
19813
+ }
19814
+
19815
+ // Generate a list of the unique answer forms.
19816
+ const uniqueForms = getUniqueAnswerForms(answerForms);
19817
+
19818
+ // Generate the example strings for each unique form.
19819
+ const examples = uniqueForms.map(form => {
19820
+ return NumericExampleStrings[form.name](form, strings);
19821
+ });
19822
+
19823
+ // Add the "Your answer" string to the beginning of the examples list.
19824
+ return [strings.yourAnswer].concat(examples);
19825
+ };
19826
+
19827
+ /**
19828
+ * Determines whether to show examples of how to input the various supported answer forms.
19829
+ * We do not show examples if all forms are accepted or if no forms are accepted.
19830
+ */
19831
+ const shouldShowExamples = answerForms => {
19832
+ // If the Content Creator has not specified any answer forms,
19833
+ // we do not need to show any examples.
19834
+ if (answerForms.length === 0) {
19835
+ return false;
19836
+ }
19837
+
19838
+ // Generate a list of the unique names of the selected answer forms.
19839
+ const answerFormNames = getUniqueAnswerForms(answerForms).map(form => form.name);
19840
+
19841
+ // If all forms are accepted, we do not need to show any examples.
19842
+ const allFormsAccepted = answerFormNames.length >= Object.keys(NumericExampleStrings).length;
19843
+ return !allFormsAccepted;
19844
+ };
19845
+
19846
+ /**
19847
+ * uniqueAnswerForms takes a list of answer forms and returns a list of unique
19848
+ * answer forms. This is useful for ensuring that we don't show duplicate examples
19849
+ * to the user.
19850
+ */
19851
+ const getUniqueAnswerForms = function getUniqueAnswerForms(list) {
19852
+ // We use a Set to keep track of the forms we've already seen.
19853
+ const foundForms = new Set();
19854
+ return list.filter(form => {
19855
+ // If we've already seen this form, skip it.
19856
+ if (foundForms.has(form.name)) {
19857
+ return false;
19858
+ }
19859
+ // Otherwise, add it to the set and return true.
19860
+ foundForms.add(form.name);
19861
+ return true;
19862
+ });
19863
+ };
19864
+
19865
+ /**
19866
+ * Takes a list of lists of answer forms, and returns a list of the forms
19867
+ * in each of these lists in the same order that they're listed in the
19868
+ * `formExamples` forms from above.
19869
+ */
19870
+ const unionAnswerForms = function unionAnswerForms(answerFormsList) {
19871
+ // Pull out all of the forms from the different lists.
19872
+ const allForms = answerFormsList.flat();
19873
+ // Pull out the unique forms using getUniqueAnswerForms.
19874
+ const uniqueForms = getUniqueAnswerForms(allForms);
19875
+ // Sort them by the order they appear in the `formExamples` list.
19876
+ const formExampleKeys = Object.keys(NumericExampleStrings);
19877
+ return uniqueForms.sort((a, b) => {
19878
+ return formExampleKeys.indexOf(a.name) - formExampleKeys.indexOf(b.name);
19879
+ });
19880
+ };
19881
+
19882
+ /**
19883
+ * The NumericInputComponent is a child component of the NumericInput class
19884
+ * component. It is responsible for rendering the UI elements of the Numeric
19885
+ * Input widget.
19886
+ */
19887
+ const NumericInputComponent = /*#__PURE__*/forwardRef((props, ref) => {
19888
+ const context = useContext(PerseusI18nContext);
19889
+ const inputRef = useRef(null);
19890
+ const [isFocused, setIsFocused] = useState(false);
19891
+
19892
+ // Pass the focus and blur methods to the Numeric Input Class component
19893
+ useImperativeHandle(ref, () => ({
19894
+ current: inputRef.current,
19895
+ focus: () => {
19896
+ if (inputRef.current) {
19897
+ var _inputRef$current;
19898
+ (_inputRef$current = inputRef.current) == null || _inputRef$current.focus();
19899
+ setIsFocused(true);
19900
+ }
19901
+ },
19902
+ blur: () => {
19903
+ if (inputRef.current) {
19904
+ var _inputRef$current2;
19905
+ (_inputRef$current2 = inputRef.current) == null || _inputRef$current2.blur();
19906
+ setIsFocused(false);
19907
+ }
19908
+ }
19909
+ }));
19910
+ const handleChange = (newValue, cb) => {
19911
+ props.onChange({
19912
+ currentValue: newValue
19913
+ }, cb);
19914
+ props.trackInteraction();
19915
+ };
19916
+ const handleFocus = () => {
19917
+ props.onFocus([]);
19918
+ setIsFocused(true);
19919
+ };
19920
+ const handleBlur = () => {
19921
+ props.onBlur([]);
19922
+ setIsFocused(false);
19923
+ };
19924
+
19925
+ // Styles for the InputWithExamples
19926
+ const styles = StyleSheet.create({
19927
+ inputWithExamples: {
19928
+ borderRadius: "3px",
19929
+ borderWidth: isFocused ? "2px" : "1px",
19930
+ display: "inline-block",
19931
+ fontFamily: `Symbola, "Times New Roman", serif`,
19932
+ fontSize: "18px",
19933
+ height: "32px",
19934
+ lineHeight: "18px",
19935
+ padding: isFocused ? "4px" : "4px 5px",
19936
+ textAlign: props.rightAlign ? "right" : "left",
19937
+ width: props.size === "small" ? 40 : 80
19938
+ }
19939
+ });
19940
+
19941
+ // (mobile-only) If the custom keypad is enabled, use the SimpleKeypadInput component
19942
+ if (props.apiOptions.customKeypad) {
19943
+ const alignmentClass = props.rightAlign ? "perseus-input-right-align" : undefined;
19944
+ return /*#__PURE__*/React.createElement("div", {
19945
+ className: alignmentClass
19946
+ }, /*#__PURE__*/React.createElement(SimpleKeypadInput, {
19947
+ ref: inputRef,
19948
+ value: props.currentValue,
19949
+ keypadElement: props.keypadElement,
19950
+ onChange: handleChange,
19951
+ onFocus: handleFocus,
19952
+ onBlur: handleBlur
19953
+ }));
19954
+ }
19955
+ // (desktop-only) Otherwise, use the InputWithExamples component
19956
+ return /*#__PURE__*/React.createElement(InputWithExamples, {
19957
+ ref: inputRef,
19958
+ value: props.currentValue,
19959
+ onChange: handleChange,
19960
+ labelText: props.labelText,
19961
+ examples: generateExamples(props.answerForms, context.strings),
19962
+ shouldShowExamples: shouldShowExamples(props.answerForms),
19963
+ onFocus: handleFocus,
19964
+ onBlur: handleBlur,
19965
+ id: props.widgetId,
19966
+ disabled: props.apiOptions.readOnly,
19967
+ style: styles.inputWithExamples
19968
+ });
19969
+ });
19970
+
19971
+ const _excluded$3 = ["answers"];
19972
+
19973
+ /**
19974
+ * The NumericInput widget is a numeric input field that supports a variety of
19975
+ * answer forms, including integers, decimals, fractions, and mixed numbers.
19976
+ *
19977
+ * [Jan 2025] We're currenly migrating from class-based components to functional
19978
+ * components. While we cannot fully migrate this component yet, we can start
19979
+ * by using the functional component for the rendering the UI of the widget.
19980
+ */
20111
19981
  class NumericInput extends React.Component {
20112
19982
  constructor(...args) {
20113
19983
  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
- };
19984
+ this.inputRef = /*#__PURE__*/React.createRef();
20131
19985
  this.focus = () => {
20132
- var _this$inputRef;
20133
- (_this$inputRef = this.inputRef) == null || _this$inputRef.focus();
19986
+ var _this$inputRef$curren;
19987
+ (_this$inputRef$curren = this.inputRef.current) == null || _this$inputRef$curren.focus();
20134
19988
  return true;
20135
19989
  };
20136
19990
  this.focusInputPath = () => {
20137
- var _this$inputRef2;
20138
- (_this$inputRef2 = this.inputRef) == null || _this$inputRef2.focus();
19991
+ var _this$inputRef$curren2;
19992
+ (_this$inputRef$curren2 = this.inputRef.current) == null || _this$inputRef$curren2.focus();
20139
19993
  };
20140
19994
  this.blurInputPath = () => {
20141
- var _this$inputRef3;
20142
- (_this$inputRef3 = this.inputRef) == null || _this$inputRef3.blur();
19995
+ var _this$inputRef$curren3;
19996
+ (_this$inputRef$curren3 = this.inputRef.current) == null || _this$inputRef$curren3.blur();
20143
19997
  };
20144
19998
  this.getInputPaths = () => {
20145
19999
  // The widget itself is an input, so we return a single empty list to
@@ -20147,33 +20001,13 @@ class NumericInput extends React.Component {
20147
20001
  /* c8 ignore next */
20148
20002
  return [[]];
20149
20003
  };
20004
+ /**
20005
+ * Sets the value of the input at the given path.
20006
+ */
20150
20007
  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
20008
  this.props.onChange({
20158
20009
  currentValue: newValue
20159
20010
  }, 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
20011
  };
20178
20012
  }
20179
20013
  static getUserInputFromProps(props) {
@@ -20182,97 +20016,26 @@ class NumericInput extends React.Component {
20182
20016
  };
20183
20017
  }
20184
20018
  /**
20185
- * Generates a string that demonstrates how to input the various supported
20186
- * answer forms.
20019
+ * Returns the value the user has currently input for this widget.
20187
20020
  */
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
20021
  getUserInput() {
20206
20022
  return NumericInput.getUserInputFromProps(this.props);
20207
20023
  }
20024
+
20025
+ /**
20026
+ * Returns the JSON representation of the prompt for this widget.
20027
+ * This is used by the AI to determine the prompt for the widget.
20028
+ */
20208
20029
  getPromptJSON() {
20209
20030
  return getPromptJSON$r(this.props, this.getUserInput());
20210
20031
  }
20211
20032
  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
- });
20033
+ return /*#__PURE__*/React.createElement(NumericInputComponent, _extends({}, this.props, {
20034
+ ref: this.inputRef
20035
+ }));
20266
20036
  }
20267
20037
  }
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;
20038
+ // Transforms the widget options to the props used to render the widget.
20276
20039
  NumericInput.defaultProps = {
20277
20040
  currentValue: "",
20278
20041
  size: "normal",
@@ -20283,52 +20046,23 @@ NumericInput.defaultProps = {
20283
20046
  labelText: "",
20284
20047
  linterContext: linterContextDefault
20285
20048
  };
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
20049
  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
- });
20050
+ // Omit the answers from the widget options since they are
20051
+ // not needed for rendering the widget.
20052
+ const _widgetOptions$answer = _extends({}, widgetOptions, {
20053
+ answerForms: unionAnswerForms(
20054
+ // Filter out the correct answers and map them to the answer forms
20055
+ // so that we can generate the examples for the widget.
20056
+ widgetOptions.answers.filter(answer => answer.status === "correct").map(answer => {
20057
+ return (answer.answerForms || []).map(form => {
20058
+ return {
20059
+ simplify: answer.simplify,
20060
+ name: form
20061
+ };
20062
+ });
20063
+ }))
20064
+ }),
20065
+ rendererProps = _objectWithoutPropertiesLoose(_widgetOptions$answer, _excluded$3);
20332
20066
  return rendererProps;
20333
20067
  };
20334
20068
  var NumericInput$1 = {
@@ -20343,23 +20077,11 @@ var NumericInput$1 = {
20343
20077
  getOneCorrectAnswerFromRubric(rubric) {
20344
20078
  const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
20345
20079
  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'.
20080
+ // Either get the first answer form or default to decimal
20081
+ const format = answer.answerForms && answer.answerForms[0] ? answer.answerForms[0] : "decimal";
20358
20082
  let answerString = KhanMath.toNumericString(answer.value, format);
20359
20083
  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);
20084
+ answerString += " \u00B1 " + KhanMath.toNumericString(answer.maxError, format);
20363
20085
  }
20364
20086
  return answerString;
20365
20087
  });
@@ -21163,7 +20885,7 @@ class HighlightTooltip extends React.PureComponent {
21163
20885
  style: style,
21164
20886
  onMouseEnter: this.props.onMouseEnter,
21165
20887
  onMouseLeave: this.props.onMouseLeave
21166
- }, /*#__PURE__*/React.createElement(Tooltip$1, {
20888
+ }, /*#__PURE__*/React.createElement(Tooltip, {
21167
20889
  content: content,
21168
20890
  opened: true
21169
20891
  }, /*#__PURE__*/React.createElement("div", null)))
@@ -22886,6 +22608,7 @@ const BASE_ICON_SIZE = 10;
22886
22608
  // If you pass in an object you need to specify an SVG path to
22887
22609
  // render along with the width and height of the path (to ensure
22888
22610
  // that it gets scaled correctly).
22611
+
22889
22612
  /**
22890
22613
  * An SVG Icon
22891
22614
  *
@@ -23971,7 +23694,6 @@ const styles$o = StyleSheet.create({
23971
23694
  paddingLeft: phoneMargin
23972
23695
  // paddingRight is handled by responsiveFieldset
23973
23696
  },
23974
-
23975
23697
  responsiveFieldset: {
23976
23698
  paddingRight: phoneMargin,
23977
23699
  minWidth: "auto"
@@ -24377,7 +24099,7 @@ const _choiceTransform = (widgetOptions, strings, problemNum) => {
24377
24099
  // 1) we randomize the order first
24378
24100
  _maybeRandomize(choices)));
24379
24101
  };
24380
- const transform = (widgetOptions, strings, problemNum) => {
24102
+ const transform$1 = (widgetOptions, strings, problemNum) => {
24381
24103
  const choices = _choiceTransform(widgetOptions, strings, problemNum);
24382
24104
  const numCorrect = _.reduce(widgetOptions.choices, function (memo, choice) {
24383
24105
  return choice.correct ? memo + 1 : memo;
@@ -24403,8 +24125,8 @@ var Radio = {
24403
24125
  displayName: "Radio / Multiple choice",
24404
24126
  accessible: true,
24405
24127
  widget: Radio$1,
24406
- transform: transform,
24407
- staticTransform: transform,
24128
+ transform: transform$1,
24129
+ staticTransform: transform$1,
24408
24130
  version: radioLogic.version,
24409
24131
  propUpgrades: radioLogic.widgetOptionsUpgrades,
24410
24132
  isLintable: true
@@ -24975,6 +24697,7 @@ var Definition$1 = {
24975
24697
 
24976
24698
  // There are no required props, but this component can receive props from any
24977
24699
  // deprecated widget
24700
+
24978
24701
  class DeprecatedStandin extends React__default.Component {
24979
24702
  constructor(...args) {
24980
24703
  super(...args);
@@ -25101,20 +24824,20 @@ Dropdown.defaultProps = {
25101
24824
  placeholder: "",
25102
24825
  apiOptions: ApiOptions.defaults
25103
24826
  };
25104
- const optionsTransform = widgetOptions => {
24827
+ function transform(widgetOptions) {
25105
24828
  return {
25106
24829
  placeholder: widgetOptions.placeholder,
25107
24830
  visibleLabel: widgetOptions.visibleLabel,
25108
24831
  ariaLabel: widgetOptions.ariaLabel,
25109
24832
  choices: widgetOptions.choices.map(choice => choice.content)
25110
24833
  };
25111
- };
24834
+ }
25112
24835
  var Dropdown$1 = {
25113
24836
  name: "dropdown",
25114
24837
  displayName: "Drop down",
25115
24838
  accessible: true,
25116
24839
  widget: Dropdown,
25117
- transform: optionsTransform
24840
+ transform
25118
24841
  };
25119
24842
 
25120
24843
  const getPromptJSON$j = renderProps => {
@@ -25178,7 +24901,6 @@ class Explanation extends React.Component {
25178
24901
  // adds space between the right border and any text that follows
25179
24902
  paddingLeft: "2px" // adds space between the left border and the button text
25180
24903
  };
25181
-
25182
24904
  const labelStyle = {
25183
24905
  fontSize: "18px",
25184
24906
  lineHeight: "inherit",
@@ -27502,7 +27224,6 @@ class Interaction extends React.Component {
27502
27224
  }
27503
27225
  });
27504
27226
  }
27505
-
27506
27227
  if (element.type === "parametric") {
27507
27228
  const fn = t => {
27508
27229
  return [
@@ -27555,7 +27276,6 @@ class Interaction extends React.Component {
27555
27276
  }
27556
27277
  });
27557
27278
  }
27558
-
27559
27279
  if (element.type === "label") {
27560
27280
  const coord = [
27561
27281
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
@@ -27900,7 +27620,6 @@ function scaleCommandBy(scaleFactor) {
27900
27620
  command.args[6] * scaleFactor // end y
27901
27621
  ]
27902
27622
  });
27903
-
27904
27623
  default:
27905
27624
  return _extends({}, command, {
27906
27625
  args: command.args.map(c => c * scaleFactor)
@@ -28642,7 +28361,6 @@ const styles$f = StyleSheet.create({
28642
28361
  userSelect: "none",
28643
28362
  borderWidth: 0 // <button> gives us a border by default
28644
28363
  },
28645
-
28646
28364
  // We want to render the element in-place in our fixtures, so allow us to
28647
28365
  // render in the bottom corner in production and in-place in testing.
28648
28366
  hudFixedPosition: {
@@ -28672,7 +28390,6 @@ const styles$f = StyleSheet.create({
28672
28390
  ":hover": {
28673
28391
  backgroundColor: "#a1a5a9" // in between those two grays
28674
28392
  },
28675
-
28676
28393
  ":active": {
28677
28394
  backgroundColor: gray68
28678
28395
  }
@@ -28680,7 +28397,7 @@ const styles$f = StyleSheet.create({
28680
28397
  });
28681
28398
 
28682
28399
  const InfoTipBase = props => {
28683
- return /*#__PURE__*/React.createElement(Tooltip$1, {
28400
+ return /*#__PURE__*/React.createElement(Tooltip, {
28684
28401
  content: /*#__PURE__*/React.createElement(Body, {
28685
28402
  style: styles$e.body
28686
28403
  }, props.children),
@@ -28720,7 +28437,6 @@ class InfoTip extends React.Component {
28720
28437
  });
28721
28438
  /* eslint-enable react/no-did-mount-set-state */
28722
28439
  }
28723
-
28724
28440
  render() {
28725
28441
  if (this.state.didMount) {
28726
28442
  return /*#__PURE__*/React.createElement(InfoTipBase, this.props);
@@ -28952,7 +28668,6 @@ class NumberInput extends React.Component {
28952
28668
  if (val !== Math.floor(val)) {
28953
28669
  return; // bail if not an integer
28954
28670
  }
28955
-
28956
28671
  if (e.key === "ArrowUp") {
28957
28672
  val = val + 1;
28958
28673
  } else if (e.key === "ArrowDown") {
@@ -29086,20 +28801,20 @@ RangeInput.defaultProps = {
29086
28801
  };
29087
28802
 
29088
28803
  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
28804
+ __proto__: null,
28805
+ ButtonGroup: ButtonGroup,
28806
+ Graphie: Graphie,
28807
+ HUD: HUD,
28808
+ Icon: Icon,
28809
+ InfoTip: InfoTip,
28810
+ InlineIcon: InlineIcon,
28811
+ MathInput: MathInput,
28812
+ MultiButtonGroup: MultiButtonGroup,
28813
+ NumberInput: NumberInput,
28814
+ RangeInput: RangeInput,
28815
+ SvgImage: SvgImage,
28816
+ TextInput: TextInput,
28817
+ TextListEditor: TextListEditor
29103
28818
  });
29104
28819
 
29105
28820
  /**
@@ -29228,9 +28943,9 @@ const LockedFunction = props => {
29228
28943
  const {
29229
28944
  color,
29230
28945
  strokeStyle,
29231
- directionalAxis
28946
+ directionalAxis,
28947
+ domain
29232
28948
  } = 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
28949
  const plotProps = {
29235
28950
  color: lockedFigureColors[color],
29236
28951
  style: strokeStyle,
@@ -30642,7 +30357,7 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
30642
30357
  }));
30643
30358
  return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && /*#__PURE__*/React.createElement(Hairlines, {
30644
30359
  point: point
30645
- }), showTooltips ? /*#__PURE__*/React.createElement(Tooltip$1, {
30360
+ }), showTooltips ? /*#__PURE__*/React.createElement(Tooltip, {
30646
30361
  autoUpdate: true,
30647
30362
  backgroundColor: wbColorName,
30648
30363
  content: `(${point[X]}, ${point[Y]})`,
@@ -30775,6 +30490,9 @@ const MovableLine = props => {
30775
30490
  onMoveLine = () => {},
30776
30491
  onMovePoint = () => {}
30777
30492
  } = props;
30493
+ const {
30494
+ snapStep
30495
+ } = useGraphConfig();
30778
30496
 
30779
30497
  // Aria live states for (0) point 1, (1) point 2, and (2) grab handle.
30780
30498
  // When moving an element, set its aria live to "polite" and the others
@@ -30806,7 +30524,8 @@ const MovableLine = props => {
30806
30524
  onMove: p => {
30807
30525
  setAriaLives(["polite", "off", "off"]);
30808
30526
  onMovePoint(0, p);
30809
- }
30527
+ },
30528
+ constrain: getMovableLineKeyboardConstraint([start, end], snapStep, 0)
30810
30529
  });
30811
30530
  const {
30812
30531
  visiblePoint: visiblePoint2,
@@ -30821,7 +30540,8 @@ const MovableLine = props => {
30821
30540
  onMove: p => {
30822
30541
  setAriaLives(["off", "polite", "off"]);
30823
30542
  onMovePoint(1, p);
30824
- }
30543
+ },
30544
+ constrain: getMovableLineKeyboardConstraint([start, end], snapStep, 1)
30825
30545
  });
30826
30546
  const line = /*#__PURE__*/React.createElement(Line$1, {
30827
30547
  ariaLabel: ariaLabels == null ? void 0 : ariaLabels.grabHandleAriaLabel,
@@ -30927,6 +30647,34 @@ const Line$1 = props => {
30927
30647
  color: stroke
30928
30648
  }));
30929
30649
  };
30650
+ const getMovableLineKeyboardConstraint = (line, snapStep, pointIndex) => {
30651
+ // Separate the two points into their own variables, and determine which point is being moved
30652
+ const coordToBeMoved = line[pointIndex];
30653
+ const otherPoint = line[1 - pointIndex];
30654
+
30655
+ // Create a helper function that moves the point and then checks
30656
+ // if it overlaps with the other point in the line after the move.
30657
+ const movePointWithConstraint = moveFunc => {
30658
+ // Move the point
30659
+ let movedCoord = moveFunc(coordToBeMoved);
30660
+ // If the moved point overlaps with the other point in the line,
30661
+ // move the point again.
30662
+ if (vec.dist(movedCoord, otherPoint) === 0) {
30663
+ movedCoord = moveFunc(movedCoord);
30664
+ }
30665
+ return movedCoord;
30666
+ };
30667
+
30668
+ // Check if the new point will overlap the other point.
30669
+ // If it will, we need to snap the point to the left or right an additional
30670
+ // snapStep to avoid overlap.
30671
+ return {
30672
+ up: movePointWithConstraint(coord => vec.add(coord, [0, snapStep[1]])),
30673
+ down: movePointWithConstraint(coord => vec.sub(coord, [0, snapStep[1]])),
30674
+ left: movePointWithConstraint(coord => vec.sub(coord, [snapStep[0], 0])),
30675
+ right: movePointWithConstraint(coord => vec.add(coord, [snapStep[0], 0]))
30676
+ };
30677
+ };
30930
30678
  function trimRange(range, graphDimensionsInPixels) {
30931
30679
  const pixelsToTrim = 4;
30932
30680
  const [xRange, yRange] = range;
@@ -31268,7 +31016,8 @@ function CircleGraph(props) {
31268
31016
  } = props;
31269
31017
  const {
31270
31018
  center,
31271
- radiusPoint
31019
+ radiusPoint,
31020
+ snapStep
31272
31021
  } = graphState;
31273
31022
  const {
31274
31023
  strings,
@@ -31331,7 +31080,8 @@ function CircleGraph(props) {
31331
31080
  onMove: newRadiusPoint => {
31332
31081
  setRadiusPointAriaLive("polite");
31333
31082
  dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
31334
- }
31083
+ },
31084
+ constrain: getCircleKeyboardConstraint(center, radiusPoint, snapStep)
31335
31085
  }), /*#__PURE__*/React.createElement("g", {
31336
31086
  id: radiusId,
31337
31087
  style: a11y.srOnly
@@ -31442,6 +31192,30 @@ function getCircleGraphDescription(state, i18n) {
31442
31192
  const strings = describeCircleGraph(state, i18n);
31443
31193
  return strings.srCircleInteractiveElement;
31444
31194
  }
31195
+ const getCircleKeyboardConstraint = (center, radiusPoint, snapStep) => {
31196
+ // Create a helper function that moves the point and then checks
31197
+ // if it overlaps with the center point after the move.
31198
+ const movePointWithConstraint = moveFunc => {
31199
+ // Move the point
31200
+ let movedCoord = moveFunc(radiusPoint);
31201
+ // If the moved point overlaps with the center point,
31202
+ // move the point again.
31203
+ if (vec.dist(movedCoord, center) === 0) {
31204
+ movedCoord = moveFunc(movedCoord);
31205
+ }
31206
+ return movedCoord;
31207
+ };
31208
+
31209
+ // Check if the new point overlaps the center point.
31210
+ // If it does, we need to snap the point to the left
31211
+ // or right an additional snapStep to avoid the overlap.
31212
+ return {
31213
+ up: movePointWithConstraint(coord => vec.add(coord, [0, snapStep[1]])),
31214
+ down: movePointWithConstraint(coord => vec.sub(coord, [0, snapStep[1]])),
31215
+ left: movePointWithConstraint(coord => vec.sub(coord, [snapStep[0], 0])),
31216
+ right: movePointWithConstraint(coord => vec.add(coord, [snapStep[0], 0]))
31217
+ };
31218
+ };
31445
31219
 
31446
31220
  // Exported for testing
31447
31221
  function describeCircleGraph(state, i18n) {
@@ -31454,6 +31228,7 @@ function describeCircleGraph(state, i18n) {
31454
31228
  radiusPoint
31455
31229
  } = state;
31456
31230
  const radius = getRadius(state);
31231
+ const isRadiusOnRight = radiusPoint[X] >= center[X];
31457
31232
 
31458
31233
  // Aria label strings
31459
31234
  const srCircleGraph = strings.srCircleGraph;
@@ -31461,7 +31236,10 @@ function describeCircleGraph(state, i18n) {
31461
31236
  centerX: srFormatNumber(center[0], locale),
31462
31237
  centerY: srFormatNumber(center[1], locale)
31463
31238
  });
31464
- const srCircleRadiusPoint = strings.srCircleRadiusPoint({
31239
+ const srCircleRadiusPoint = isRadiusOnRight ? strings.srCircleRadiusPointRight({
31240
+ radiusPointX: srFormatNumber(radiusPoint[0], locale),
31241
+ radiusPointY: srFormatNumber(radiusPoint[1], locale)
31242
+ }) : strings.srCircleRadiusPointLeft({
31465
31243
  radiusPointX: srFormatNumber(radiusPoint[0], locale),
31466
31244
  radiusPointY: srFormatNumber(radiusPoint[1], locale)
31467
31245
  });
@@ -31627,6 +31405,12 @@ const LinearSystemGraph = props => {
31627
31405
  locale
31628
31406
  } = usePerseusI18n();
31629
31407
  const id = React.useId();
31408
+ const intersectionId = `${id}-intersection`;
31409
+ const intersectionPoint = geometry.getLineIntersection(lines[0], lines[1]);
31410
+ const intersectionDescription = intersectionPoint ? strings.srLinearSystemIntersection({
31411
+ x: srFormatNumber(intersectionPoint[0], locale),
31412
+ y: srFormatNumber(intersectionPoint[1], locale)
31413
+ }) : strings.srLinearSystemParallel;
31630
31414
  const linesAriaInfo = lines.map((line, i) => {
31631
31415
  return {
31632
31416
  pointsDescriptionId: `${id}-line${i + 1}-points`,
@@ -31643,13 +31427,14 @@ const LinearSystemGraph = props => {
31643
31427
  slopeDescription: getSlopeStringForLine(line, strings)
31644
31428
  };
31645
31429
  });
31430
+ const individualLineDescriptions = linesAriaInfo.map(({
31431
+ pointsDescriptionId,
31432
+ interceptDescriptionId,
31433
+ slopeDescriptionId
31434
+ }) => `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`).join(" ");
31646
31435
  return /*#__PURE__*/React.createElement("g", {
31647
31436
  "aria-label": strings.srLinearSystemGraph,
31648
- "aria-describedby": linesAriaInfo.map(({
31649
- pointsDescriptionId,
31650
- interceptDescriptionId,
31651
- slopeDescriptionId
31652
- }) => `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`).join(" ")
31437
+ "aria-describedby": `${individualLineDescriptions} ${intersectionId}`
31653
31438
  }, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(MovableLine, {
31654
31439
  key: i,
31655
31440
  points: line,
@@ -31666,9 +31451,15 @@ const LinearSystemGraph = props => {
31666
31451
  x: srFormatNumber(line[1][0], locale),
31667
31452
  y: srFormatNumber(line[1][1], locale)
31668
31453
  }),
31669
- grabHandleAriaLabel: `${linesAriaInfo[i].interceptDescription} ${linesAriaInfo[i].slopeDescription}`
31454
+ grabHandleAriaLabel: strings.srLinearSystemGrabHandle({
31455
+ lineNumber: i + 1,
31456
+ point1X: srFormatNumber(line[0][0], locale),
31457
+ point1Y: srFormatNumber(line[0][1], locale),
31458
+ point2X: srFormatNumber(line[1][0], locale),
31459
+ point2Y: srFormatNumber(line[1][1], locale)
31460
+ })
31670
31461
  },
31671
- ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId}`,
31462
+ ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId} ${intersectionId}`,
31672
31463
  onMoveLine: delta => {
31673
31464
  dispatch(actions.linearSystem.moveLine(i, delta));
31674
31465
  },
@@ -31697,7 +31488,10 @@ const LinearSystemGraph = props => {
31697
31488
  key: slopeDescriptionId,
31698
31489
  id: slopeDescriptionId,
31699
31490
  style: a11y.srOnly
31700
- }, slopeDescription))));
31491
+ }, slopeDescription))), /*#__PURE__*/React.createElement("g", {
31492
+ id: intersectionId,
31493
+ style: a11y.srOnly
31494
+ }, intersectionDescription));
31701
31495
  };
31702
31496
 
31703
31497
  // Exported for testing
@@ -32358,7 +32152,8 @@ function QuadraticGraph(props) {
32358
32152
  graphState
32359
32153
  } = props;
32360
32154
  const {
32361
- coords
32155
+ coords,
32156
+ snapStep
32362
32157
  } = graphState;
32363
32158
  const {
32364
32159
  strings,
@@ -32411,6 +32206,7 @@ function QuadraticGraph(props) {
32411
32206
  ariaLabel: `${srQuadraticPoint}${srVertex}`,
32412
32207
  point: coord,
32413
32208
  sequenceNumber: i + 1,
32209
+ constrain: getQuadraticKeyboardConstraint(coords, snapStep, i),
32414
32210
  onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
32415
32211
  });
32416
32212
  }), srQuadraticDirection && /*#__PURE__*/React.createElement("g", {
@@ -32499,6 +32295,60 @@ function describeQuadraticGraph(state, i18n) {
32499
32295
  srQuadraticInteractiveElements
32500
32296
  };
32501
32297
  }
32298
+ const getQuadraticKeyboardConstraint = (coords, snapStep, pointMoved) => {
32299
+ // Make newCoords mutable
32300
+ const newCoords = [coords[0], coords[1], coords[2]];
32301
+
32302
+ // Get the point that is being moved
32303
+ const coordToBeMoved = newCoords[pointMoved];
32304
+
32305
+ // Create a function to validate and adjust the movement of the point
32306
+ const movePointWithConstraint = moveFunc => {
32307
+ // Move the point the desired direction
32308
+ let movedCoord = moveFunc(coordToBeMoved);
32309
+ newCoords[pointMoved] = movedCoord;
32310
+
32311
+ // If these new coordinates are valid, return the moved coord
32312
+ if (areCoordsValid(newCoords)) {
32313
+ return movedCoord;
32314
+ }
32315
+
32316
+ // If the new coordinates are invalid, we need to move the point an additional
32317
+ // snapStep to avoid creating an invalid quadratic graph.
32318
+ movedCoord = moveFunc(movedCoord);
32319
+ newCoords[pointMoved] = movedCoord;
32320
+
32321
+ // If these updated coordinates are valid now, we can return the new coordinates.
32322
+ if (areCoordsValid(newCoords)) {
32323
+ return movedCoord;
32324
+ }
32325
+
32326
+ // Otherwise, if the new coordinates are still invalid, we need to move the point one final snapStep.
32327
+ // This is to support the edge case where all of the points are each exactly one snapStep away
32328
+ // from each other.
32329
+ // Eg. When coords = [[0, 0], [1, 0], [2, 0]], snapStep = [1, 1], and we're moving coords[0] to the right.
32330
+ return moveFunc(movedCoord);
32331
+ };
32332
+ return {
32333
+ up: vec.add(coordToBeMoved, [0, snapStep[1]]),
32334
+ down: vec.sub(coordToBeMoved, [0, snapStep[1]]),
32335
+ // For horizontal movement, we need to ensure that the points are not on the same vertical line.
32336
+ left: movePointWithConstraint(coord => vec.sub(coord, [snapStep[0], 0])),
32337
+ right: movePointWithConstraint(coord => vec.add(coord, [snapStep[0], 0]))
32338
+ };
32339
+ };
32340
+ const areCoordsValid = coords => {
32341
+ const p1 = coords[0];
32342
+ const p2 = coords[1];
32343
+ const p3 = coords[2];
32344
+
32345
+ // If any of the points share the same x-coordinate,
32346
+ // we are unable to calculate the coefficients, and the graph is invalid.
32347
+ if (p1[0] === p2[0] || p2[0] === p3[0] || p1[0] === p3[0]) {
32348
+ return false;
32349
+ }
32350
+ return true;
32351
+ };
32502
32352
 
32503
32353
  function renderRayGraph(state, dispatch, i18n) {
32504
32354
  return {
@@ -32749,7 +32599,8 @@ function SinusoidGraph(props) {
32749
32599
  // Note: The order of the coordinates is important:
32750
32600
  // The coords[0] is the root and the coords[1] is the first peak
32751
32601
  const {
32752
- coords
32602
+ coords,
32603
+ snapStep
32753
32604
  } = graphState;
32754
32605
 
32755
32606
  // The coefficients are used to calculate the sinusoid equation, plot the graph, and to indicate
@@ -32788,11 +32639,37 @@ function SinusoidGraph(props) {
32788
32639
  key: "point-" + i,
32789
32640
  point: coord,
32790
32641
  sequenceNumber: i + 1,
32642
+ constrain: getSinusoidKeyboardConstraint(coords, snapStep, i),
32791
32643
  onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
32792
32644
  })), /*#__PURE__*/React.createElement("g", {
32793
32645
  id: descriptionId
32794
32646
  }, srSinusoidDescription));
32795
32647
  }
32648
+ const getSinusoidKeyboardConstraint = (coords, snapStep, pointIndex) => {
32649
+ // Separate the two points into their own variables, and determine which point is being moved
32650
+ const coordToBeMoved = coords[pointIndex];
32651
+ const otherPoint = coords[1 - pointIndex];
32652
+
32653
+ // Create a helper function that checks if the new point is on the same
32654
+ // vertical line as the other point. If it is, we need to move the point
32655
+ // an additional snapStep.
32656
+ const movePointWithConstraint = moveFunc => {
32657
+ // Move the point
32658
+ let movedCoord = moveFunc(coordToBeMoved);
32659
+ // If the moved point overlaps with the other point in the line,
32660
+ // move the point again.
32661
+ if (movedCoord[X] === otherPoint[X]) {
32662
+ movedCoord = moveFunc(movedCoord);
32663
+ }
32664
+ return movedCoord;
32665
+ };
32666
+ return {
32667
+ up: movePointWithConstraint(coord => vec.add(coord, [0, snapStep[1]])),
32668
+ down: movePointWithConstraint(coord => vec.sub(coord, [0, snapStep[1]])),
32669
+ left: movePointWithConstraint(coord => vec.sub(coord, [snapStep[0], 0])),
32670
+ right: movePointWithConstraint(coord => vec.add(coord, [snapStep[0], 0]))
32671
+ };
32672
+ };
32796
32673
 
32797
32674
  // Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
32798
32675
  const computeSine = function computeSine(x,
@@ -32844,6 +32721,14 @@ function describeSinusoidGraph(state, i18n) {
32844
32721
  const [root, peak] = coords;
32845
32722
  const diffX = Math.abs(peak[X] - root[X]);
32846
32723
  const diffY = Math.abs(peak[Y] - root[Y]);
32724
+ const formattedRoot = {
32725
+ x: srFormatNumber(root[X], locale),
32726
+ y: srFormatNumber(root[Y], locale)
32727
+ };
32728
+ const formattedPeak = {
32729
+ x: srFormatNumber(peak[X], locale),
32730
+ y: srFormatNumber(peak[Y], locale)
32731
+ };
32847
32732
  const srSinusoidGraph = strings.srSinusoidGraph;
32848
32733
  const srSinusoidDescription = strings.srSinusoidDescription({
32849
32734
  minValue: srFormatNumber(root[Y] - diffY, locale),
@@ -32851,14 +32736,8 @@ function describeSinusoidGraph(state, i18n) {
32851
32736
  cycleStart: srFormatNumber(root[X] - 2 * diffX, locale),
32852
32737
  cycleEnd: srFormatNumber(root[X] + 2 * diffX, locale)
32853
32738
  });
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
- });
32739
+ const srSinusoidRootPoint = strings.srSinusoidRootPoint(formattedRoot);
32740
+ const srSinusoidPeakPoint = peak[Y] === root[Y] ? strings.srSinusoidFlatPoint(formattedPeak) : peak[Y] > root[Y] ? strings.srSinusoidMaxPoint(formattedPeak) : strings.srSinusoidMinPoint(formattedPeak);
32862
32741
  const srSinusoidInteractiveElements = strings.srInteractiveElements({
32863
32742
  elements: strings.srSinusoidInteractiveElements({
32864
32743
  point1X: srFormatNumber(root[X], locale),
@@ -32880,16 +32759,17 @@ const {
32880
32759
  calculateAngleInDegrees,
32881
32760
  convertDegreesToRadians
32882
32761
  } = angles;
32883
- const protractorImage = "https://ka-perseus-graphie.s3.amazonaws.com/e9d032f2ab8b95979f674fbfa67056442ba1ff6a.png";
32762
+ const protractorImage = "https://cdn.kastatic.org/images/perseus/protractor.svg";
32884
32763
 
32885
32764
  // The vector from the center of the protractor to the top left corner of the
32886
32765
  // protractor image, in pixels. Used for positioning.
32887
- const centerToTopLeft = [-180, -170];
32766
+ const centerToTopLeft = [-195, -190];
32888
32767
 
32889
32768
  // The vector from the center of the protractor to the center of the rotation
32890
32769
  // handle.
32891
- const centerToRotationHandle = [-176, -15];
32770
+ const centerToRotationHandle = [-201, -15];
32892
32771
  function Protractor() {
32772
+ const staticUrl = getDependencies().staticUrl;
32893
32773
  const {
32894
32774
  range,
32895
32775
  snapStep
@@ -32901,7 +32781,9 @@ function Protractor() {
32901
32781
  const [center, setCenter] = useState(initialCenter);
32902
32782
  const [rotationHandleOffset, setRotationHandleOffset] = useState(centerToRotationHandle);
32903
32783
  const draggableRef = useRef(null);
32904
- useDraggable({
32784
+ const {
32785
+ dragging
32786
+ } = useDraggable({
32905
32787
  gestureTarget: draggableRef,
32906
32788
  onMove: setCenter,
32907
32789
  point: center,
@@ -32925,10 +32807,11 @@ function Protractor() {
32925
32807
  ref: draggableRef,
32926
32808
  transform: `translate(${topLeftPx[X]}, ${topLeftPx[Y]}), rotate(${angle})`,
32927
32809
  style: {
32928
- transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`
32810
+ transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`,
32811
+ cursor: dragging ? "grabbing" : "grab"
32929
32812
  }
32930
32813
  }, /*#__PURE__*/React.createElement("image", {
32931
- href: protractorImage
32814
+ href: staticUrl(protractorImage)
32932
32815
  }), /*#__PURE__*/React.createElement("g", {
32933
32816
  transform: `translate(5, ${-centerToTopLeft[1]})`,
32934
32817
  ref: rotationHandleRef
@@ -34803,7 +34686,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
34803
34686
 
34804
34687
  // If the graph is static, it always displays the correct answer. This is
34805
34688
  // standard behavior for Perseus widgets (e.g. compare the Radio widget).
34806
- if (props.static) {
34689
+ if (props.static && props.correct) {
34807
34690
  return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
34808
34691
  state: initializeGraphState(_extends({}, props, {
34809
34692
  graph: props.correct
@@ -34822,7 +34705,7 @@ const {
34822
34705
  } = angles;
34823
34706
  const {
34824
34707
  getLineEquation,
34825
- getLineIntersection,
34708
+ getLineIntersectionString,
34826
34709
  magnitude,
34827
34710
  vector
34828
34711
  } = geometry;
@@ -35180,7 +35063,7 @@ class InteractiveGraph extends React.Component {
35180
35063
  }
35181
35064
  static getLinearSystemEquationString(props) {
35182
35065
  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]);
35066
+ return "\n" + getLineEquation(coords[0][0], coords[0][1]) + "\n" + getLineEquation(coords[1][0], coords[1][1]) + "\n" + getLineIntersectionString(coords[0], coords[1]);
35184
35067
  }
35185
35068
  static getPointEquationString(props) {
35186
35069
  if (props.graph.type !== "point") {
@@ -35628,7 +35511,6 @@ const styles$a = StyleSheet.create({
35628
35511
  markerCorrect: {
35629
35512
  background: "#00880b" // WB green darkened by 18%
35630
35513
  },
35631
-
35632
35514
  markerIncorrect: {
35633
35515
  background: color.offBlack64
35634
35516
  }
@@ -36021,7 +35903,6 @@ class LabelImage extends React.Component {
36021
35903
  const adjustPillDistance = {
36022
35904
  [`margin${markerPosition.charAt(0).toUpperCase() + markerPosition.slice(1)}`]: 10 // move pill further from marker
36023
35905
  };
36024
-
36025
35906
  const answerChoicesActive = index === activeMarkerIndex;
36026
35907
  const showAnswerChoice = marker.selected && !answerChoicesActive && !this.state.hideAnswers;
36027
35908
  return /*#__PURE__*/React.createElement(View, {
@@ -36466,7 +36347,6 @@ class Draggable extends React.Component {
36466
36347
  if (this.props.width) {
36467
36348
  style.width = this.props.width + 1; // Fix for non-integer widths
36468
36349
  }
36469
-
36470
36350
  if (this.props.height) {
36471
36351
  style.height = this.props.height;
36472
36352
  }
@@ -36808,8 +36688,7 @@ class Sortable extends React.Component {
36808
36688
  // @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
36809
36689
  margin = "0 0 " + this.props.margin + "px 0"; // bottom
36810
36690
  }
36811
-
36812
- cards.push( /*#__PURE__*/React.createElement(Draggable, {
36691
+ cards.push(/*#__PURE__*/React.createElement(Draggable, {
36813
36692
  content: item.option,
36814
36693
  key: item.key,
36815
36694
  state: item.state
@@ -36862,7 +36741,7 @@ class Sortable extends React.Component {
36862
36741
  this, item.key)
36863
36742
  }));
36864
36743
  if (item.state === ItemState.DRAGGING || item.state === ItemState.ANIMATING) {
36865
- cards.push( /*#__PURE__*/React.createElement(Placeholder, {
36744
+ cards.push(/*#__PURE__*/React.createElement(Placeholder, {
36866
36745
  key: "placeholder_" + item.key,
36867
36746
  ref: "placeholder_" + item.key,
36868
36747
  width: item.width,
@@ -38652,7 +38531,6 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
38652
38531
  if (!_.isFinite(props.tickStep) || props.tickStep <= 0) {
38653
38532
  return []; // this has screwed me for the last time!
38654
38533
  }
38655
-
38656
38534
  const results = [];
38657
38535
 
38658
38536
  // For convenience, extract some props into separate variables
@@ -38723,6 +38601,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
38723
38601
 
38724
38602
  // TODO: most widgets use some like Widget<Something, PerseusNumberLineWidgetOptions>
38725
38603
  // should this one?
38604
+
38726
38605
  class NumberLine extends React.Component {
38727
38606
  constructor(..._args) {
38728
38607
  super(..._args);
@@ -39088,7 +38967,6 @@ const numberLineTransform = editorProps => {
39088
38967
  } else {
39089
38968
  numDivisions = undefined; // send to getDefaultProps()
39090
38969
  }
39091
-
39092
38970
  _.extend(props, {
39093
38971
  numLinePosition: numLinePosition,
39094
38972
  numDivisions: numDivisions,
@@ -39113,7 +38991,6 @@ const staticTransform$1 = editorProps => {
39113
38991
  } else {
39114
38992
  numDivisions = undefined; // send to getDefaultProps()
39115
38993
  }
39116
-
39117
38994
  _.extend(props, {
39118
38995
  numLinePosition: numLinePosition,
39119
38996
  numDivisions: numDivisions,
@@ -40102,7 +39979,6 @@ class Plotter extends React.Component {
40102
39979
  const initialY = isMobile ? c.scaleY : 0;
40103
39980
  for (let y = initialY; y <= c.dimY; y += c.scaleY) {
40104
39981
  graphie.label([0, y], KhanMath.roundToApprox(y, 2), "left", /* isTeX */true /* for the \approx symbol */);
40105
-
40106
39982
  graphie.style({
40107
39983
  stroke: isMobile ? "#e9ebec" : "#000",
40108
39984
  strokeWidth: 1,
@@ -41312,9 +41188,51 @@ const init = function init() {
41312
41188
  replaceDeprecatedWidgets();
41313
41189
  };
41314
41190
 
41191
+ /**
41192
+ * Adds the given perseus library version information to the __perseus_debug__
41193
+ * object and ensures that the object is attached to `globalThis` (`window` in
41194
+ * browser environments).
41195
+ *
41196
+ * This allows each library to provide runtime version information to assist in
41197
+ * debugging in production environments.
41198
+ */
41199
+ const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
41200
+ // If the library version is the default value, then we don't want to
41201
+ // prefix it with a "v" to indicate that it is a version number.
41202
+ let prefix = "v";
41203
+ if (libraryVersion === "__lib_version__") {
41204
+ prefix = "";
41205
+ }
41206
+ const formattedVersion = `${prefix}${libraryVersion}`;
41207
+ if (typeof globalThis !== "undefined") {
41208
+ var _globalThis$__perseus;
41209
+ globalThis.__perseus_debug__ = (_globalThis$__perseus = globalThis.__perseus_debug__) != null ? _globalThis$__perseus : {};
41210
+ const existingVersionEntry = globalThis.__perseus_debug__[libraryName];
41211
+ if (existingVersionEntry) {
41212
+ // If we already have an entry and it doesn't match the registered
41213
+ // version, we morph the entry into an array and log a warning.
41214
+ if (existingVersionEntry !== formattedVersion) {
41215
+ // Existing entry might be an array already (oops, at least 2
41216
+ // versions of the library already loaded!).
41217
+ const allVersions = Array.isArray(existingVersionEntry) ? existingVersionEntry : [existingVersionEntry];
41218
+ allVersions.push(formattedVersion);
41219
+ globalThis.__perseus_debug__[libraryName] = allVersions;
41220
+
41221
+ // eslint-disable-next-line no-console
41222
+ console.warn(`Multiple versions of ${libraryName} loaded on this page: ${allVersions.sort().join(", ")}`);
41223
+ }
41224
+ } else {
41225
+ globalThis.__perseus_debug__[libraryName] = formattedVersion;
41226
+ }
41227
+ } else {
41228
+ // eslint-disable-next-line no-console
41229
+ console.warn(`globalThis not found found (${formattedVersion})`);
41230
+ }
41231
+ };
41232
+
41315
41233
  // This file is processed by a Rollup plugin (replace) to inject the production
41316
41234
  const libName = "@khanacademy/perseus";
41317
- const libVersion = "54.1.0";
41235
+ const libVersion = "55.0.1";
41318
41236
  addLibraryVersionToPerseusDebug(libName, libVersion);
41319
41237
 
41320
41238
  const apiVersion = {
@@ -41708,7 +41626,6 @@ class HintsRenderer extends React.Component {
41708
41626
  ReactDOM__default.findDOMNode(this.refs["hintRenderer" + pos]).focus(); // eslint-disable-line react/no-string-refs
41709
41627
  }
41710
41628
  }
41711
-
41712
41629
  render() {
41713
41630
  const apiOptions = this.getApiOptions();
41714
41631
  const hintsVisible = this._hintsVisible();