@khanacademy/perseus 54.0.0 → 55.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +18 -0
- package/dist/components/number-input.d.ts +9 -10
- package/dist/components/range-input.d.ts +5 -6
- package/dist/components/simple-keypad-input.d.ts +5 -0
- package/dist/components/stub-tag-editor.d.ts +3 -4
- package/dist/components/text-list-editor.d.ts +3 -4
- package/dist/es/index.css +16 -6
- package/dist/es/index.js +919 -1066
- package/dist/es/index.js.map +1 -1
- package/dist/es/strings.js +40 -10
- package/dist/es/strings.js.map +1 -1
- package/dist/index.css +16 -6
- package/dist/index.js +1055 -1170
- package/dist/index.js.map +1 -1
- package/dist/mixins/changeable.d.ts +1 -2
- package/dist/perseus-api.d.ts +1 -56
- package/dist/shared-utils/add-library-version-to-perseus-debug.d.ts +9 -0
- package/dist/strings.d.ts +34 -4
- package/dist/strings.js +142 -97
- package/dist/strings.js.map +1 -1
- package/dist/types.d.ts +3 -1
- package/dist/util.d.ts +0 -5
- package/dist/widget-ai-utils/numeric-input/prompt-utils.d.ts +1 -1
- package/dist/widgets/categorizer/categorizer.d.ts +1 -1
- package/dist/widgets/dropdown/dropdown.d.ts +5 -4
- package/dist/widgets/expression/expression.d.ts +0 -2
- package/dist/widgets/interactive-graph.d.ts +4 -2
- package/dist/widgets/interactive-graphs/graphs/circle.d.ts +7 -0
- package/dist/widgets/interactive-graphs/graphs/components/movable-line.d.ts +6 -0
- package/dist/widgets/interactive-graphs/graphs/quadratic.d.ts +8 -0
- package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +7 -0
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +1 -1
- package/dist/widgets/interactive-graphs/stateful-mafs-graph.d.ts +6 -1
- package/dist/widgets/numeric-input/index.d.ts +1 -1
- package/dist/widgets/numeric-input/input-with-examples.d.ts +8 -0
- package/dist/widgets/numeric-input/numeric-input.class.d.ts +66 -0
- package/dist/widgets/numeric-input/numeric-input.d.ts +123 -77
- package/dist/widgets/numeric-input/utils.d.ts +25 -0
- package/dist/widgets/radio/radio.d.ts +1 -1
- package/dist/widgets/sorter/sorter.d.ts +1 -1
- package/package.json +120 -124
- package/dist/components/input-with-examples.d.ts +0 -51
- 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, GrapherUtil as GrapherUtil$1, lockedFigureColors, lockedFigureFillStyles, approximateEqual, getMatrixSize, measurerLogic,
|
|
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
|
|
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,
|
|
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
|
-
|
|
260
|
+
srCircleRadiusPointRight: ({
|
|
266
261
|
radiusPointX,
|
|
267
262
|
radiusPointY
|
|
268
|
-
}) => `
|
|
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
|
|
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
|
|
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
|
-
|
|
499
|
+
srSinusoidMaxPoint: ({
|
|
500
|
+
x,
|
|
501
|
+
y
|
|
502
|
+
}) => `Maximum point at ${x} comma ${y}.`,
|
|
503
|
+
srSinusoidMinPoint: ({
|
|
504
|
+
x,
|
|
505
|
+
y
|
|
506
|
+
}) => `Minimum point at ${x} comma ${y}.`,
|
|
507
|
+
srSinusoidFlatPoint: ({
|
|
489
508
|
x,
|
|
490
509
|
y
|
|
491
|
-
}) => `
|
|
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
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
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
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
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
|
|
1713
|
+
}, this.state.showErrorTooltip && ERROR_TITLE + " " + ERROR_MESSAGE), /*#__PURE__*/React.createElement(Tooltip, {
|
|
1695
1714
|
forceAnchorFocusivity: false,
|
|
1696
1715
|
opened: this.state.showErrorTooltip,
|
|
1697
1716
|
title: ERROR_TITLE,
|
|
@@ -1847,6 +1866,182 @@ var Expression$1 = {
|
|
|
1847
1866
|
}
|
|
1848
1867
|
};
|
|
1849
1868
|
|
|
1869
|
+
const _excluded$4 = ["keypadElement", "onFocus", "value"];
|
|
1870
|
+
class SimpleKeypadInput extends React.Component {
|
|
1871
|
+
constructor(...args) {
|
|
1872
|
+
super(...args);
|
|
1873
|
+
this._isMounted = false;
|
|
1874
|
+
this.inputRef = /*#__PURE__*/React.createRef();
|
|
1875
|
+
}
|
|
1876
|
+
componentDidMount() {
|
|
1877
|
+
// TODO(scottgrant): This is a hack to remove the deprecated call to
|
|
1878
|
+
// this.isMounted() but is still considered an anti-pattern.
|
|
1879
|
+
this._isMounted = true;
|
|
1880
|
+
}
|
|
1881
|
+
componentWillUnmount() {
|
|
1882
|
+
this._isMounted = false;
|
|
1883
|
+
}
|
|
1884
|
+
focus() {
|
|
1885
|
+
var _this$inputRef$curren;
|
|
1886
|
+
// The inputRef is a ref to a MathInput, which
|
|
1887
|
+
// also controls the keypad state during focus events.
|
|
1888
|
+
(_this$inputRef$curren = this.inputRef.current) == null || _this$inputRef$curren.focus(this.context.setKeypadActive);
|
|
1889
|
+
}
|
|
1890
|
+
blur() {
|
|
1891
|
+
var _this$inputRef$curren2;
|
|
1892
|
+
(_this$inputRef$curren2 = this.inputRef.current) == null || _this$inputRef$curren2.blur();
|
|
1893
|
+
}
|
|
1894
|
+
getValue() {
|
|
1895
|
+
return this.props.value;
|
|
1896
|
+
}
|
|
1897
|
+
render() {
|
|
1898
|
+
const _this = this;
|
|
1899
|
+
// Intercept the `onFocus` prop, as we need to configure the keypad
|
|
1900
|
+
// before continuing with the default focus logic for Perseus inputs.
|
|
1901
|
+
// Intercept the `value` prop so as to map `null` to the empty string,
|
|
1902
|
+
// as the `KeypadInput` does not support `null` values.
|
|
1903
|
+
const _this$props = _this.props,
|
|
1904
|
+
{
|
|
1905
|
+
keypadElement,
|
|
1906
|
+
onFocus,
|
|
1907
|
+
value
|
|
1908
|
+
} = _this$props,
|
|
1909
|
+
rest = _objectWithoutPropertiesLoose(_this$props, _excluded$4);
|
|
1910
|
+
return (
|
|
1911
|
+
/*#__PURE__*/
|
|
1912
|
+
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
1913
|
+
React.createElement(KeypadInput, _extends({
|
|
1914
|
+
ref: this.inputRef,
|
|
1915
|
+
keypadElement: keypadElement,
|
|
1916
|
+
onFocus: () => {
|
|
1917
|
+
if (keypadElement) {
|
|
1918
|
+
keypadElement.configure({
|
|
1919
|
+
keypadType: KeypadType.FRACTION
|
|
1920
|
+
}, () => {
|
|
1921
|
+
if (_this._isMounted) {
|
|
1922
|
+
onFocus == null || onFocus();
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
1925
|
+
} else {
|
|
1926
|
+
onFocus == null || onFocus();
|
|
1927
|
+
}
|
|
1928
|
+
},
|
|
1929
|
+
value: value == null ? "" : "" + value
|
|
1930
|
+
}, rest))
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
// @ts-expect-error - TS2339 - Property 'propTypes' does not exist on type 'typeof SimpleKeypadInput'.
|
|
1936
|
+
SimpleKeypadInput.contextType = KeypadContext;
|
|
1937
|
+
SimpleKeypadInput.propTypes = {
|
|
1938
|
+
keypadElement: keypadElementPropType,
|
|
1939
|
+
onFocus: PropTypes.func,
|
|
1940
|
+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
1941
|
+
};
|
|
1942
|
+
|
|
1943
|
+
const getPromptJSON$s = (renderProps, userInput) => {
|
|
1944
|
+
return {
|
|
1945
|
+
type: "input-number",
|
|
1946
|
+
options: {
|
|
1947
|
+
simplify: renderProps.simplify,
|
|
1948
|
+
answerType: renderProps.answerType
|
|
1949
|
+
},
|
|
1950
|
+
userInput: {
|
|
1951
|
+
value: userInput.currentValue
|
|
1952
|
+
}
|
|
1953
|
+
};
|
|
1954
|
+
};
|
|
1955
|
+
|
|
1956
|
+
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
1957
|
+
let lastId = 0;
|
|
1958
|
+
function uniqueIdForInput(prefix = "input-") {
|
|
1959
|
+
lastId++;
|
|
1960
|
+
return `${prefix}${lastId}`;
|
|
1961
|
+
}
|
|
1962
|
+
class TextInput extends React.Component {
|
|
1963
|
+
constructor(props) {
|
|
1964
|
+
super(props);
|
|
1965
|
+
this.inputRef = /*#__PURE__*/React.createRef();
|
|
1966
|
+
this.id = void 0;
|
|
1967
|
+
this._getInput = () => {
|
|
1968
|
+
if (!this.inputRef.current) {
|
|
1969
|
+
throw new PerseusError("Input ref accessed before set", Errors.Internal);
|
|
1970
|
+
}
|
|
1971
|
+
return this.inputRef.current;
|
|
1972
|
+
};
|
|
1973
|
+
this.focus = () => {
|
|
1974
|
+
this._getInput().focus();
|
|
1975
|
+
};
|
|
1976
|
+
this.blur = () => {
|
|
1977
|
+
this._getInput().blur();
|
|
1978
|
+
};
|
|
1979
|
+
this.getValue = () => {
|
|
1980
|
+
var _this$inputRef$curren;
|
|
1981
|
+
return (_this$inputRef$curren = this.inputRef.current) == null ? void 0 : _this$inputRef$curren.value;
|
|
1982
|
+
};
|
|
1983
|
+
this.getStringValue = () => {
|
|
1984
|
+
var _this$inputRef$curren2;
|
|
1985
|
+
return (_this$inputRef$curren2 = this.inputRef.current) == null ? void 0 : _this$inputRef$curren2.value.toString();
|
|
1986
|
+
};
|
|
1987
|
+
this.setSelectionRange = (selectionStart, selectionEnd) => {
|
|
1988
|
+
this._getInput().setSelectionRange(selectionStart, selectionEnd);
|
|
1989
|
+
};
|
|
1990
|
+
this.getSelectionStart = () => {
|
|
1991
|
+
return this._getInput().selectionStart;
|
|
1992
|
+
};
|
|
1993
|
+
this.getSelectionEnd = () => {
|
|
1994
|
+
return this._getInput().selectionEnd;
|
|
1995
|
+
};
|
|
1996
|
+
if (props.id) {
|
|
1997
|
+
this.id = props.id;
|
|
1998
|
+
} else {
|
|
1999
|
+
this.id = uniqueIdForInput();
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
render() {
|
|
2003
|
+
const {
|
|
2004
|
+
labelText,
|
|
2005
|
+
value,
|
|
2006
|
+
onFocus,
|
|
2007
|
+
onBlur,
|
|
2008
|
+
disabled,
|
|
2009
|
+
placeholder,
|
|
2010
|
+
onKeyDown,
|
|
2011
|
+
style
|
|
2012
|
+
} = this.props;
|
|
2013
|
+
const formattedValue = value === null ? "" : value.toString();
|
|
2014
|
+
|
|
2015
|
+
// Some of our content was saved with empty strings as the label text,
|
|
2016
|
+
// and we don't want to render an empty aria-label attribute.
|
|
2017
|
+
const ariaLabel = labelText || undefined;
|
|
2018
|
+
return /*#__PURE__*/React.createElement(TextField, {
|
|
2019
|
+
ref: this.inputRef,
|
|
2020
|
+
style: style,
|
|
2021
|
+
disabled: disabled,
|
|
2022
|
+
id: this.id,
|
|
2023
|
+
value: formattedValue,
|
|
2024
|
+
type: "text",
|
|
2025
|
+
"aria-label": ariaLabel,
|
|
2026
|
+
"aria-describedby": this.props["aria-describedby"],
|
|
2027
|
+
onChange: value => this.props.onChange(value),
|
|
2028
|
+
placeholder: placeholder,
|
|
2029
|
+
onFocus: onFocus,
|
|
2030
|
+
onBlur: onBlur,
|
|
2031
|
+
onKeyDown: onKeyDown
|
|
2032
|
+
// @ts-expect-error - TS2322 - Type '{ style: StyleType; disabled: boolean | undefined; id: string; value: string; type: "text"; "aria-label": string | undefined; onChange: (value: string) => void; placeholder: string | undefined; ... 5 more ...; autoComplete: string; }' is not assignable to type 'IntrinsicAttributes & ExportProps & RefAttributes<HTMLInputElement>'.
|
|
2033
|
+
,
|
|
2034
|
+
autoCorrect: "off",
|
|
2035
|
+
autoCapitalize: "off",
|
|
2036
|
+
autoComplete: "off"
|
|
2037
|
+
});
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
TextInput.defaultProps = {
|
|
2041
|
+
value: "",
|
|
2042
|
+
disabled: false
|
|
2043
|
+
};
|
|
2044
|
+
|
|
1850
2045
|
/**
|
|
1851
2046
|
* A React context for keeping track of whether an component is fully
|
|
1852
2047
|
* loaded. This is used to know when SvgImage and (Zoomable)TeX are finished
|
|
@@ -1939,54 +2134,6 @@ const rWidgetRule = /^\[\[\u2603 (([a-z-]+) ([0-9]+))\]\]/;
|
|
|
1939
2134
|
const rTypeFromWidgetId = /^([a-z-]+) ([0-9]+)$/;
|
|
1940
2135
|
const rWidgetParts = new RegExp(rWidgetRule.source + "$");
|
|
1941
2136
|
const snowman = "\u2603";
|
|
1942
|
-
const seededRNG$1 = function seededRNG(seed) {
|
|
1943
|
-
let randomSeed = seed;
|
|
1944
|
-
return function () {
|
|
1945
|
-
// Robert Jenkins' 32 bit integer hash function.
|
|
1946
|
-
let seed = randomSeed;
|
|
1947
|
-
seed = seed + 0x7ed55d16 + (seed << 12) & 0xffffffff;
|
|
1948
|
-
seed = (seed ^ 0xc761c23c ^ seed >>> 19) & 0xffffffff;
|
|
1949
|
-
seed = seed + 0x165667b1 + (seed << 5) & 0xffffffff;
|
|
1950
|
-
seed = (seed + 0xd3a2646c ^ seed << 9) & 0xffffffff;
|
|
1951
|
-
seed = seed + 0xfd7046c5 + (seed << 3) & 0xffffffff;
|
|
1952
|
-
seed = (seed ^ 0xb55a4f09 ^ seed >>> 16) & 0xffffffff;
|
|
1953
|
-
return (randomSeed = seed & 0xfffffff) / 0x10000000;
|
|
1954
|
-
};
|
|
1955
|
-
};
|
|
1956
|
-
|
|
1957
|
-
// Shuffle an array using a given random seed or function.
|
|
1958
|
-
// If `ensurePermuted` is true, the input and ouput are guaranteed to be
|
|
1959
|
-
// distinct permutations.
|
|
1960
|
-
function shuffle$3(array, randomSeed, ensurePermuted = false) {
|
|
1961
|
-
// Always return a copy of the input array
|
|
1962
|
-
const shuffled = _.clone(array);
|
|
1963
|
-
|
|
1964
|
-
// Handle edge cases (input array is empty or uniform)
|
|
1965
|
-
if (!shuffled.length || _.all(shuffled, function (value) {
|
|
1966
|
-
return _.isEqual(value, shuffled[0]);
|
|
1967
|
-
})) {
|
|
1968
|
-
return shuffled;
|
|
1969
|
-
}
|
|
1970
|
-
let random;
|
|
1971
|
-
if (typeof randomSeed === "function") {
|
|
1972
|
-
random = randomSeed;
|
|
1973
|
-
} else {
|
|
1974
|
-
random = seededRNG$1(randomSeed);
|
|
1975
|
-
}
|
|
1976
|
-
do {
|
|
1977
|
-
// Fischer-Yates shuffle
|
|
1978
|
-
for (let top = shuffled.length; top > 0; top--) {
|
|
1979
|
-
const newEnd = Math.floor(random() * top);
|
|
1980
|
-
const temp = shuffled[newEnd];
|
|
1981
|
-
|
|
1982
|
-
// @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
|
|
1983
|
-
shuffled[newEnd] = shuffled[top - 1];
|
|
1984
|
-
// @ts-expect-error - TS2542 - Index signature in type 'readonly T[]' only permits reading.
|
|
1985
|
-
shuffled[top - 1] = temp;
|
|
1986
|
-
}
|
|
1987
|
-
} while (ensurePermuted && _.isEqual(array, shuffled));
|
|
1988
|
-
return shuffled;
|
|
1989
|
-
}
|
|
1990
2137
|
|
|
1991
2138
|
/**
|
|
1992
2139
|
* TODO(somewhatabstract, FEI-3463):
|
|
@@ -2419,7 +2566,6 @@ const textarea = {
|
|
|
2419
2566
|
* to translate it but don't need the extra dollar signs.
|
|
2420
2567
|
*/
|
|
2421
2568
|
const unescapeMathMode$1 = label => label.startsWith("$") && label.endsWith("$") ? label.slice(1, -1) : label;
|
|
2422
|
-
const random$1 = seededRNG$1(new Date().getTime() & 0xffffffff);
|
|
2423
2569
|
const Util = {
|
|
2424
2570
|
inputPathsEqual,
|
|
2425
2571
|
nestedMap: nestedMap$2,
|
|
@@ -2427,8 +2573,6 @@ const Util = {
|
|
|
2427
2573
|
rTypeFromWidgetId,
|
|
2428
2574
|
rWidgetParts,
|
|
2429
2575
|
snowman,
|
|
2430
|
-
seededRNG: seededRNG$1,
|
|
2431
|
-
shuffle: shuffle$3,
|
|
2432
2576
|
split,
|
|
2433
2577
|
firstNumericalParse: firstNumericalParse$1,
|
|
2434
2578
|
stringArrayOfSize: stringArrayOfSize$1,
|
|
@@ -2456,16 +2600,17 @@ const Util = {
|
|
|
2456
2600
|
getSvgUrl: getSvgUrl,
|
|
2457
2601
|
getDataUrl: getDataUrl,
|
|
2458
2602
|
textarea,
|
|
2459
|
-
unescapeMathMode: unescapeMathMode$1
|
|
2460
|
-
random: random$1
|
|
2603
|
+
unescapeMathMode: unescapeMathMode$1
|
|
2461
2604
|
};
|
|
2462
2605
|
|
|
2463
2606
|
// TODO(LP-11481): Change the primary API we use for logging in Perseus to use
|
|
2464
2607
|
// getDependencies() internally... simplify usage to just using a "singleton"
|
|
2465
2608
|
// which get's the injected logger.
|
|
2609
|
+
|
|
2466
2610
|
/**
|
|
2467
2611
|
* Provides logging infrastructure for Perseus
|
|
2468
2612
|
*/
|
|
2613
|
+
|
|
2469
2614
|
const Log = {
|
|
2470
2615
|
// Logs a message
|
|
2471
2616
|
log: (message, extra) => {
|
|
@@ -3139,40 +3284,40 @@ const articleMaxWidthInPx = 688;
|
|
|
3139
3284
|
const articleMaxWidthTableInPx = 512;
|
|
3140
3285
|
|
|
3141
3286
|
var constants = /*#__PURE__*/Object.freeze({
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3287
|
+
__proto__: null,
|
|
3288
|
+
baseFontFamily: baseFontFamily,
|
|
3289
|
+
boldFontFamily: boldFontFamily,
|
|
3290
|
+
kaGreen: kaGreen,
|
|
3291
|
+
white: white,
|
|
3292
|
+
gray97: gray97,
|
|
3293
|
+
gray85: gray85,
|
|
3294
|
+
gray76: gray76,
|
|
3295
|
+
gray68: gray68,
|
|
3296
|
+
gray17: gray17,
|
|
3297
|
+
pureSmMin: pureSmMin$1,
|
|
3298
|
+
pureMdMin: pureMdMin$1,
|
|
3299
|
+
pureLgMin: pureLgMin$1,
|
|
3300
|
+
pureXlMin: pureXlMin$1,
|
|
3301
|
+
pureXsMax: pureXsMax$1,
|
|
3302
|
+
pureSmMax: pureSmMax$1,
|
|
3303
|
+
pureMdMax: pureMdMax$1,
|
|
3304
|
+
pureLgMax: pureLgMax$1,
|
|
3305
|
+
tableBackgroundAccent: tableBackgroundAccent,
|
|
3306
|
+
phoneMargin: phoneMargin,
|
|
3307
|
+
negativePhoneMargin: negativePhoneMargin,
|
|
3308
|
+
hintBorderWidth: hintBorderWidth,
|
|
3309
|
+
baseUnitPx: baseUnitPx,
|
|
3310
|
+
interactiveSizes: interactiveSizes$1,
|
|
3311
|
+
circleSize: circleSize$1,
|
|
3312
|
+
radioMarginWidth: radioMarginWidth$1,
|
|
3313
|
+
warningColor: warningColor,
|
|
3314
|
+
warningColorHover: warningColorHover,
|
|
3315
|
+
warningColorActive: warningColorActive,
|
|
3316
|
+
publishBlockingErrorColor: publishBlockingErrorColor,
|
|
3317
|
+
radioBorderColor: radioBorderColor$1,
|
|
3318
|
+
checkedColor: checkedColor$1,
|
|
3319
|
+
articleMaxWidthInPx: articleMaxWidthInPx,
|
|
3320
|
+
articleMaxWidthTableInPx: articleMaxWidthTableInPx
|
|
3176
3321
|
});
|
|
3177
3322
|
|
|
3178
3323
|
/**
|
|
@@ -3522,16 +3667,6 @@ const InteractiveUtil = {
|
|
|
3522
3667
|
}
|
|
3523
3668
|
};
|
|
3524
3669
|
|
|
3525
|
-
function _classPrivateFieldBase(e, t) {
|
|
3526
|
-
if (!{}.hasOwnProperty.call(e, t)) throw new TypeError("attempted to use private field on non-instance");
|
|
3527
|
-
return e;
|
|
3528
|
-
}
|
|
3529
|
-
|
|
3530
|
-
var id = 0;
|
|
3531
|
-
function _classPrivateFieldKey(e) {
|
|
3532
|
-
return "__private_" + id++ + "_" + e;
|
|
3533
|
-
}
|
|
3534
|
-
|
|
3535
3670
|
/*!
|
|
3536
3671
|
* Raphael 1.5.2 - JavaScript Vector Library
|
|
3537
3672
|
*
|
|
@@ -3588,7 +3723,7 @@ var Raphael$1 = (function () {
|
|
|
3588
3723
|
appendChild = "appendChild",
|
|
3589
3724
|
apply = "apply",
|
|
3590
3725
|
concat = "concat",
|
|
3591
|
-
supportsTouch =
|
|
3726
|
+
supportsTouch = "createTouch" in doc,
|
|
3592
3727
|
E = "",
|
|
3593
3728
|
S = " ",
|
|
3594
3729
|
Str = String,
|
|
@@ -5193,10 +5328,10 @@ var Raphael$1 = (function () {
|
|
|
5193
5328
|
});
|
|
5194
5329
|
break;
|
|
5195
5330
|
case "gradient":
|
|
5196
|
-
(
|
|
5331
|
+
({
|
|
5197
5332
|
circle: 1,
|
|
5198
5333
|
ellipse: 1
|
|
5199
|
-
}
|
|
5334
|
+
}[has](o.type) || Str(value).charAt() != "r") && _addGradientFill(node, value, o.paper);
|
|
5200
5335
|
break;
|
|
5201
5336
|
case "opacity":
|
|
5202
5337
|
if (attrs.gradient && !attrs[has]("stroke-opacity")) {
|
|
@@ -8023,8 +8158,8 @@ function polar$2(r, th) {
|
|
|
8023
8158
|
th = th * Math.PI / 180;
|
|
8024
8159
|
return [r[0] * Math.cos(th), r[1] * Math.sin(th)];
|
|
8025
8160
|
}
|
|
8026
|
-
var _bounds = /*#__PURE__*/
|
|
8027
|
-
var _drawingTransform = /*#__PURE__*/
|
|
8161
|
+
var _bounds = /*#__PURE__*/_classPrivateFieldLooseKey("bounds");
|
|
8162
|
+
var _drawingTransform = /*#__PURE__*/_classPrivateFieldLooseKey("drawingTransform");
|
|
8028
8163
|
class Graphie$1 {
|
|
8029
8164
|
constructor(el) {
|
|
8030
8165
|
this.el = void 0;
|
|
@@ -8229,8 +8364,8 @@ class Graphie$1 {
|
|
|
8229
8364
|
if (options.range == null) {
|
|
8230
8365
|
throw new PerseusError("range should be specified in graph init", Errors.Internal);
|
|
8231
8366
|
}
|
|
8232
|
-
|
|
8233
|
-
|
|
8367
|
+
_classPrivateFieldLooseBase(this, _bounds)[_bounds] = new GraphBounds(...options.range);
|
|
8368
|
+
_classPrivateFieldLooseBase(this, _drawingTransform)[_drawingTransform] = new DrawingTransform(this.raphael, scale, this.bounds());
|
|
8234
8369
|
const [w, h] = this.drawingTransform().canvasDimensions();
|
|
8235
8370
|
$(this.el).css({
|
|
8236
8371
|
width: w,
|
|
@@ -8465,16 +8600,16 @@ class Graphie$1 {
|
|
|
8465
8600
|
}
|
|
8466
8601
|
}
|
|
8467
8602
|
drawingTransform() {
|
|
8468
|
-
if (
|
|
8603
|
+
if (_classPrivateFieldLooseBase(this, _drawingTransform)[_drawingTransform] == null) {
|
|
8469
8604
|
throw new Error("Can't get drawingTransform of an uninitialized Graphie");
|
|
8470
8605
|
}
|
|
8471
|
-
return
|
|
8606
|
+
return _classPrivateFieldLooseBase(this, _drawingTransform)[_drawingTransform];
|
|
8472
8607
|
}
|
|
8473
8608
|
bounds() {
|
|
8474
|
-
if (
|
|
8609
|
+
if (_classPrivateFieldLooseBase(this, _bounds)[_bounds] == null) {
|
|
8475
8610
|
throw new Error("Can't get bounds of an uninitialized Graphie");
|
|
8476
8611
|
}
|
|
8477
|
-
return
|
|
8612
|
+
return _classPrivateFieldLooseBase(this, _bounds)[_bounds];
|
|
8478
8613
|
}
|
|
8479
8614
|
style(attrs, fn) {
|
|
8480
8615
|
const processed = this.processAttributes(attrs);
|
|
@@ -9025,8 +9160,10 @@ const labelDirections = {
|
|
|
9025
9160
|
// is equivalent to [[-5, 5], [-10, 10]].
|
|
9026
9161
|
// - A single magnitude to be used for both x and y. E.g. 10 is equivalent to
|
|
9027
9162
|
// [[-10, 10], [-10, 10]]
|
|
9163
|
+
|
|
9028
9164
|
// Overload normalizeRange to specify that it only returns undefined if its
|
|
9029
9165
|
// argument is undefined.
|
|
9166
|
+
|
|
9030
9167
|
// normalizeRange converts a RangeSpecifier into a pair of Intervals that give
|
|
9031
9168
|
// the x and y ranges, respectively
|
|
9032
9169
|
function normalizeRange(range) {
|
|
@@ -9219,7 +9356,7 @@ const GraphUtils = {
|
|
|
9219
9356
|
clickBlockList = [],
|
|
9220
9357
|
blockMouseTriggers = false,
|
|
9221
9358
|
blockTouchTriggers = false,
|
|
9222
|
-
eventCaptureSupported =
|
|
9359
|
+
eventCaptureSupported = "addEventListener" in document,
|
|
9223
9360
|
$document = $(document),
|
|
9224
9361
|
nextTouchID = 1,
|
|
9225
9362
|
lastTouchID = 0;
|
|
@@ -9914,7 +10051,6 @@ function scaledPolarRad(radius, radians) {
|
|
|
9914
10051
|
return [radius * Math.cos(radians), radius * Math.sin(radians) * -1 // SVG flips y axis
|
|
9915
10052
|
];
|
|
9916
10053
|
}
|
|
9917
|
-
|
|
9918
10054
|
function scaledPolarDeg(radius, degrees) {
|
|
9919
10055
|
const radians = degrees * Math.PI / 180;
|
|
9920
10056
|
return scaledPolarRad(radius, radians);
|
|
@@ -11732,7 +11868,6 @@ _.extend(GraphUtils.Graphie.prototype, {
|
|
|
11732
11868
|
if (hideArrow) {
|
|
11733
11869
|
return; // Don't draw anything!
|
|
11734
11870
|
}
|
|
11735
|
-
|
|
11736
11871
|
const handleVec = vector$3.subtract(handleCoord, rotatePoint.coord);
|
|
11737
11872
|
const handlePolar = vector$3.polarDegFromCart(handleVec);
|
|
11738
11873
|
const angle = handlePolar[1];
|
|
@@ -12696,7 +12831,6 @@ _.extend(MovableAngle.prototype, {
|
|
|
12696
12831
|
// clockwise to the first point
|
|
12697
12832
|
coords[1] // the vertex parameter is last
|
|
12698
12833
|
);
|
|
12699
|
-
|
|
12700
12834
|
const clockwiseAngle = rawAngle + 360 % 360;
|
|
12701
12835
|
return clockwiseAngle;
|
|
12702
12836
|
},
|
|
@@ -14070,7 +14204,7 @@ class MovablePoint$5 {
|
|
|
14070
14204
|
const showTrashTooltip = () => {
|
|
14071
14205
|
this._showTooltip(container => {
|
|
14072
14206
|
// eslint-disable-next-line import/no-deprecated
|
|
14073
|
-
render(
|
|
14207
|
+
render(/*#__PURE__*/React.createElement("span", {
|
|
14074
14208
|
style: {
|
|
14075
14209
|
fontSize: "2em"
|
|
14076
14210
|
}
|
|
@@ -14869,7 +15003,6 @@ const DEFAULT_PROPS = {
|
|
|
14869
15003
|
// likewise
|
|
14870
15004
|
labelStyle: null // likewise
|
|
14871
15005
|
};
|
|
14872
|
-
|
|
14873
15006
|
const DEFAULT_STATE = {
|
|
14874
15007
|
added: false,
|
|
14875
15008
|
hasMoved: false,
|
|
@@ -17520,7 +17653,7 @@ class TranslationLinter {
|
|
|
17520
17653
|
* Preprocess TeX code to convert things that MathJax doesn't know how to handle
|
|
17521
17654
|
* to things is does.
|
|
17522
17655
|
*/
|
|
17523
|
-
var preprocessTex =
|
|
17656
|
+
var preprocessTex = texCode => texCode
|
|
17524
17657
|
// Replace uses of \begin{align}...\end{align} which KaTeX doesn't
|
|
17525
17658
|
// support (yet) with \begin{aligned}...\end{aligned} which renders
|
|
17526
17659
|
// the same is supported by KaTeX. It does the same for align*.
|
|
@@ -17530,7 +17663,7 @@ var preprocessTex = (texCode => texCode
|
|
|
17530
17663
|
// "aligned").
|
|
17531
17664
|
.replace(/\{align[*]?\}/g, "{aligned}")
|
|
17532
17665
|
// Replace non-breaking spaces with regular spaces.
|
|
17533
|
-
.replace(/[\u00a0]/g, " ")
|
|
17666
|
+
.replace(/[\u00a0]/g, " ");
|
|
17534
17667
|
|
|
17535
17668
|
const {
|
|
17536
17669
|
interactiveSizes
|
|
@@ -17792,29 +17925,29 @@ const isLintable = type => {
|
|
|
17792
17925
|
};
|
|
17793
17926
|
|
|
17794
17927
|
var widgets$1 = /*#__PURE__*/Object.freeze({
|
|
17795
|
-
|
|
17796
|
-
|
|
17797
|
-
|
|
17798
|
-
|
|
17799
|
-
|
|
17800
|
-
|
|
17801
|
-
|
|
17802
|
-
|
|
17803
|
-
|
|
17804
|
-
|
|
17805
|
-
|
|
17806
|
-
|
|
17807
|
-
|
|
17808
|
-
|
|
17809
|
-
|
|
17810
|
-
|
|
17811
|
-
|
|
17812
|
-
|
|
17813
|
-
|
|
17814
|
-
|
|
17815
|
-
|
|
17816
|
-
|
|
17817
|
-
|
|
17928
|
+
__proto__: null,
|
|
17929
|
+
registerWidget: registerWidget,
|
|
17930
|
+
registerWidgets: registerWidgets,
|
|
17931
|
+
replaceWidget: replaceWidget,
|
|
17932
|
+
replaceDeprecatedWidgets: replaceDeprecatedWidgets,
|
|
17933
|
+
registerEditors: registerEditors,
|
|
17934
|
+
replaceEditor: replaceEditor,
|
|
17935
|
+
replaceDeprecatedEditors: replaceDeprecatedEditors,
|
|
17936
|
+
getWidget: getWidget,
|
|
17937
|
+
getWidgetExport: getWidgetExport,
|
|
17938
|
+
getEditor: getEditor,
|
|
17939
|
+
getTransform: getTransform,
|
|
17940
|
+
getVersion: getVersion,
|
|
17941
|
+
getVersionVector: getVersionVector,
|
|
17942
|
+
getPublicWidgets: getPublicWidgets,
|
|
17943
|
+
isAccessible: isAccessible,
|
|
17944
|
+
getAllWidgetTypes: getAllWidgetTypes,
|
|
17945
|
+
getRendererPropsForWidgetInfo: getRendererPropsForWidgetInfo,
|
|
17946
|
+
traverseChildWidgets: traverseChildWidgets$3,
|
|
17947
|
+
supportsStaticMode: supportsStaticMode,
|
|
17948
|
+
getStaticTransform: getStaticTransform,
|
|
17949
|
+
getTracking: getTracking,
|
|
17950
|
+
isLintable: isLintable
|
|
17818
17951
|
});
|
|
17819
17952
|
|
|
17820
17953
|
class WidgetContainer extends React.Component {
|
|
@@ -17857,7 +17990,6 @@ class WidgetContainer extends React.Component {
|
|
|
17857
17990
|
/* eslint-enable react/no-did-mount-set-state */
|
|
17858
17991
|
}
|
|
17859
17992
|
}
|
|
17860
|
-
|
|
17861
17993
|
UNSAFE_componentWillReceiveProps(nextProps) {
|
|
17862
17994
|
if (this.props.type !== nextProps.type) {
|
|
17863
17995
|
throw new Error("WidgetContainer can't change widget type; set a different " + "key instead to recreate the container.");
|
|
@@ -19311,620 +19443,123 @@ const styles$A = {
|
|
|
19311
19443
|
}
|
|
19312
19444
|
};
|
|
19313
19445
|
|
|
19314
|
-
|
|
19315
|
-
|
|
19316
|
-
|
|
19317
|
-
|
|
19318
|
-
|
|
19319
|
-
|
|
19320
|
-
|
|
19321
|
-
|
|
19322
|
-
|
|
19323
|
-
|
|
19324
|
-
|
|
19325
|
-
|
|
19326
|
-
|
|
19327
|
-
|
|
19446
|
+
const {
|
|
19447
|
+
captureScratchpadTouchStart: captureScratchpadTouchStart$2
|
|
19448
|
+
} = Util;
|
|
19449
|
+
// [LEMS-2411](Jan 2025) Third: This component has been moved to the NumericInput
|
|
19450
|
+
// folder as we are actively working towards removing the InputNumber widget.
|
|
19451
|
+
// This comment can be removed as part of LEMS-2411.
|
|
19452
|
+
|
|
19453
|
+
/**
|
|
19454
|
+
* The InputWithExamples component is a child component of the NumericInput
|
|
19455
|
+
* and InputNumber components. It is responsible for rendering the UI elements
|
|
19456
|
+
* for the desktop versions of these widgets, and displays a tooltip with
|
|
19457
|
+
* examples of how to input the selected answer forms.
|
|
19458
|
+
*/
|
|
19459
|
+
const InputWithExamples = /*#__PURE__*/forwardRef((props, ref) => {
|
|
19460
|
+
// Desctructure the props to set default values
|
|
19461
|
+
const {
|
|
19462
|
+
shouldShowExamples = true,
|
|
19463
|
+
onFocus = () => {},
|
|
19464
|
+
onBlur = () => {},
|
|
19465
|
+
disabled = false,
|
|
19466
|
+
linterContext = PerseusLinter.linterContextDefault,
|
|
19467
|
+
className = ""
|
|
19468
|
+
} = props;
|
|
19469
|
+
const context = React.useContext(PerseusI18nContext);
|
|
19470
|
+
const inputRef = React.useRef(null);
|
|
19471
|
+
const [inputFocused, setInputFocused] = React.useState(false);
|
|
19472
|
+
const id = useId();
|
|
19473
|
+
const ariaId = `aria-for-${id}`;
|
|
19474
|
+
useImperativeHandle(ref, () => ({
|
|
19475
|
+
current: inputRef.current,
|
|
19476
|
+
focus: () => {
|
|
19477
|
+
if (inputRef.current) {
|
|
19478
|
+
inputRef.current.focus();
|
|
19479
|
+
}
|
|
19480
|
+
},
|
|
19481
|
+
blur: () => {
|
|
19482
|
+
if (inputRef.current) {
|
|
19483
|
+
inputRef.current.blur();
|
|
19328
19484
|
}
|
|
19329
|
-
return this.inputRef.current;
|
|
19330
|
-
};
|
|
19331
|
-
this.focus = () => {
|
|
19332
|
-
this._getInput().focus();
|
|
19333
|
-
};
|
|
19334
|
-
this.blur = () => {
|
|
19335
|
-
this._getInput().blur();
|
|
19336
|
-
};
|
|
19337
|
-
this.getValue = () => {
|
|
19338
|
-
var _this$inputRef$curren;
|
|
19339
|
-
return (_this$inputRef$curren = this.inputRef.current) == null ? void 0 : _this$inputRef$curren.value;
|
|
19340
|
-
};
|
|
19341
|
-
this.getStringValue = () => {
|
|
19342
|
-
var _this$inputRef$curren2;
|
|
19343
|
-
return (_this$inputRef$curren2 = this.inputRef.current) == null ? void 0 : _this$inputRef$curren2.value.toString();
|
|
19344
|
-
};
|
|
19345
|
-
this.setSelectionRange = (selectionStart, selectionEnd) => {
|
|
19346
|
-
this._getInput().setSelectionRange(selectionStart, selectionEnd);
|
|
19347
|
-
};
|
|
19348
|
-
this.getSelectionStart = () => {
|
|
19349
|
-
return this._getInput().selectionStart;
|
|
19350
|
-
};
|
|
19351
|
-
this.getSelectionEnd = () => {
|
|
19352
|
-
return this._getInput().selectionEnd;
|
|
19353
|
-
};
|
|
19354
|
-
if (props.id) {
|
|
19355
|
-
this.id = props.id;
|
|
19356
|
-
} else {
|
|
19357
|
-
this.id = uniqueIdForInput();
|
|
19358
19485
|
}
|
|
19359
|
-
}
|
|
19360
|
-
|
|
19361
|
-
|
|
19362
|
-
|
|
19363
|
-
|
|
19364
|
-
|
|
19365
|
-
|
|
19366
|
-
|
|
19367
|
-
|
|
19368
|
-
|
|
19369
|
-
|
|
19370
|
-
|
|
19371
|
-
|
|
19372
|
-
|
|
19373
|
-
|
|
19374
|
-
|
|
19486
|
+
}));
|
|
19487
|
+
const getInputClassName = () => {
|
|
19488
|
+
let inputClassName = ClassNames.INPUT;
|
|
19489
|
+
if (inputFocused) {
|
|
19490
|
+
inputClassName += " " + ClassNames.FOCUSED;
|
|
19491
|
+
}
|
|
19492
|
+
if (className) {
|
|
19493
|
+
inputClassName += " " + className;
|
|
19494
|
+
}
|
|
19495
|
+
return inputClassName;
|
|
19496
|
+
};
|
|
19497
|
+
const handleFocus = () => {
|
|
19498
|
+
onFocus();
|
|
19499
|
+
setInputFocused(true);
|
|
19500
|
+
};
|
|
19501
|
+
const handleBlur = () => {
|
|
19502
|
+
onBlur();
|
|
19503
|
+
setInputFocused(false);
|
|
19504
|
+
};
|
|
19505
|
+
const renderInput = () => {
|
|
19506
|
+
// Generate the provided examples in simple language for screen readers.
|
|
19507
|
+
const examplesAria = props.shouldShowExamples ? `${props.examples[0]}
|
|
19508
|
+
${props.examples.slice(1).join(", or\n")}`
|
|
19509
|
+
// @ts-expect-error TS2550: Property replaceAll does not exist on type string.
|
|
19510
|
+
.replaceAll("*", "").replaceAll("$", "").replaceAll("\\ \\text{pi}", " pi").replaceAll("\\ ", " and ") : "";
|
|
19511
|
+
const inputProps = {
|
|
19512
|
+
id: id,
|
|
19513
|
+
// If we have examples, we want to provide the aria-describedby attribute
|
|
19514
|
+
"aria-describedby": props.shouldShowExamples ? ariaId : undefined,
|
|
19515
|
+
ref: inputRef,
|
|
19516
|
+
className: getInputClassName(),
|
|
19517
|
+
labelText: props.labelText,
|
|
19518
|
+
value: props.value,
|
|
19519
|
+
onFocus: handleFocus,
|
|
19520
|
+
onBlur: handleBlur,
|
|
19375
19521
|
disabled: disabled,
|
|
19376
|
-
|
|
19377
|
-
|
|
19378
|
-
|
|
19379
|
-
"aria-label": labelText,
|
|
19380
|
-
"aria-describedby": this.props["aria-describedby"],
|
|
19381
|
-
onChange: value => this.props.onChange(value),
|
|
19382
|
-
placeholder: placeholder,
|
|
19383
|
-
onFocus: onFocus,
|
|
19384
|
-
onBlur: onBlur,
|
|
19385
|
-
onKeyDown: onKeyDown
|
|
19386
|
-
// @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>'.
|
|
19387
|
-
,
|
|
19388
|
-
autoCorrect: "off",
|
|
19522
|
+
style: props.style,
|
|
19523
|
+
onChange: props.onChange,
|
|
19524
|
+
onTouchStart: captureScratchpadTouchStart$2,
|
|
19389
19525
|
autoCapitalize: "off",
|
|
19390
|
-
autoComplete: "off"
|
|
19391
|
-
|
|
19392
|
-
|
|
19393
|
-
}
|
|
19394
|
-
TextInput.defaultProps = {
|
|
19395
|
-
value: "",
|
|
19396
|
-
disabled: false
|
|
19397
|
-
};
|
|
19398
|
-
|
|
19399
|
-
function _objectWithoutPropertiesLoose(r, e) {
|
|
19400
|
-
if (null == r) return {};
|
|
19401
|
-
var t = {};
|
|
19402
|
-
for (var n in r) if ({}.hasOwnProperty.call(r, n)) {
|
|
19403
|
-
if (-1 !== e.indexOf(n)) continue;
|
|
19404
|
-
t[n] = r[n];
|
|
19405
|
-
}
|
|
19406
|
-
return t;
|
|
19407
|
-
}
|
|
19408
|
-
|
|
19409
|
-
const _excluded$4 = ["position", "visibility", "left", "top"];
|
|
19410
|
-
const zIndex = 10;
|
|
19411
|
-
let HorizontalDirection = /*#__PURE__*/function (HorizontalDirection) {
|
|
19412
|
-
HorizontalDirection["Left"] = "left";
|
|
19413
|
-
HorizontalDirection["Right"] = "right";
|
|
19414
|
-
return HorizontalDirection;
|
|
19415
|
-
}({});
|
|
19416
|
-
let VerticalDirection = /*#__PURE__*/function (VerticalDirection) {
|
|
19417
|
-
VerticalDirection["Top"] = "top";
|
|
19418
|
-
VerticalDirection["Bottom"] = "bottom";
|
|
19419
|
-
return VerticalDirection;
|
|
19420
|
-
}({});
|
|
19421
|
-
const Triangle = props => {
|
|
19422
|
-
let borderLeft;
|
|
19423
|
-
let borderRight;
|
|
19424
|
-
let borderTop;
|
|
19425
|
-
let borderBottom;
|
|
19426
|
-
const hBorder = `${props.width}px solid transparent`;
|
|
19427
|
-
if (props.horizontalDirection === "right") {
|
|
19428
|
-
borderLeft = hBorder;
|
|
19429
|
-
} else {
|
|
19430
|
-
borderRight = hBorder;
|
|
19431
|
-
}
|
|
19432
|
-
const vBorder = `${props.height}px solid ${props.color}`;
|
|
19433
|
-
if (props.verticalDirection === "top") {
|
|
19434
|
-
borderTop = vBorder;
|
|
19435
|
-
} else {
|
|
19436
|
-
borderBottom = vBorder;
|
|
19437
|
-
}
|
|
19438
|
-
return /*#__PURE__*/React.createElement("div", {
|
|
19439
|
-
style: {
|
|
19440
|
-
display: "block",
|
|
19441
|
-
height: 0,
|
|
19442
|
-
width: 0,
|
|
19443
|
-
position: "absolute",
|
|
19444
|
-
left: props.left,
|
|
19445
|
-
top: props.top,
|
|
19446
|
-
borderLeft: borderLeft,
|
|
19447
|
-
borderRight: borderRight,
|
|
19448
|
-
borderTop: borderTop,
|
|
19449
|
-
borderBottom: borderBottom
|
|
19450
|
-
}
|
|
19451
|
-
});
|
|
19452
|
-
};
|
|
19453
|
-
const TooltipArrow = _ref => {
|
|
19454
|
-
let {
|
|
19455
|
-
position = "relative",
|
|
19456
|
-
visibility = "visible",
|
|
19457
|
-
left = 100,
|
|
19458
|
-
top = 0
|
|
19459
|
-
} = _ref,
|
|
19460
|
-
props = _objectWithoutPropertiesLoose(_ref, _excluded$4);
|
|
19461
|
-
// TODO(aria): Think about adding a box-shadow to the triangle here
|
|
19462
|
-
// See http://css-tricks.com/triangle-with-shadow/
|
|
19463
|
-
|
|
19464
|
-
//const isRight = (this.props.horizontalDirection === "right");
|
|
19465
|
-
const isTop = props.verticalDirection === "top";
|
|
19466
|
-
const frontTopOffset = isTop ? 0 : 1;
|
|
19467
|
-
const borderTopOffset = isTop ? 0 : -1;
|
|
19468
|
-
return /*#__PURE__*/React.createElement("div", {
|
|
19469
|
-
style: {
|
|
19470
|
-
display: "block",
|
|
19471
|
-
position: position,
|
|
19472
|
-
visibility: visibility,
|
|
19473
|
-
left: left,
|
|
19474
|
-
top: top,
|
|
19475
|
-
width: props.width + 2,
|
|
19476
|
-
height: props.height + 1,
|
|
19477
|
-
marginTop: -1,
|
|
19478
|
-
marginBottom: -2,
|
|
19479
|
-
zIndex: zIndex
|
|
19480
|
-
}
|
|
19481
|
-
}, /*#__PURE__*/React.createElement(Triangle, {
|
|
19482
|
-
horizontalDirection: props.horizontalDirection,
|
|
19483
|
-
verticalDirection: props.verticalDirection,
|
|
19484
|
-
color: props.border,
|
|
19485
|
-
left: 0,
|
|
19486
|
-
top: borderTopOffset,
|
|
19487
|
-
width: props.width + 2 // one extra for the diagonal
|
|
19488
|
-
,
|
|
19489
|
-
height: props.height + 2
|
|
19490
|
-
}), /*#__PURE__*/React.createElement(Triangle, {
|
|
19491
|
-
horizontalDirection: props.horizontalDirection,
|
|
19492
|
-
verticalDirection: props.verticalDirection,
|
|
19493
|
-
color: props.color,
|
|
19494
|
-
left: 1,
|
|
19495
|
-
top: frontTopOffset,
|
|
19496
|
-
width: props.width,
|
|
19497
|
-
height: props.height
|
|
19498
|
-
}));
|
|
19499
|
-
};
|
|
19500
|
-
const VERTICAL_CORNERS = {
|
|
19501
|
-
top: {
|
|
19502
|
-
top: "-100%"
|
|
19503
|
-
},
|
|
19504
|
-
bottom: {
|
|
19505
|
-
top: 0
|
|
19506
|
-
}
|
|
19507
|
-
};
|
|
19508
|
-
const HORIZONTAL_CORNERS = {
|
|
19509
|
-
left: {
|
|
19510
|
-
targetLeft: 0
|
|
19511
|
-
},
|
|
19512
|
-
right: {
|
|
19513
|
-
targetLeft: "100%"
|
|
19514
|
-
}
|
|
19515
|
-
};
|
|
19516
|
-
const HORIZONTAL_ALIGNMNENTS = {
|
|
19517
|
-
left: {
|
|
19518
|
-
tooltipLeft: 0,
|
|
19519
|
-
arrowLeft: arrowSize => 0
|
|
19520
|
-
},
|
|
19521
|
-
right: {
|
|
19522
|
-
tooltipLeft: "-100%",
|
|
19523
|
-
arrowLeft: arrowSize => -arrowSize - 2
|
|
19524
|
-
}
|
|
19525
|
-
};
|
|
19526
|
-
/**
|
|
19527
|
-
* DEPRECATED! Use Wonder Blocks tooltip instead.
|
|
19528
|
-
*
|
|
19529
|
-
* A generic tooltip library for React.js
|
|
19530
|
-
*
|
|
19531
|
-
* ```
|
|
19532
|
-
* import Tooltip from "./tooltip";
|
|
19533
|
-
* <Tooltip
|
|
19534
|
-
* className="class-for-tooltip-contents"
|
|
19535
|
-
* horizontalPosition={HoriziontalDirection.Left}
|
|
19536
|
-
* horizontalAlign={HoriziontalDirection.Left}
|
|
19537
|
-
* verticalPosition={VerticalDirection.Top}
|
|
19538
|
-
* arrowSize={10} // arrow size in pixels
|
|
19539
|
-
* borderColor="#ccc" // color of the border for the tooltip
|
|
19540
|
-
* show={true} // whether the tooltip should currently be visible
|
|
19541
|
-
* targetContainerStyle={targetContainerStyle}
|
|
19542
|
-
* >
|
|
19543
|
-
* <TargetElementOfTheTooltip />
|
|
19544
|
-
* <TooltipContents1 />
|
|
19545
|
-
* <TooltipContents2 />
|
|
19546
|
-
* </Tooltip>
|
|
19547
|
-
* ```
|
|
19548
|
-
*
|
|
19549
|
-
* To show/hide the tooltip, the parent component should call the
|
|
19550
|
-
* `.show()` and `.hide()` methods of the tooltip when appropriate.
|
|
19551
|
-
* (These are usually set up as handlers of events on the target element.)
|
|
19552
|
-
*
|
|
19553
|
-
* Notes:
|
|
19554
|
-
* `className` should not specify a border; that is handled by `borderColor`
|
|
19555
|
-
* so that the arrow and tooltip match
|
|
19556
|
-
*
|
|
19557
|
-
* ```
|
|
19558
|
-
* __,,--``\\
|
|
19559
|
-
* _,,-''`` \\ ,
|
|
19560
|
-
* '----------_.------'-.___|\__
|
|
19561
|
-
* _.--''`` `)__ )__ @\__
|
|
19562
|
-
* ( .. ''---/___,,E/__,E'------`
|
|
19563
|
-
* `-''`''
|
|
19564
|
-
* Here be dragons.
|
|
19565
|
-
* ```
|
|
19566
|
-
*/
|
|
19567
|
-
class Tooltip extends React.Component {
|
|
19568
|
-
constructor(...args) {
|
|
19569
|
-
super(...args);
|
|
19570
|
-
this.tooltipContainerRef = /*#__PURE__*/React.createRef();
|
|
19571
|
-
this.state = {
|
|
19572
|
-
height: null // used for offsetting "top" positioned tooltips
|
|
19526
|
+
autoComplete: "off",
|
|
19527
|
+
autoCorrect: "off",
|
|
19528
|
+
spellCheck: "false"
|
|
19573
19529
|
};
|
|
19574
|
-
|
|
19575
|
-
|
|
19576
|
-
componentDidMount() {
|
|
19577
|
-
this._updateHeight();
|
|
19578
|
-
}
|
|
19579
|
-
UNSAFE_componentWillReceiveProps() {
|
|
19580
|
-
// If the contents have changed, reset our measure of the height
|
|
19581
|
-
this.setState({
|
|
19582
|
-
height: null
|
|
19583
|
-
});
|
|
19584
|
-
}
|
|
19585
|
-
componentDidUpdate() {
|
|
19586
|
-
this._updateHeight();
|
|
19587
|
-
}
|
|
19588
|
-
_renderToolTipDiv(isTooltipAbove) {
|
|
19589
|
-
const settings = Object.assign({}, HORIZONTAL_CORNERS[this.props.horizontalPosition], HORIZONTAL_ALIGNMNENTS[this.props.horizontalAlign], VERTICAL_CORNERS[this.props.verticalPosition]);
|
|
19590
|
-
let arrowAbove;
|
|
19591
|
-
let arrowBelow;
|
|
19592
|
-
if (isTooltipAbove) {
|
|
19593
|
-
// We put an absolutely positioned arrow in the correct place
|
|
19594
|
-
arrowAbove = /*#__PURE__*/React.createElement(TooltipArrow, {
|
|
19595
|
-
verticalDirection: VerticalDirection.Top,
|
|
19596
|
-
horizontalDirection: this.props.horizontalAlign,
|
|
19597
|
-
position: "absolute",
|
|
19598
|
-
color: "white",
|
|
19599
|
-
border: this.props.borderColor,
|
|
19600
|
-
left: settings.arrowLeft(this.props.arrowSize),
|
|
19601
|
-
top: -this.props.arrowSize + 2,
|
|
19602
|
-
width: this.props.arrowSize,
|
|
19603
|
-
height: this.props.arrowSize,
|
|
19604
|
-
zIndex: zIndex
|
|
19605
|
-
});
|
|
19606
|
-
|
|
19607
|
-
// And we use a visibility: hidden arrow below to shift up the
|
|
19608
|
-
// content by the correct amount
|
|
19609
|
-
arrowBelow = /*#__PURE__*/React.createElement(TooltipArrow, {
|
|
19610
|
-
verticalDirection: VerticalDirection.Top,
|
|
19611
|
-
horizontalDirection: this.props.horizontalAlign,
|
|
19612
|
-
visibility: "hidden",
|
|
19613
|
-
color: "white",
|
|
19614
|
-
border: this.props.borderColor,
|
|
19615
|
-
left: settings.arrowLeft(this.props.arrowSize),
|
|
19616
|
-
top: -1,
|
|
19617
|
-
width: this.props.arrowSize,
|
|
19618
|
-
height: this.props.arrowSize,
|
|
19619
|
-
zIndex: zIndex
|
|
19620
|
-
});
|
|
19621
|
-
} else {
|
|
19622
|
-
arrowAbove = /*#__PURE__*/React.createElement(TooltipArrow, {
|
|
19623
|
-
verticalDirection: VerticalDirection.Bottom,
|
|
19624
|
-
horizontalDirection: this.props.horizontalAlign,
|
|
19625
|
-
color: "white",
|
|
19626
|
-
border: this.props.borderColor,
|
|
19627
|
-
left: settings.arrowLeft(this.props.arrowSize),
|
|
19628
|
-
top: -1,
|
|
19629
|
-
width: this.props.arrowSize,
|
|
19630
|
-
height: this.props.arrowSize,
|
|
19631
|
-
zIndex: zIndex
|
|
19632
|
-
});
|
|
19633
|
-
arrowBelow = null;
|
|
19634
|
-
}
|
|
19635
|
-
|
|
19636
|
-
/* A positioned div below the input to be the parent for our
|
|
19637
|
-
tooltip */
|
|
19638
|
-
return /*#__PURE__*/React.createElement("div", {
|
|
19530
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TextInput, inputProps), /*#__PURE__*/React.createElement("span", {
|
|
19531
|
+
id: ariaId,
|
|
19639
19532
|
style: {
|
|
19640
|
-
|
|
19641
|
-
height: 0,
|
|
19642
|
-
display: this.props.show ? "block" : "none"
|
|
19533
|
+
display: "none"
|
|
19643
19534
|
}
|
|
19644
|
-
},
|
|
19645
|
-
|
|
19646
|
-
|
|
19647
|
-
|
|
19648
|
-
|
|
19649
|
-
|
|
19650
|
-
// height must start out undefined, not null, so that
|
|
19651
|
-
// we can measure the actual height with jquery.
|
|
19652
|
-
// This is used to position the tooltip with top: -100%
|
|
19653
|
-
// when in verticalPosition: "top" mode
|
|
19654
|
-
height: this.state.height || undefined,
|
|
19655
|
-
left: settings.targetLeft
|
|
19656
|
-
}
|
|
19657
|
-
}, arrowAbove, /*#__PURE__*/React.createElement("div", {
|
|
19658
|
-
className: this.props.className
|
|
19659
|
-
// eslint-disable-next-line react/no-string-refs
|
|
19660
|
-
,
|
|
19661
|
-
ref: "tooltipContent",
|
|
19662
|
-
style: {
|
|
19663
|
-
position: "relative",
|
|
19664
|
-
top: settings["top"],
|
|
19665
|
-
left: settings.tooltipLeft,
|
|
19666
|
-
border: "1px solid " + this.props.borderColor,
|
|
19667
|
-
boxShadow: "0 1px 3px " + this.props.borderColor,
|
|
19668
|
-
zIndex: zIndex - 1
|
|
19669
|
-
}
|
|
19670
|
-
}, this.props.children.slice(1)), arrowBelow));
|
|
19671
|
-
}
|
|
19672
|
-
_updateHeight() {
|
|
19673
|
-
const tooltipContainer = this.tooltipContainerRef.current;
|
|
19674
|
-
const height = (tooltipContainer == null ? void 0 : tooltipContainer.offsetHeight) || 0;
|
|
19675
|
-
if (height !== this.state.height) {
|
|
19676
|
-
this.setState({
|
|
19677
|
-
height
|
|
19678
|
-
});
|
|
19679
|
-
}
|
|
19680
|
-
}
|
|
19681
|
-
render() {
|
|
19682
|
-
const isTooltipAbove = this.props.verticalPosition === "top";
|
|
19683
|
-
|
|
19684
|
-
/* We wrap the entire output in a span so that it displays inline */
|
|
19685
|
-
return /*#__PURE__*/React.createElement("span", null, isTooltipAbove && this._renderToolTipDiv(isTooltipAbove), /*#__PURE__*/React.createElement("div", {
|
|
19686
|
-
style: this.props.targetContainerStyle
|
|
19687
|
-
}, this.props.children[0]), !isTooltipAbove && this._renderToolTipDiv());
|
|
19688
|
-
}
|
|
19689
|
-
}
|
|
19690
|
-
|
|
19691
|
-
// Sorry. // Apology-Oriented-Programming
|
|
19692
|
-
Tooltip.defaultProps = {
|
|
19693
|
-
className: "",
|
|
19694
|
-
arrowSize: 10,
|
|
19695
|
-
borderColor: "#ccc",
|
|
19696
|
-
verticalPosition: VerticalDirection.Bottom,
|
|
19697
|
-
horizontalPosition: HorizontalDirection.Left,
|
|
19698
|
-
horizontalAlign: HorizontalDirection.Left,
|
|
19699
|
-
targetContainerStyle: {}
|
|
19700
|
-
};
|
|
19701
|
-
|
|
19702
|
-
const {
|
|
19703
|
-
captureScratchpadTouchStart: captureScratchpadTouchStart$2
|
|
19704
|
-
} = Util;
|
|
19705
|
-
class InputWithExamples extends React.Component {
|
|
19706
|
-
constructor(...args) {
|
|
19707
|
-
super(...args);
|
|
19708
|
-
this.state = {
|
|
19709
|
-
focused: false,
|
|
19710
|
-
showExamples: false
|
|
19711
|
-
};
|
|
19712
|
-
this._getUniqueId = () => {
|
|
19713
|
-
return `input-with-examples-${btoa(this.props.id).replace(/=/g, "")}`;
|
|
19714
|
-
};
|
|
19715
|
-
this._getInputClassName = () => {
|
|
19716
|
-
// Otherwise, we need to add these INPUT and FOCUSED tags here.
|
|
19717
|
-
let className = ClassNames.INPUT;
|
|
19718
|
-
if (this.state.focused) {
|
|
19719
|
-
className += " " + ClassNames.FOCUSED;
|
|
19720
|
-
}
|
|
19721
|
-
if (this.props.className) {
|
|
19722
|
-
className += " " + this.props.className;
|
|
19723
|
-
}
|
|
19724
|
-
return className;
|
|
19725
|
-
};
|
|
19726
|
-
this._renderInput = () => {
|
|
19727
|
-
const id = this._getUniqueId();
|
|
19728
|
-
const ariaId = `aria-for-${id}`;
|
|
19729
|
-
// Generate text from a known set of format options that will read well in a screen reader
|
|
19730
|
-
const examplesAria = this.props.examples.length === 7 ? "" : `${this.props.examples[0]}
|
|
19731
|
-
${this.props.examples.slice(1).join(", or\n")}`
|
|
19732
|
-
// @ts-expect-error TS2550: Property replaceAll does not exist on type string.
|
|
19733
|
-
.replaceAll("*", "").replaceAll("$", "").replaceAll("\\ \\text{pi}", " pi").replaceAll("\\ ", " and ");
|
|
19734
|
-
const inputProps = {
|
|
19735
|
-
id: id,
|
|
19736
|
-
"aria-describedby": ariaId,
|
|
19737
|
-
ref: "input",
|
|
19738
|
-
className: this._getInputClassName(),
|
|
19739
|
-
labelText: this.props.labelText,
|
|
19740
|
-
value: this.props.value,
|
|
19741
|
-
onFocus: this._handleFocus,
|
|
19742
|
-
onBlur: this._handleBlur,
|
|
19743
|
-
disabled: this.props.disabled,
|
|
19744
|
-
style: this.props.style,
|
|
19745
|
-
onChange: this.props.onChange,
|
|
19746
|
-
onTouchStart: captureScratchpadTouchStart$2,
|
|
19747
|
-
autoCapitalize: "off",
|
|
19748
|
-
autoComplete: "off",
|
|
19749
|
-
autoCorrect: "off",
|
|
19750
|
-
spellCheck: "false"
|
|
19751
|
-
};
|
|
19752
|
-
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(TextInput, inputProps), /*#__PURE__*/React.createElement("span", {
|
|
19753
|
-
id: ariaId,
|
|
19754
|
-
style: {
|
|
19755
|
-
display: "none"
|
|
19756
|
-
}
|
|
19757
|
-
}, examplesAria));
|
|
19758
|
-
};
|
|
19759
|
-
this._handleFocus = () => {
|
|
19760
|
-
this.props.onFocus();
|
|
19761
|
-
this.setState({
|
|
19762
|
-
focused: true,
|
|
19763
|
-
showExamples: true
|
|
19764
|
-
});
|
|
19765
|
-
};
|
|
19766
|
-
this.show = () => {
|
|
19767
|
-
this.setState({
|
|
19768
|
-
showExamples: true
|
|
19769
|
-
});
|
|
19770
|
-
};
|
|
19771
|
-
this.hide = () => {
|
|
19772
|
-
this.setState({
|
|
19773
|
-
showExamples: false
|
|
19774
|
-
});
|
|
19775
|
-
};
|
|
19776
|
-
this._handleBlur = () => {
|
|
19777
|
-
this.props.onBlur();
|
|
19778
|
-
this.setState({
|
|
19779
|
-
focused: false,
|
|
19780
|
-
showExamples: false
|
|
19781
|
-
});
|
|
19782
|
-
};
|
|
19783
|
-
this.focus = () => {
|
|
19784
|
-
// eslint-disable-next-line react/no-string-refs
|
|
19785
|
-
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
19786
|
-
this.refs.input.focus();
|
|
19787
|
-
};
|
|
19788
|
-
this.blur = () => {
|
|
19789
|
-
// eslint-disable-next-line react/no-string-refs
|
|
19790
|
-
// @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
|
|
19791
|
-
this.refs.input.blur();
|
|
19792
|
-
};
|
|
19793
|
-
this.handleChange = e => {
|
|
19794
|
-
this.props.onChange(e.target.value);
|
|
19795
|
-
};
|
|
19796
|
-
}
|
|
19797
|
-
render() {
|
|
19798
|
-
const input = this._renderInput();
|
|
19799
|
-
const examplesContent = this.props.examples.length <= 2 ? this.props.examples.join(" ") // A single item (with or without leading text) is not a "list"
|
|
19800
|
-
: this.props.examples // 2 or more items should display as a list
|
|
19801
|
-
.map((example, index) => {
|
|
19802
|
-
// If the first example is bold, then it is most likely a heading/leading text.
|
|
19803
|
-
// So, it shouldn't be part of the list.
|
|
19804
|
-
return index === 0 && example.startsWith("**") ? `${example}\n` : `- ${example}`;
|
|
19805
|
-
}).join("\n");
|
|
19806
|
-
const showExamples = this.props.shouldShowExamples && this.state.showExamples;
|
|
19807
|
-
return /*#__PURE__*/React.createElement(Tooltip, {
|
|
19808
|
-
className: "perseus-formats-tooltip preview-measure",
|
|
19809
|
-
horizontalPosition: HorizontalDirection.Left,
|
|
19810
|
-
horizontalAlign: HorizontalDirection.Left,
|
|
19811
|
-
verticalPosition: VerticalDirection.Bottom,
|
|
19812
|
-
arrowSize: 10,
|
|
19813
|
-
borderColor: "#ccc",
|
|
19814
|
-
show: showExamples
|
|
19815
|
-
}, input, /*#__PURE__*/React.createElement("div", {
|
|
19816
|
-
id: this._getUniqueId()
|
|
19535
|
+
}, examplesAria));
|
|
19536
|
+
};
|
|
19537
|
+
const renderTooltipContent = () => {
|
|
19538
|
+
return /*#__PURE__*/React.createElement(TooltipContent, null, /*#__PURE__*/React.createElement("div", {
|
|
19539
|
+
id: id,
|
|
19540
|
+
className: "input-with-examples-tooltip"
|
|
19817
19541
|
}, /*#__PURE__*/React.createElement(Renderer, {
|
|
19818
19542
|
content: examplesContent,
|
|
19819
|
-
linterContext: PerseusLinter.pushContextStack(
|
|
19820
|
-
strings:
|
|
19543
|
+
linterContext: PerseusLinter.pushContextStack(linterContext, "input-with-examples"),
|
|
19544
|
+
strings: context.strings
|
|
19821
19545
|
})));
|
|
19822
|
-
}
|
|
19823
|
-
}
|
|
19824
|
-
InputWithExamples.contextType = PerseusI18nContext;
|
|
19825
|
-
InputWithExamples.defaultProps = {
|
|
19826
|
-
shouldShowExamples: true,
|
|
19827
|
-
onFocus: function () {},
|
|
19828
|
-
onBlur: function () {},
|
|
19829
|
-
disabled: false,
|
|
19830
|
-
linterContext: PerseusLinter.linterContextDefault,
|
|
19831
|
-
className: ""
|
|
19832
|
-
};
|
|
19833
|
-
|
|
19834
|
-
const _excluded$3 = ["keypadElement", "onFocus", "value"];
|
|
19835
|
-
class SimpleKeypadInput extends React.Component {
|
|
19836
|
-
constructor(...args) {
|
|
19837
|
-
super(...args);
|
|
19838
|
-
this._isMounted = false;
|
|
19839
|
-
}
|
|
19840
|
-
componentDidMount() {
|
|
19841
|
-
// TODO(scottgrant): This is a hack to remove the deprecated call to
|
|
19842
|
-
// this.isMounted() but is still considered an anti-pattern.
|
|
19843
|
-
this._isMounted = true;
|
|
19844
|
-
}
|
|
19845
|
-
componentWillUnmount() {
|
|
19846
|
-
this._isMounted = false;
|
|
19847
|
-
}
|
|
19848
|
-
focus() {
|
|
19849
|
-
// @ts-expect-error - TS2339 - Property 'focus' does not exist on type 'ReactInstance'.
|
|
19850
|
-
this.refs.input.focus(); // eslint-disable-line react/no-string-refs
|
|
19851
|
-
}
|
|
19852
|
-
|
|
19853
|
-
blur() {
|
|
19854
|
-
var _this$refs$input;
|
|
19855
|
-
// eslint-disable-next-line react/no-string-refs
|
|
19856
|
-
// @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
|
|
19857
|
-
if (typeof ((_this$refs$input = this.refs.input) == null ? void 0 : _this$refs$input.blur) === "function") {
|
|
19858
|
-
var _this$refs$input2;
|
|
19859
|
-
// eslint-disable-next-line react/no-string-refs
|
|
19860
|
-
// @ts-expect-error - TS2339 - Property 'blur' does not exist on type 'ReactInstance'.
|
|
19861
|
-
(_this$refs$input2 = this.refs.input) == null || _this$refs$input2.blur();
|
|
19862
|
-
}
|
|
19863
|
-
}
|
|
19864
|
-
getValue() {
|
|
19865
|
-
return this.props.value;
|
|
19866
|
-
}
|
|
19867
|
-
render() {
|
|
19868
|
-
const _this = this;
|
|
19869
|
-
// Intercept the `onFocus` prop, as we need to configure the keypad
|
|
19870
|
-
// before continuing with the default focus logic for Perseus inputs.
|
|
19871
|
-
// Intercept the `value` prop so as to map `null` to the empty string,
|
|
19872
|
-
// as the `KeypadInput` does not support `null` values.
|
|
19873
|
-
const _this$props = _this.props,
|
|
19874
|
-
{
|
|
19875
|
-
keypadElement,
|
|
19876
|
-
onFocus,
|
|
19877
|
-
value
|
|
19878
|
-
} = _this$props,
|
|
19879
|
-
rest = _objectWithoutPropertiesLoose(_this$props, _excluded$3);
|
|
19880
|
-
return (
|
|
19881
|
-
/*#__PURE__*/
|
|
19882
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
19883
|
-
React.createElement(KeypadInput
|
|
19884
|
-
// eslint-disable-next-line react/no-string-refs
|
|
19885
|
-
, _extends({
|
|
19886
|
-
ref: "input",
|
|
19887
|
-
keypadElement: keypadElement,
|
|
19888
|
-
onFocus: () => {
|
|
19889
|
-
if (keypadElement) {
|
|
19890
|
-
keypadElement.configure({
|
|
19891
|
-
keypadType: KeypadType.FRACTION
|
|
19892
|
-
}, () => {
|
|
19893
|
-
if (_this._isMounted) {
|
|
19894
|
-
onFocus == null || onFocus();
|
|
19895
|
-
}
|
|
19896
|
-
});
|
|
19897
|
-
} else {
|
|
19898
|
-
onFocus == null || onFocus();
|
|
19899
|
-
}
|
|
19900
|
-
},
|
|
19901
|
-
value: value == null ? "" : "" + value
|
|
19902
|
-
}, rest))
|
|
19903
|
-
);
|
|
19904
|
-
}
|
|
19905
|
-
}
|
|
19906
|
-
|
|
19907
|
-
// @ts-expect-error - TS2339 - Property 'propTypes' does not exist on type 'typeof SimpleKeypadInput'.
|
|
19908
|
-
SimpleKeypadInput.propTypes = {
|
|
19909
|
-
keypadElement: keypadElementPropType,
|
|
19910
|
-
onFocus: PropTypes.func,
|
|
19911
|
-
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
19912
|
-
};
|
|
19913
|
-
|
|
19914
|
-
const getPromptJSON$s = (renderProps, userInput) => {
|
|
19915
|
-
return {
|
|
19916
|
-
type: "input-number",
|
|
19917
|
-
options: {
|
|
19918
|
-
simplify: renderProps.simplify,
|
|
19919
|
-
answerType: renderProps.answerType
|
|
19920
|
-
},
|
|
19921
|
-
userInput: {
|
|
19922
|
-
value: userInput.currentValue
|
|
19923
|
-
}
|
|
19924
19546
|
};
|
|
19925
|
-
|
|
19547
|
+
// Display the examples as a string when there are less than or equal to 2 examples.
|
|
19548
|
+
// Otherwise, display the examples as a list.
|
|
19549
|
+
const examplesContent = props.examples.length <= 2 ? props.examples.join(" ") : props.examples.map((example, index) => {
|
|
19550
|
+
return index === 0 && example.startsWith("**") ? `${example}\n` : `- ${example}`;
|
|
19551
|
+
}).join("\n");
|
|
19552
|
+
|
|
19553
|
+
// Display the examples when they are enabled (shouldShowExamples) and the input is focused.
|
|
19554
|
+
const showExamplesTooltip = shouldShowExamples && inputFocused;
|
|
19555
|
+
return /*#__PURE__*/React.createElement(Tooltip, {
|
|
19556
|
+
content: renderTooltipContent(),
|
|
19557
|
+
opened: showExamplesTooltip,
|
|
19558
|
+
placement: "bottom"
|
|
19559
|
+
}, renderInput());
|
|
19560
|
+
});
|
|
19926
19561
|
|
|
19927
|
-
const formExamples
|
|
19562
|
+
const formExamples = {
|
|
19928
19563
|
integer: function (options, strings) {
|
|
19929
19564
|
return strings.integerExample;
|
|
19930
19565
|
},
|
|
@@ -20027,7 +19662,7 @@ class InputNumber extends React.Component {
|
|
|
20027
19662
|
} = this.context;
|
|
20028
19663
|
const type = this.props.answerType;
|
|
20029
19664
|
const forms = inputNumberAnswerTypes[type].forms.split(/\s*,\s*/);
|
|
20030
|
-
const examples = _.map(forms, form => formExamples
|
|
19665
|
+
const examples = _.map(forms, form => formExamples[form](this.props, strings));
|
|
20031
19666
|
return [strings.yourAnswer].concat(examples);
|
|
20032
19667
|
}
|
|
20033
19668
|
render() {
|
|
@@ -20152,7 +19787,11 @@ const getPromptJSON$r = (renderProps, userInput) => {
|
|
|
20152
19787
|
};
|
|
20153
19788
|
};
|
|
20154
19789
|
|
|
20155
|
-
|
|
19790
|
+
/**
|
|
19791
|
+
* The full list of available strings for the numeric input widget,
|
|
19792
|
+
* based on whether the Content Creator has specified that the answer must be simplified.
|
|
19793
|
+
*/
|
|
19794
|
+
const NumericExampleStrings = {
|
|
20156
19795
|
integer: (form, strings) => strings.integerExample,
|
|
20157
19796
|
proper: (form, strings) => form.simplify === "optional" ? strings.properExample : strings.simplifiedProperExample,
|
|
20158
19797
|
improper: (form, strings) => form.simplify === "optional" ? strings.improperExample : strings.simplifiedImproperExample,
|
|
@@ -20160,38 +19799,200 @@ const formExamples = {
|
|
|
20160
19799
|
decimal: (form, strings) => strings.decimalExample,
|
|
20161
19800
|
pi: (form, strings) => strings.piExample
|
|
20162
19801
|
};
|
|
19802
|
+
|
|
19803
|
+
/**
|
|
19804
|
+
* Generates the specific set of examples for the current question.
|
|
19805
|
+
* This string is shown as examples to the user in a tooltip.
|
|
19806
|
+
*/
|
|
19807
|
+
const generateExamples = (answerForms, strings) => {
|
|
19808
|
+
// If the Content Creator has not specified any answer forms,
|
|
19809
|
+
// we do not need to show any examples.
|
|
19810
|
+
if (answerForms.length === 0) {
|
|
19811
|
+
return [];
|
|
19812
|
+
}
|
|
19813
|
+
|
|
19814
|
+
// Generate a list of the unique answer forms.
|
|
19815
|
+
const uniqueForms = getUniqueAnswerForms(answerForms);
|
|
19816
|
+
|
|
19817
|
+
// Generate the example strings for each unique form.
|
|
19818
|
+
const examples = uniqueForms.map(form => {
|
|
19819
|
+
return NumericExampleStrings[form.name](form, strings);
|
|
19820
|
+
});
|
|
19821
|
+
|
|
19822
|
+
// Add the "Your answer" string to the beginning of the examples list.
|
|
19823
|
+
return [strings.yourAnswer].concat(examples);
|
|
19824
|
+
};
|
|
19825
|
+
|
|
19826
|
+
/**
|
|
19827
|
+
* Determines whether to show examples of how to input the various supported answer forms.
|
|
19828
|
+
* We do not show examples if all forms are accepted or if no forms are accepted.
|
|
19829
|
+
*/
|
|
19830
|
+
const shouldShowExamples = answerForms => {
|
|
19831
|
+
// If the Content Creator has not specified any answer forms,
|
|
19832
|
+
// we do not need to show any examples.
|
|
19833
|
+
if (answerForms.length === 0) {
|
|
19834
|
+
return false;
|
|
19835
|
+
}
|
|
19836
|
+
|
|
19837
|
+
// Generate a list of the unique names of the selected answer forms.
|
|
19838
|
+
const answerFormNames = getUniqueAnswerForms(answerForms).map(form => form.name);
|
|
19839
|
+
|
|
19840
|
+
// If all forms are accepted, we do not need to show any examples.
|
|
19841
|
+
const allFormsAccepted = answerFormNames.length >= Object.keys(NumericExampleStrings).length;
|
|
19842
|
+
return !allFormsAccepted;
|
|
19843
|
+
};
|
|
19844
|
+
|
|
19845
|
+
/**
|
|
19846
|
+
* uniqueAnswerForms takes a list of answer forms and returns a list of unique
|
|
19847
|
+
* answer forms. This is useful for ensuring that we don't show duplicate examples
|
|
19848
|
+
* to the user.
|
|
19849
|
+
*/
|
|
19850
|
+
const getUniqueAnswerForms = function getUniqueAnswerForms(list) {
|
|
19851
|
+
// We use a Set to keep track of the forms we've already seen.
|
|
19852
|
+
const foundForms = new Set();
|
|
19853
|
+
return list.filter(form => {
|
|
19854
|
+
// If we've already seen this form, skip it.
|
|
19855
|
+
if (foundForms.has(form.name)) {
|
|
19856
|
+
return false;
|
|
19857
|
+
}
|
|
19858
|
+
// Otherwise, add it to the set and return true.
|
|
19859
|
+
foundForms.add(form.name);
|
|
19860
|
+
return true;
|
|
19861
|
+
});
|
|
19862
|
+
};
|
|
19863
|
+
|
|
19864
|
+
/**
|
|
19865
|
+
* Takes a list of lists of answer forms, and returns a list of the forms
|
|
19866
|
+
* in each of these lists in the same order that they're listed in the
|
|
19867
|
+
* `formExamples` forms from above.
|
|
19868
|
+
*/
|
|
19869
|
+
const unionAnswerForms = function unionAnswerForms(answerFormsList) {
|
|
19870
|
+
// Pull out all of the forms from the different lists.
|
|
19871
|
+
const allForms = answerFormsList.flat();
|
|
19872
|
+
// Pull out the unique forms using getUniqueAnswerForms.
|
|
19873
|
+
const uniqueForms = getUniqueAnswerForms(allForms);
|
|
19874
|
+
// Sort them by the order they appear in the `formExamples` list.
|
|
19875
|
+
const formExampleKeys = Object.keys(NumericExampleStrings);
|
|
19876
|
+
return uniqueForms.sort((a, b) => {
|
|
19877
|
+
return formExampleKeys.indexOf(a.name) - formExampleKeys.indexOf(b.name);
|
|
19878
|
+
});
|
|
19879
|
+
};
|
|
19880
|
+
|
|
19881
|
+
/**
|
|
19882
|
+
* The NumericInputComponent is a child component of the NumericInput class
|
|
19883
|
+
* component. It is responsible for rendering the UI elements of the Numeric
|
|
19884
|
+
* Input widget.
|
|
19885
|
+
*/
|
|
19886
|
+
const NumericInputComponent = /*#__PURE__*/forwardRef((props, ref) => {
|
|
19887
|
+
const context = useContext(PerseusI18nContext);
|
|
19888
|
+
const inputRef = useRef(null);
|
|
19889
|
+
const [isFocused, setIsFocused] = useState(false);
|
|
19890
|
+
|
|
19891
|
+
// Pass the focus and blur methods to the Numeric Input Class component
|
|
19892
|
+
useImperativeHandle(ref, () => ({
|
|
19893
|
+
current: inputRef.current,
|
|
19894
|
+
focus: () => {
|
|
19895
|
+
if (inputRef.current) {
|
|
19896
|
+
var _inputRef$current;
|
|
19897
|
+
(_inputRef$current = inputRef.current) == null || _inputRef$current.focus();
|
|
19898
|
+
setIsFocused(true);
|
|
19899
|
+
}
|
|
19900
|
+
},
|
|
19901
|
+
blur: () => {
|
|
19902
|
+
if (inputRef.current) {
|
|
19903
|
+
var _inputRef$current2;
|
|
19904
|
+
(_inputRef$current2 = inputRef.current) == null || _inputRef$current2.blur();
|
|
19905
|
+
setIsFocused(false);
|
|
19906
|
+
}
|
|
19907
|
+
}
|
|
19908
|
+
}));
|
|
19909
|
+
const handleChange = (newValue, cb) => {
|
|
19910
|
+
props.onChange({
|
|
19911
|
+
currentValue: newValue
|
|
19912
|
+
}, cb);
|
|
19913
|
+
props.trackInteraction();
|
|
19914
|
+
};
|
|
19915
|
+
const handleFocus = () => {
|
|
19916
|
+
props.onFocus([]);
|
|
19917
|
+
setIsFocused(true);
|
|
19918
|
+
};
|
|
19919
|
+
const handleBlur = () => {
|
|
19920
|
+
props.onBlur([]);
|
|
19921
|
+
setIsFocused(false);
|
|
19922
|
+
};
|
|
19923
|
+
|
|
19924
|
+
// Styles for the InputWithExamples
|
|
19925
|
+
const styles = StyleSheet.create({
|
|
19926
|
+
inputWithExamples: {
|
|
19927
|
+
borderRadius: "3px",
|
|
19928
|
+
borderWidth: isFocused ? "2px" : "1px",
|
|
19929
|
+
display: "inline-block",
|
|
19930
|
+
fontFamily: `Symbola, "Times New Roman", serif`,
|
|
19931
|
+
fontSize: "18px",
|
|
19932
|
+
height: "32px",
|
|
19933
|
+
lineHeight: "18px",
|
|
19934
|
+
padding: isFocused ? "4px" : "4px 5px",
|
|
19935
|
+
textAlign: props.rightAlign ? "right" : "left",
|
|
19936
|
+
width: props.size === "small" ? 40 : 80
|
|
19937
|
+
}
|
|
19938
|
+
});
|
|
19939
|
+
|
|
19940
|
+
// (mobile-only) If the custom keypad is enabled, use the SimpleKeypadInput component
|
|
19941
|
+
if (props.apiOptions.customKeypad) {
|
|
19942
|
+
const alignmentClass = props.rightAlign ? "perseus-input-right-align" : undefined;
|
|
19943
|
+
return /*#__PURE__*/React.createElement("div", {
|
|
19944
|
+
className: alignmentClass
|
|
19945
|
+
}, /*#__PURE__*/React.createElement(SimpleKeypadInput, {
|
|
19946
|
+
ref: inputRef,
|
|
19947
|
+
value: props.currentValue,
|
|
19948
|
+
keypadElement: props.keypadElement,
|
|
19949
|
+
onChange: handleChange,
|
|
19950
|
+
onFocus: handleFocus,
|
|
19951
|
+
onBlur: handleBlur
|
|
19952
|
+
}));
|
|
19953
|
+
}
|
|
19954
|
+
// (desktop-only) Otherwise, use the InputWithExamples component
|
|
19955
|
+
return /*#__PURE__*/React.createElement(InputWithExamples, {
|
|
19956
|
+
ref: inputRef,
|
|
19957
|
+
value: props.currentValue,
|
|
19958
|
+
onChange: handleChange,
|
|
19959
|
+
labelText: props.labelText,
|
|
19960
|
+
examples: generateExamples(props.answerForms, context.strings),
|
|
19961
|
+
shouldShowExamples: shouldShowExamples(props.answerForms),
|
|
19962
|
+
onFocus: handleFocus,
|
|
19963
|
+
onBlur: handleBlur,
|
|
19964
|
+
id: props.widgetId,
|
|
19965
|
+
disabled: props.apiOptions.readOnly,
|
|
19966
|
+
style: styles.inputWithExamples
|
|
19967
|
+
});
|
|
19968
|
+
});
|
|
19969
|
+
|
|
19970
|
+
const _excluded$3 = ["answers"];
|
|
19971
|
+
|
|
19972
|
+
/**
|
|
19973
|
+
* The NumericInput widget is a numeric input field that supports a variety of
|
|
19974
|
+
* answer forms, including integers, decimals, fractions, and mixed numbers.
|
|
19975
|
+
*
|
|
19976
|
+
* [Jan 2025] We're currenly migrating from class-based components to functional
|
|
19977
|
+
* components. While we cannot fully migrate this component yet, we can start
|
|
19978
|
+
* by using the functional component for the rendering the UI of the widget.
|
|
19979
|
+
*/
|
|
20163
19980
|
class NumericInput extends React.Component {
|
|
20164
19981
|
constructor(...args) {
|
|
20165
19982
|
super(...args);
|
|
20166
|
-
this.inputRef =
|
|
20167
|
-
this.state = {
|
|
20168
|
-
// keeps track of the other set of values when switching
|
|
20169
|
-
// between 0 and finite solutions
|
|
20170
|
-
previousValues: [""],
|
|
20171
|
-
isFocused: false
|
|
20172
|
-
};
|
|
20173
|
-
this.shouldShowExamples = () => {
|
|
20174
|
-
var _this$props$answerFor, _this$props$answerFor2;
|
|
20175
|
-
const noFormsAccepted = ((_this$props$answerFor = this.props.answerForms) == null ? void 0 : _this$props$answerFor.length) === 0;
|
|
20176
|
-
// To check if all answer forms are accepted, we must first
|
|
20177
|
-
// find the *names* of all accepted forms, and see if they are
|
|
20178
|
-
// all present, ignoring duplicates
|
|
20179
|
-
const answerFormNames = _.uniq((_this$props$answerFor2 = this.props.answerForms) == null ? void 0 : _this$props$answerFor2.map(form => form.name));
|
|
20180
|
-
const allFormsAccepted = answerFormNames.length >= Object.keys(formExamples).length;
|
|
20181
|
-
return !noFormsAccepted && !allFormsAccepted;
|
|
20182
|
-
};
|
|
19983
|
+
this.inputRef = /*#__PURE__*/React.createRef();
|
|
20183
19984
|
this.focus = () => {
|
|
20184
|
-
var _this$inputRef;
|
|
20185
|
-
(_this$inputRef = this.inputRef) == null || _this$inputRef.focus();
|
|
19985
|
+
var _this$inputRef$curren;
|
|
19986
|
+
(_this$inputRef$curren = this.inputRef.current) == null || _this$inputRef$curren.focus();
|
|
20186
19987
|
return true;
|
|
20187
19988
|
};
|
|
20188
19989
|
this.focusInputPath = () => {
|
|
20189
|
-
var _this$
|
|
20190
|
-
(_this$
|
|
19990
|
+
var _this$inputRef$curren2;
|
|
19991
|
+
(_this$inputRef$curren2 = this.inputRef.current) == null || _this$inputRef$curren2.focus();
|
|
20191
19992
|
};
|
|
20192
19993
|
this.blurInputPath = () => {
|
|
20193
|
-
var _this$
|
|
20194
|
-
(_this$
|
|
19994
|
+
var _this$inputRef$curren3;
|
|
19995
|
+
(_this$inputRef$curren3 = this.inputRef.current) == null || _this$inputRef$curren3.blur();
|
|
20195
19996
|
};
|
|
20196
19997
|
this.getInputPaths = () => {
|
|
20197
19998
|
// The widget itself is an input, so we return a single empty list to
|
|
@@ -20199,34 +20000,14 @@ class NumericInput extends React.Component {
|
|
|
20199
20000
|
/* c8 ignore next */
|
|
20200
20001
|
return [[]];
|
|
20201
20002
|
};
|
|
20003
|
+
/**
|
|
20004
|
+
* Sets the value of the input at the given path.
|
|
20005
|
+
*/
|
|
20202
20006
|
this.setInputValue = (path, newValue, cb) => {
|
|
20203
|
-
/* c8 ignore next */
|
|
20204
20007
|
this.props.onChange({
|
|
20205
20008
|
currentValue: newValue
|
|
20206
20009
|
}, cb);
|
|
20207
20010
|
};
|
|
20208
|
-
this.handleChange = (newValue, cb) => {
|
|
20209
|
-
this.props.onChange({
|
|
20210
|
-
currentValue: newValue
|
|
20211
|
-
}, cb);
|
|
20212
|
-
this.props.trackInteraction();
|
|
20213
|
-
};
|
|
20214
|
-
this._handleFocus = () => {
|
|
20215
|
-
this.props.onFocus([]);
|
|
20216
|
-
this.setState(currentState => {
|
|
20217
|
-
return _extends({}, currentState, {
|
|
20218
|
-
isFocused: true
|
|
20219
|
-
});
|
|
20220
|
-
});
|
|
20221
|
-
};
|
|
20222
|
-
this._handleBlur = () => {
|
|
20223
|
-
this.props.onBlur([]);
|
|
20224
|
-
this.setState(currentState => {
|
|
20225
|
-
return _extends({}, currentState, {
|
|
20226
|
-
isFocused: false
|
|
20227
|
-
});
|
|
20228
|
-
});
|
|
20229
|
-
};
|
|
20230
20011
|
}
|
|
20231
20012
|
static getUserInputFromProps(props) {
|
|
20232
20013
|
return {
|
|
@@ -20234,97 +20015,26 @@ class NumericInput extends React.Component {
|
|
|
20234
20015
|
};
|
|
20235
20016
|
}
|
|
20236
20017
|
/**
|
|
20237
|
-
*
|
|
20238
|
-
* answer forms.
|
|
20018
|
+
* Returns the value the user has currently input for this widget.
|
|
20239
20019
|
*/
|
|
20240
|
-
examples() {
|
|
20241
|
-
var _this$props$answerFor3;
|
|
20242
|
-
// if the set of specified forms are empty, allow all forms
|
|
20243
|
-
const forms = ((_this$props$answerFor3 = this.props.answerForms) == null ? void 0 : _this$props$answerFor3.length) !== 0 ? this.props.answerForms : Object.keys(formExamples).map(name => {
|
|
20244
|
-
return {
|
|
20245
|
-
name: name,
|
|
20246
|
-
simplify: "required"
|
|
20247
|
-
};
|
|
20248
|
-
});
|
|
20249
|
-
let examples = _.map(forms, form => {
|
|
20250
|
-
return formExamples[form.name](form, this.context.strings);
|
|
20251
|
-
});
|
|
20252
|
-
// Ensure no duplicate tooltip text from simplified and unsimplified
|
|
20253
|
-
// versions of the same format
|
|
20254
|
-
examples = _.uniq(examples);
|
|
20255
|
-
return [this.context.strings.yourAnswer].concat(examples);
|
|
20256
|
-
}
|
|
20257
20020
|
getUserInput() {
|
|
20258
20021
|
return NumericInput.getUserInputFromProps(this.props);
|
|
20259
20022
|
}
|
|
20023
|
+
|
|
20024
|
+
/**
|
|
20025
|
+
* Returns the JSON representation of the prompt for this widget.
|
|
20026
|
+
* This is used by the AI to determine the prompt for the widget.
|
|
20027
|
+
*/
|
|
20260
20028
|
getPromptJSON() {
|
|
20261
20029
|
return getPromptJSON$r(this.props, this.getUserInput());
|
|
20262
20030
|
}
|
|
20263
20031
|
render() {
|
|
20264
|
-
|
|
20265
|
-
|
|
20266
|
-
|
|
20267
|
-
}
|
|
20268
|
-
|
|
20269
|
-
// To right align a custom keypad we need to wrap it.
|
|
20270
|
-
const maybeRightAlignKeypadInput = keypadInput => {
|
|
20271
|
-
return this.props.rightAlign ? /*#__PURE__*/React.createElement("div", {
|
|
20272
|
-
className: "perseus-input-right-align"
|
|
20273
|
-
}, keypadInput) : keypadInput;
|
|
20274
|
-
};
|
|
20275
|
-
if (this.props.apiOptions.customKeypad) {
|
|
20276
|
-
// TODO(charlie): Support "Review Mode".
|
|
20277
|
-
return maybeRightAlignKeypadInput( /*#__PURE__*/React.createElement(SimpleKeypadInput, {
|
|
20278
|
-
ref: ref => this.inputRef = ref,
|
|
20279
|
-
value: this.props.currentValue,
|
|
20280
|
-
keypadElement: this.props.keypadElement,
|
|
20281
|
-
onChange: this.handleChange,
|
|
20282
|
-
onFocus: this._handleFocus,
|
|
20283
|
-
onBlur: this._handleBlur
|
|
20284
|
-
}));
|
|
20285
|
-
}
|
|
20286
|
-
|
|
20287
|
-
// Note: This is _very_ similar to what `input-number.jsx` does. If
|
|
20288
|
-
// you modify this, double-check if you also need to modify that
|
|
20289
|
-
// component.
|
|
20290
|
-
const styles = StyleSheet.create({
|
|
20291
|
-
input: {
|
|
20292
|
-
borderRadius: "3px",
|
|
20293
|
-
borderWidth: this.state.isFocused ? "2px" : "1px",
|
|
20294
|
-
display: "inline-block",
|
|
20295
|
-
fontFamily: `Symbola, "Times New Roman", serif`,
|
|
20296
|
-
fontSize: "18px",
|
|
20297
|
-
height: "32px",
|
|
20298
|
-
lineHeight: "18px",
|
|
20299
|
-
padding: this.state.isFocused ? "4px" : "4px 5px",
|
|
20300
|
-
// account for added focus border thickness
|
|
20301
|
-
textAlign: this.props.rightAlign ? "right" : "left",
|
|
20302
|
-
width: this.props.size === "small" ? 40 : 80
|
|
20303
|
-
}
|
|
20304
|
-
});
|
|
20305
|
-
return /*#__PURE__*/React.createElement(InputWithExamples, {
|
|
20306
|
-
ref: ref => this.inputRef = ref,
|
|
20307
|
-
value: this.props.currentValue,
|
|
20308
|
-
onChange: this.handleChange,
|
|
20309
|
-
labelText: labelText,
|
|
20310
|
-
examples: this.examples(),
|
|
20311
|
-
shouldShowExamples: this.shouldShowExamples(),
|
|
20312
|
-
onFocus: this._handleFocus,
|
|
20313
|
-
onBlur: this._handleBlur,
|
|
20314
|
-
id: this.props.widgetId,
|
|
20315
|
-
disabled: this.props.apiOptions.readOnly,
|
|
20316
|
-
style: styles.input
|
|
20317
|
-
});
|
|
20032
|
+
return /*#__PURE__*/React.createElement(NumericInputComponent, _extends({}, this.props, {
|
|
20033
|
+
ref: this.inputRef
|
|
20034
|
+
}));
|
|
20318
20035
|
}
|
|
20319
20036
|
}
|
|
20320
|
-
|
|
20321
|
-
// TODO(thomas): Currently we receive a list of lists of acceptable answer types
|
|
20322
|
-
// and union them down into a single set. It's worth considering whether it
|
|
20323
|
-
// wouldn't make more sense to have a single set of acceptable answer types for
|
|
20324
|
-
// a given *problem* rather than for each possible [correct/wrong] *answer*.
|
|
20325
|
-
// When should two answers to a problem take different answer types?
|
|
20326
|
-
// See D27790 for more discussion.
|
|
20327
|
-
NumericInput.contextType = PerseusI18nContext;
|
|
20037
|
+
// Transforms the widget options to the props used to render the widget.
|
|
20328
20038
|
NumericInput.defaultProps = {
|
|
20329
20039
|
currentValue: "",
|
|
20330
20040
|
size: "normal",
|
|
@@ -20335,52 +20045,23 @@ NumericInput.defaultProps = {
|
|
|
20335
20045
|
labelText: "",
|
|
20336
20046
|
linterContext: linterContextDefault
|
|
20337
20047
|
};
|
|
20338
|
-
const unionAnswerForms = function unionAnswerForms(answerFormsList) {
|
|
20339
|
-
// Takes a list of lists of answer forms, and returns a list of the forms
|
|
20340
|
-
// in each of these lists in the same order that they're listed in the
|
|
20341
|
-
// `formExamples` forms from above.
|
|
20342
|
-
|
|
20343
|
-
// uniqueBy takes a list of elements and a function which compares whether
|
|
20344
|
-
// two elements are equal, and returns a list of unique elements. This is
|
|
20345
|
-
// just a helper function here, but works generally.
|
|
20346
|
-
const uniqueBy = function uniqueBy(list, iteratee) {
|
|
20347
|
-
// @ts-expect-error - TS2347 - Untyped function calls may not accept type arguments.
|
|
20348
|
-
return list.reduce((uniqueList, element) => {
|
|
20349
|
-
// For each element, decide whether it's already in the list of
|
|
20350
|
-
// unique items.
|
|
20351
|
-
const inList = _.find(uniqueList, iteratee.bind(null, element));
|
|
20352
|
-
if (inList) {
|
|
20353
|
-
return uniqueList;
|
|
20354
|
-
}
|
|
20355
|
-
return uniqueList.concat([element]);
|
|
20356
|
-
}, []);
|
|
20357
|
-
};
|
|
20358
|
-
|
|
20359
|
-
// Pull out all of the forms from the different lists.
|
|
20360
|
-
const allForms = answerFormsList.flat();
|
|
20361
|
-
// Pull out the unique forms using uniqueBy.
|
|
20362
|
-
const uniqueForms = uniqueBy(allForms, _.isEqual);
|
|
20363
|
-
// Sort them by the order they appear in the `formExamples` list.
|
|
20364
|
-
const formExampleKeys = Object.keys(formExamples);
|
|
20365
|
-
return _.sortBy(uniqueForms, form => {
|
|
20366
|
-
return formExampleKeys.indexOf(form.name);
|
|
20367
|
-
});
|
|
20368
|
-
};
|
|
20369
20048
|
const propsTransform = function propsTransform(widgetOptions) {
|
|
20370
|
-
|
|
20371
|
-
|
|
20372
|
-
|
|
20373
|
-
|
|
20374
|
-
|
|
20375
|
-
//
|
|
20376
|
-
|
|
20377
|
-
return {
|
|
20378
|
-
|
|
20379
|
-
|
|
20380
|
-
|
|
20381
|
-
|
|
20382
|
-
|
|
20383
|
-
|
|
20049
|
+
// Omit the answers from the widget options since they are
|
|
20050
|
+
// not needed for rendering the widget.
|
|
20051
|
+
const _widgetOptions$answer = _extends({}, widgetOptions, {
|
|
20052
|
+
answerForms: unionAnswerForms(
|
|
20053
|
+
// Filter out the correct answers and map them to the answer forms
|
|
20054
|
+
// so that we can generate the examples for the widget.
|
|
20055
|
+
widgetOptions.answers.filter(answer => answer.status === "correct").map(answer => {
|
|
20056
|
+
return (answer.answerForms || []).map(form => {
|
|
20057
|
+
return {
|
|
20058
|
+
simplify: answer.simplify,
|
|
20059
|
+
name: form
|
|
20060
|
+
};
|
|
20061
|
+
});
|
|
20062
|
+
}))
|
|
20063
|
+
}),
|
|
20064
|
+
rendererProps = _objectWithoutPropertiesLoose(_widgetOptions$answer, _excluded$3);
|
|
20384
20065
|
return rendererProps;
|
|
20385
20066
|
};
|
|
20386
20067
|
var NumericInput$1 = {
|
|
@@ -20395,23 +20076,11 @@ var NumericInput$1 = {
|
|
|
20395
20076
|
getOneCorrectAnswerFromRubric(rubric) {
|
|
20396
20077
|
const correctAnswers = rubric.answers.filter(answer => answer.status === "correct");
|
|
20397
20078
|
const answerStrings = correctAnswers.map(answer => {
|
|
20398
|
-
//
|
|
20399
|
-
|
|
20400
|
-
let format = "decimal";
|
|
20401
|
-
if (answer.answerForms && answer.answerForms[0]) {
|
|
20402
|
-
// NOTE(johnsullivan): This isn't exactly ideal, but
|
|
20403
|
-
// it does behave well for all the currently known
|
|
20404
|
-
// problems. See D14742 for some discussion on
|
|
20405
|
-
// alternate strategies.
|
|
20406
|
-
format = answer.answerForms[0];
|
|
20407
|
-
}
|
|
20408
|
-
|
|
20409
|
-
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
|
|
20079
|
+
// Either get the first answer form or default to decimal
|
|
20080
|
+
const format = answer.answerForms && answer.answerForms[0] ? answer.answerForms[0] : "decimal";
|
|
20410
20081
|
let answerString = KhanMath.toNumericString(answer.value, format);
|
|
20411
20082
|
if (answer.maxError) {
|
|
20412
|
-
answerString += " \u00B1 " +
|
|
20413
|
-
// @ts-expect-error - TS2345 - Argument of type 'string' is not assignable to parameter of type 'MathFormat | undefined'.
|
|
20414
|
-
KhanMath.toNumericString(answer.maxError, format);
|
|
20083
|
+
answerString += " \u00B1 " + KhanMath.toNumericString(answer.maxError, format);
|
|
20415
20084
|
}
|
|
20416
20085
|
return answerString;
|
|
20417
20086
|
});
|
|
@@ -21215,7 +20884,7 @@ class HighlightTooltip extends React.PureComponent {
|
|
|
21215
20884
|
style: style,
|
|
21216
20885
|
onMouseEnter: this.props.onMouseEnter,
|
|
21217
20886
|
onMouseLeave: this.props.onMouseLeave
|
|
21218
|
-
}, /*#__PURE__*/React.createElement(Tooltip
|
|
20887
|
+
}, /*#__PURE__*/React.createElement(Tooltip, {
|
|
21219
20888
|
content: content,
|
|
21220
20889
|
opened: true
|
|
21221
20890
|
}, /*#__PURE__*/React.createElement("div", null)))
|
|
@@ -22938,6 +22607,7 @@ const BASE_ICON_SIZE = 10;
|
|
|
22938
22607
|
// If you pass in an object you need to specify an SVG path to
|
|
22939
22608
|
// render along with the width and height of the path (to ensure
|
|
22940
22609
|
// that it gets scaled correctly).
|
|
22610
|
+
|
|
22941
22611
|
/**
|
|
22942
22612
|
* An SVG Icon
|
|
22943
22613
|
*
|
|
@@ -24023,7 +23693,6 @@ const styles$o = StyleSheet.create({
|
|
|
24023
23693
|
paddingLeft: phoneMargin
|
|
24024
23694
|
// paddingRight is handled by responsiveFieldset
|
|
24025
23695
|
},
|
|
24026
|
-
|
|
24027
23696
|
responsiveFieldset: {
|
|
24028
23697
|
paddingRight: phoneMargin,
|
|
24029
23698
|
minWidth: "auto"
|
|
@@ -24369,11 +24038,6 @@ Radio$1.defaultProps = {
|
|
|
24369
24038
|
showSolutions: "none"
|
|
24370
24039
|
};
|
|
24371
24040
|
|
|
24372
|
-
const {
|
|
24373
|
-
shuffle: shuffle$2,
|
|
24374
|
-
random
|
|
24375
|
-
} = Util;
|
|
24376
|
-
|
|
24377
24041
|
// Transforms the choices for display.
|
|
24378
24042
|
const _choiceTransform = (widgetOptions, strings, problemNum) => {
|
|
24379
24043
|
const _maybeRandomize = function _maybeRandomize(array) {
|
|
@@ -24384,7 +24048,7 @@ const _choiceTransform = (widgetOptions, strings, problemNum) => {
|
|
|
24384
24048
|
// or if rendered within an article. This results in a predictable
|
|
24385
24049
|
// shuffle order. To avoid this we use a random seed when `problemNum`
|
|
24386
24050
|
// is `undefined`.
|
|
24387
|
-
return widgetOptions.randomize ? shuffle
|
|
24051
|
+
return widgetOptions.randomize ? shuffle(array, randomSeed != null ? randomSeed : 0) : array;
|
|
24388
24052
|
};
|
|
24389
24053
|
const _addNoneOfAbove = function _addNoneOfAbove(choices) {
|
|
24390
24054
|
let noneOfTheAbove = null;
|
|
@@ -24434,7 +24098,7 @@ const _choiceTransform = (widgetOptions, strings, problemNum) => {
|
|
|
24434
24098
|
// 1) we randomize the order first
|
|
24435
24099
|
_maybeRandomize(choices)));
|
|
24436
24100
|
};
|
|
24437
|
-
const transform = (widgetOptions, strings, problemNum) => {
|
|
24101
|
+
const transform$1 = (widgetOptions, strings, problemNum) => {
|
|
24438
24102
|
const choices = _choiceTransform(widgetOptions, strings, problemNum);
|
|
24439
24103
|
const numCorrect = _.reduce(widgetOptions.choices, function (memo, choice) {
|
|
24440
24104
|
return choice.correct ? memo + 1 : memo;
|
|
@@ -24460,8 +24124,8 @@ var Radio = {
|
|
|
24460
24124
|
displayName: "Radio / Multiple choice",
|
|
24461
24125
|
accessible: true,
|
|
24462
24126
|
widget: Radio$1,
|
|
24463
|
-
transform: transform,
|
|
24464
|
-
staticTransform: transform,
|
|
24127
|
+
transform: transform$1,
|
|
24128
|
+
staticTransform: transform$1,
|
|
24465
24129
|
version: radioLogic.version,
|
|
24466
24130
|
propUpgrades: radioLogic.widgetOptionsUpgrades,
|
|
24467
24131
|
isLintable: true
|
|
@@ -24614,8 +24278,7 @@ class Categorizer extends React.Component {
|
|
|
24614
24278
|
const isMobile = this.props.apiOptions.isMobile;
|
|
24615
24279
|
let indexedItems = this.props.items.map((item, n) => [item, n]);
|
|
24616
24280
|
if (this.props.randomizeItems) {
|
|
24617
|
-
|
|
24618
|
-
indexedItems = Util.shuffle(indexedItems, this.props.problemNum);
|
|
24281
|
+
indexedItems = shuffle(indexedItems, this.props.problemNum);
|
|
24619
24282
|
}
|
|
24620
24283
|
const table = /*#__PURE__*/React.createElement("table", {
|
|
24621
24284
|
className: "categorizer-table " + css(styles$n.mobileTable)
|
|
@@ -24641,9 +24304,7 @@ class Categorizer extends React.Component {
|
|
|
24641
24304
|
const uniqueId = self.state.uniqueId + "_" + itemNum;
|
|
24642
24305
|
return /*#__PURE__*/React.createElement("tr", {
|
|
24643
24306
|
key: itemNum
|
|
24644
|
-
}, /*#__PURE__*/React.createElement("td", null, /*#__PURE__*/React.createElement(Renderer
|
|
24645
|
-
// @ts-expect-error - TS2322 - Type 'string | number' is not assignable to type 'string | undefined'.
|
|
24646
|
-
, {
|
|
24307
|
+
}, /*#__PURE__*/React.createElement("td", null, /*#__PURE__*/React.createElement(Renderer, {
|
|
24647
24308
|
content: item,
|
|
24648
24309
|
linterContext: this.props.linterContext,
|
|
24649
24310
|
strings: this.context.strings
|
|
@@ -25035,6 +24696,7 @@ var Definition$1 = {
|
|
|
25035
24696
|
|
|
25036
24697
|
// There are no required props, but this component can receive props from any
|
|
25037
24698
|
// deprecated widget
|
|
24699
|
+
|
|
25038
24700
|
class DeprecatedStandin extends React__default.Component {
|
|
25039
24701
|
constructor(...args) {
|
|
25040
24702
|
super(...args);
|
|
@@ -25161,20 +24823,20 @@ Dropdown.defaultProps = {
|
|
|
25161
24823
|
placeholder: "",
|
|
25162
24824
|
apiOptions: ApiOptions.defaults
|
|
25163
24825
|
};
|
|
25164
|
-
|
|
24826
|
+
function transform(widgetOptions) {
|
|
25165
24827
|
return {
|
|
25166
24828
|
placeholder: widgetOptions.placeholder,
|
|
25167
24829
|
visibleLabel: widgetOptions.visibleLabel,
|
|
25168
24830
|
ariaLabel: widgetOptions.ariaLabel,
|
|
25169
24831
|
choices: widgetOptions.choices.map(choice => choice.content)
|
|
25170
24832
|
};
|
|
25171
|
-
}
|
|
24833
|
+
}
|
|
25172
24834
|
var Dropdown$1 = {
|
|
25173
24835
|
name: "dropdown",
|
|
25174
24836
|
displayName: "Drop down",
|
|
25175
24837
|
accessible: true,
|
|
25176
24838
|
widget: Dropdown,
|
|
25177
|
-
transform
|
|
24839
|
+
transform
|
|
25178
24840
|
};
|
|
25179
24841
|
|
|
25180
24842
|
const getPromptJSON$j = renderProps => {
|
|
@@ -25238,7 +24900,6 @@ class Explanation extends React.Component {
|
|
|
25238
24900
|
// adds space between the right border and any text that follows
|
|
25239
24901
|
paddingLeft: "2px" // adds space between the left border and the button text
|
|
25240
24902
|
};
|
|
25241
|
-
|
|
25242
24903
|
const labelStyle = {
|
|
25243
24904
|
fontSize: "18px",
|
|
25244
24905
|
lineHeight: "inherit",
|
|
@@ -27562,7 +27223,6 @@ class Interaction extends React.Component {
|
|
|
27562
27223
|
}
|
|
27563
27224
|
});
|
|
27564
27225
|
}
|
|
27565
|
-
|
|
27566
27226
|
if (element.type === "parametric") {
|
|
27567
27227
|
const fn = t => {
|
|
27568
27228
|
return [
|
|
@@ -27615,7 +27275,6 @@ class Interaction extends React.Component {
|
|
|
27615
27275
|
}
|
|
27616
27276
|
});
|
|
27617
27277
|
}
|
|
27618
|
-
|
|
27619
27278
|
if (element.type === "label") {
|
|
27620
27279
|
const coord = [
|
|
27621
27280
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
@@ -27960,7 +27619,6 @@ function scaleCommandBy(scaleFactor) {
|
|
|
27960
27619
|
command.args[6] * scaleFactor // end y
|
|
27961
27620
|
]
|
|
27962
27621
|
});
|
|
27963
|
-
|
|
27964
27622
|
default:
|
|
27965
27623
|
return _extends({}, command, {
|
|
27966
27624
|
args: command.args.map(c => c * scaleFactor)
|
|
@@ -28702,7 +28360,6 @@ const styles$f = StyleSheet.create({
|
|
|
28702
28360
|
userSelect: "none",
|
|
28703
28361
|
borderWidth: 0 // <button> gives us a border by default
|
|
28704
28362
|
},
|
|
28705
|
-
|
|
28706
28363
|
// We want to render the element in-place in our fixtures, so allow us to
|
|
28707
28364
|
// render in the bottom corner in production and in-place in testing.
|
|
28708
28365
|
hudFixedPosition: {
|
|
@@ -28732,7 +28389,6 @@ const styles$f = StyleSheet.create({
|
|
|
28732
28389
|
":hover": {
|
|
28733
28390
|
backgroundColor: "#a1a5a9" // in between those two grays
|
|
28734
28391
|
},
|
|
28735
|
-
|
|
28736
28392
|
":active": {
|
|
28737
28393
|
backgroundColor: gray68
|
|
28738
28394
|
}
|
|
@@ -28740,7 +28396,7 @@ const styles$f = StyleSheet.create({
|
|
|
28740
28396
|
});
|
|
28741
28397
|
|
|
28742
28398
|
const InfoTipBase = props => {
|
|
28743
|
-
return /*#__PURE__*/React.createElement(Tooltip
|
|
28399
|
+
return /*#__PURE__*/React.createElement(Tooltip, {
|
|
28744
28400
|
content: /*#__PURE__*/React.createElement(Body, {
|
|
28745
28401
|
style: styles$e.body
|
|
28746
28402
|
}, props.children),
|
|
@@ -28780,7 +28436,6 @@ class InfoTip extends React.Component {
|
|
|
28780
28436
|
});
|
|
28781
28437
|
/* eslint-enable react/no-did-mount-set-state */
|
|
28782
28438
|
}
|
|
28783
|
-
|
|
28784
28439
|
render() {
|
|
28785
28440
|
if (this.state.didMount) {
|
|
28786
28441
|
return /*#__PURE__*/React.createElement(InfoTipBase, this.props);
|
|
@@ -29012,7 +28667,6 @@ class NumberInput extends React.Component {
|
|
|
29012
28667
|
if (val !== Math.floor(val)) {
|
|
29013
28668
|
return; // bail if not an integer
|
|
29014
28669
|
}
|
|
29015
|
-
|
|
29016
28670
|
if (e.key === "ArrowUp") {
|
|
29017
28671
|
val = val + 1;
|
|
29018
28672
|
} else if (e.key === "ArrowDown") {
|
|
@@ -29146,20 +28800,20 @@ RangeInput.defaultProps = {
|
|
|
29146
28800
|
};
|
|
29147
28801
|
|
|
29148
28802
|
var components = /*#__PURE__*/Object.freeze({
|
|
29149
|
-
|
|
29150
|
-
|
|
29151
|
-
|
|
29152
|
-
|
|
29153
|
-
|
|
29154
|
-
|
|
29155
|
-
|
|
29156
|
-
|
|
29157
|
-
|
|
29158
|
-
|
|
29159
|
-
|
|
29160
|
-
|
|
29161
|
-
|
|
29162
|
-
|
|
28803
|
+
__proto__: null,
|
|
28804
|
+
ButtonGroup: ButtonGroup,
|
|
28805
|
+
Graphie: Graphie,
|
|
28806
|
+
HUD: HUD,
|
|
28807
|
+
Icon: Icon,
|
|
28808
|
+
InfoTip: InfoTip,
|
|
28809
|
+
InlineIcon: InlineIcon,
|
|
28810
|
+
MathInput: MathInput,
|
|
28811
|
+
MultiButtonGroup: MultiButtonGroup,
|
|
28812
|
+
NumberInput: NumberInput,
|
|
28813
|
+
RangeInput: RangeInput,
|
|
28814
|
+
SvgImage: SvgImage,
|
|
28815
|
+
TextInput: TextInput,
|
|
28816
|
+
TextListEditor: TextListEditor
|
|
29163
28817
|
});
|
|
29164
28818
|
|
|
29165
28819
|
/**
|
|
@@ -29288,9 +28942,9 @@ const LockedFunction = props => {
|
|
|
29288
28942
|
const {
|
|
29289
28943
|
color,
|
|
29290
28944
|
strokeStyle,
|
|
29291
|
-
directionalAxis
|
|
28945
|
+
directionalAxis,
|
|
28946
|
+
domain
|
|
29292
28947
|
} = props;
|
|
29293
|
-
const domain = props.domain ? [Number.isFinite(props.domain[0]) ? props.domain[0] : -Infinity, Number.isFinite(props.domain[1]) ? props.domain[1] : Infinity] : undefined;
|
|
29294
28948
|
const plotProps = {
|
|
29295
28949
|
color: lockedFigureColors[color],
|
|
29296
28950
|
style: strokeStyle,
|
|
@@ -30702,7 +30356,7 @@ const MovablePointView = /*#__PURE__*/forwardRef((props, hitboxRef) => {
|
|
|
30702
30356
|
}));
|
|
30703
30357
|
return /*#__PURE__*/React.createElement(React.Fragment, null, showHairlines && /*#__PURE__*/React.createElement(Hairlines, {
|
|
30704
30358
|
point: point
|
|
30705
|
-
}), showTooltips ? /*#__PURE__*/React.createElement(Tooltip
|
|
30359
|
+
}), showTooltips ? /*#__PURE__*/React.createElement(Tooltip, {
|
|
30706
30360
|
autoUpdate: true,
|
|
30707
30361
|
backgroundColor: wbColorName,
|
|
30708
30362
|
content: `(${point[X]}, ${point[Y]})`,
|
|
@@ -30835,6 +30489,9 @@ const MovableLine = props => {
|
|
|
30835
30489
|
onMoveLine = () => {},
|
|
30836
30490
|
onMovePoint = () => {}
|
|
30837
30491
|
} = props;
|
|
30492
|
+
const {
|
|
30493
|
+
snapStep
|
|
30494
|
+
} = useGraphConfig();
|
|
30838
30495
|
|
|
30839
30496
|
// Aria live states for (0) point 1, (1) point 2, and (2) grab handle.
|
|
30840
30497
|
// When moving an element, set its aria live to "polite" and the others
|
|
@@ -30866,7 +30523,8 @@ const MovableLine = props => {
|
|
|
30866
30523
|
onMove: p => {
|
|
30867
30524
|
setAriaLives(["polite", "off", "off"]);
|
|
30868
30525
|
onMovePoint(0, p);
|
|
30869
|
-
}
|
|
30526
|
+
},
|
|
30527
|
+
constrain: getMovableLineKeyboardConstraint([start, end], snapStep, 0)
|
|
30870
30528
|
});
|
|
30871
30529
|
const {
|
|
30872
30530
|
visiblePoint: visiblePoint2,
|
|
@@ -30881,7 +30539,8 @@ const MovableLine = props => {
|
|
|
30881
30539
|
onMove: p => {
|
|
30882
30540
|
setAriaLives(["off", "polite", "off"]);
|
|
30883
30541
|
onMovePoint(1, p);
|
|
30884
|
-
}
|
|
30542
|
+
},
|
|
30543
|
+
constrain: getMovableLineKeyboardConstraint([start, end], snapStep, 1)
|
|
30885
30544
|
});
|
|
30886
30545
|
const line = /*#__PURE__*/React.createElement(Line$1, {
|
|
30887
30546
|
ariaLabel: ariaLabels == null ? void 0 : ariaLabels.grabHandleAriaLabel,
|
|
@@ -30987,6 +30646,34 @@ const Line$1 = props => {
|
|
|
30987
30646
|
color: stroke
|
|
30988
30647
|
}));
|
|
30989
30648
|
};
|
|
30649
|
+
const getMovableLineKeyboardConstraint = (line, snapStep, pointIndex) => {
|
|
30650
|
+
// Separate the two points into their own variables, and determine which point is being moved
|
|
30651
|
+
const coordToBeMoved = line[pointIndex];
|
|
30652
|
+
const otherPoint = line[1 - pointIndex];
|
|
30653
|
+
|
|
30654
|
+
// Create a helper function that moves the point and then checks
|
|
30655
|
+
// if it overlaps with the other point in the line after the move.
|
|
30656
|
+
const movePointWithConstraint = moveFunc => {
|
|
30657
|
+
// Move the point
|
|
30658
|
+
let movedCoord = moveFunc(coordToBeMoved);
|
|
30659
|
+
// If the moved point overlaps with the other point in the line,
|
|
30660
|
+
// move the point again.
|
|
30661
|
+
if (vec.dist(movedCoord, otherPoint) === 0) {
|
|
30662
|
+
movedCoord = moveFunc(movedCoord);
|
|
30663
|
+
}
|
|
30664
|
+
return movedCoord;
|
|
30665
|
+
};
|
|
30666
|
+
|
|
30667
|
+
// Check if the new point will overlap the other point.
|
|
30668
|
+
// If it will, we need to snap the point to the left or right an additional
|
|
30669
|
+
// snapStep to avoid overlap.
|
|
30670
|
+
return {
|
|
30671
|
+
up: movePointWithConstraint(coord => vec.add(coord, [0, snapStep[1]])),
|
|
30672
|
+
down: movePointWithConstraint(coord => vec.sub(coord, [0, snapStep[1]])),
|
|
30673
|
+
left: movePointWithConstraint(coord => vec.sub(coord, [snapStep[0], 0])),
|
|
30674
|
+
right: movePointWithConstraint(coord => vec.add(coord, [snapStep[0], 0]))
|
|
30675
|
+
};
|
|
30676
|
+
};
|
|
30990
30677
|
function trimRange(range, graphDimensionsInPixels) {
|
|
30991
30678
|
const pixelsToTrim = 4;
|
|
30992
30679
|
const [xRange, yRange] = range;
|
|
@@ -31328,7 +31015,8 @@ function CircleGraph(props) {
|
|
|
31328
31015
|
} = props;
|
|
31329
31016
|
const {
|
|
31330
31017
|
center,
|
|
31331
|
-
radiusPoint
|
|
31018
|
+
radiusPoint,
|
|
31019
|
+
snapStep
|
|
31332
31020
|
} = graphState;
|
|
31333
31021
|
const {
|
|
31334
31022
|
strings,
|
|
@@ -31391,7 +31079,8 @@ function CircleGraph(props) {
|
|
|
31391
31079
|
onMove: newRadiusPoint => {
|
|
31392
31080
|
setRadiusPointAriaLive("polite");
|
|
31393
31081
|
dispatch(actions.circle.moveRadiusPoint(newRadiusPoint));
|
|
31394
|
-
}
|
|
31082
|
+
},
|
|
31083
|
+
constrain: getCircleKeyboardConstraint(center, radiusPoint, snapStep)
|
|
31395
31084
|
}), /*#__PURE__*/React.createElement("g", {
|
|
31396
31085
|
id: radiusId,
|
|
31397
31086
|
style: a11y.srOnly
|
|
@@ -31502,6 +31191,30 @@ function getCircleGraphDescription(state, i18n) {
|
|
|
31502
31191
|
const strings = describeCircleGraph(state, i18n);
|
|
31503
31192
|
return strings.srCircleInteractiveElement;
|
|
31504
31193
|
}
|
|
31194
|
+
const getCircleKeyboardConstraint = (center, radiusPoint, snapStep) => {
|
|
31195
|
+
// Create a helper function that moves the point and then checks
|
|
31196
|
+
// if it overlaps with the center point after the move.
|
|
31197
|
+
const movePointWithConstraint = moveFunc => {
|
|
31198
|
+
// Move the point
|
|
31199
|
+
let movedCoord = moveFunc(radiusPoint);
|
|
31200
|
+
// If the moved point overlaps with the center point,
|
|
31201
|
+
// move the point again.
|
|
31202
|
+
if (vec.dist(movedCoord, center) === 0) {
|
|
31203
|
+
movedCoord = moveFunc(movedCoord);
|
|
31204
|
+
}
|
|
31205
|
+
return movedCoord;
|
|
31206
|
+
};
|
|
31207
|
+
|
|
31208
|
+
// Check if the new point overlaps the center point.
|
|
31209
|
+
// If it does, we need to snap the point to the left
|
|
31210
|
+
// or right an additional snapStep to avoid the overlap.
|
|
31211
|
+
return {
|
|
31212
|
+
up: movePointWithConstraint(coord => vec.add(coord, [0, snapStep[1]])),
|
|
31213
|
+
down: movePointWithConstraint(coord => vec.sub(coord, [0, snapStep[1]])),
|
|
31214
|
+
left: movePointWithConstraint(coord => vec.sub(coord, [snapStep[0], 0])),
|
|
31215
|
+
right: movePointWithConstraint(coord => vec.add(coord, [snapStep[0], 0]))
|
|
31216
|
+
};
|
|
31217
|
+
};
|
|
31505
31218
|
|
|
31506
31219
|
// Exported for testing
|
|
31507
31220
|
function describeCircleGraph(state, i18n) {
|
|
@@ -31514,6 +31227,7 @@ function describeCircleGraph(state, i18n) {
|
|
|
31514
31227
|
radiusPoint
|
|
31515
31228
|
} = state;
|
|
31516
31229
|
const radius = getRadius(state);
|
|
31230
|
+
const isRadiusOnRight = radiusPoint[X] >= center[X];
|
|
31517
31231
|
|
|
31518
31232
|
// Aria label strings
|
|
31519
31233
|
const srCircleGraph = strings.srCircleGraph;
|
|
@@ -31521,7 +31235,10 @@ function describeCircleGraph(state, i18n) {
|
|
|
31521
31235
|
centerX: srFormatNumber(center[0], locale),
|
|
31522
31236
|
centerY: srFormatNumber(center[1], locale)
|
|
31523
31237
|
});
|
|
31524
|
-
const srCircleRadiusPoint = strings.
|
|
31238
|
+
const srCircleRadiusPoint = isRadiusOnRight ? strings.srCircleRadiusPointRight({
|
|
31239
|
+
radiusPointX: srFormatNumber(radiusPoint[0], locale),
|
|
31240
|
+
radiusPointY: srFormatNumber(radiusPoint[1], locale)
|
|
31241
|
+
}) : strings.srCircleRadiusPointLeft({
|
|
31525
31242
|
radiusPointX: srFormatNumber(radiusPoint[0], locale),
|
|
31526
31243
|
radiusPointY: srFormatNumber(radiusPoint[1], locale)
|
|
31527
31244
|
});
|
|
@@ -31687,6 +31404,12 @@ const LinearSystemGraph = props => {
|
|
|
31687
31404
|
locale
|
|
31688
31405
|
} = usePerseusI18n();
|
|
31689
31406
|
const id = React.useId();
|
|
31407
|
+
const intersectionId = `${id}-intersection`;
|
|
31408
|
+
const intersectionPoint = geometry.getLineIntersection(lines[0], lines[1]);
|
|
31409
|
+
const intersectionDescription = intersectionPoint ? strings.srLinearSystemIntersection({
|
|
31410
|
+
x: srFormatNumber(intersectionPoint[0], locale),
|
|
31411
|
+
y: srFormatNumber(intersectionPoint[1], locale)
|
|
31412
|
+
}) : strings.srLinearSystemParallel;
|
|
31690
31413
|
const linesAriaInfo = lines.map((line, i) => {
|
|
31691
31414
|
return {
|
|
31692
31415
|
pointsDescriptionId: `${id}-line${i + 1}-points`,
|
|
@@ -31703,13 +31426,14 @@ const LinearSystemGraph = props => {
|
|
|
31703
31426
|
slopeDescription: getSlopeStringForLine(line, strings)
|
|
31704
31427
|
};
|
|
31705
31428
|
});
|
|
31429
|
+
const individualLineDescriptions = linesAriaInfo.map(({
|
|
31430
|
+
pointsDescriptionId,
|
|
31431
|
+
interceptDescriptionId,
|
|
31432
|
+
slopeDescriptionId
|
|
31433
|
+
}) => `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`).join(" ");
|
|
31706
31434
|
return /*#__PURE__*/React.createElement("g", {
|
|
31707
31435
|
"aria-label": strings.srLinearSystemGraph,
|
|
31708
|
-
"aria-describedby":
|
|
31709
|
-
pointsDescriptionId,
|
|
31710
|
-
interceptDescriptionId,
|
|
31711
|
-
slopeDescriptionId
|
|
31712
|
-
}) => `${pointsDescriptionId} ${interceptDescriptionId} ${slopeDescriptionId}`).join(" ")
|
|
31436
|
+
"aria-describedby": `${individualLineDescriptions} ${intersectionId}`
|
|
31713
31437
|
}, lines == null ? void 0 : lines.map((line, i) => /*#__PURE__*/React.createElement(MovableLine, {
|
|
31714
31438
|
key: i,
|
|
31715
31439
|
points: line,
|
|
@@ -31726,9 +31450,15 @@ const LinearSystemGraph = props => {
|
|
|
31726
31450
|
x: srFormatNumber(line[1][0], locale),
|
|
31727
31451
|
y: srFormatNumber(line[1][1], locale)
|
|
31728
31452
|
}),
|
|
31729
|
-
grabHandleAriaLabel:
|
|
31453
|
+
grabHandleAriaLabel: strings.srLinearSystemGrabHandle({
|
|
31454
|
+
lineNumber: i + 1,
|
|
31455
|
+
point1X: srFormatNumber(line[0][0], locale),
|
|
31456
|
+
point1Y: srFormatNumber(line[0][1], locale),
|
|
31457
|
+
point2X: srFormatNumber(line[1][0], locale),
|
|
31458
|
+
point2Y: srFormatNumber(line[1][1], locale)
|
|
31459
|
+
})
|
|
31730
31460
|
},
|
|
31731
|
-
ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId}`,
|
|
31461
|
+
ariaDescribedBy: `${linesAriaInfo[i].interceptDescriptionId} ${linesAriaInfo[i].slopeDescriptionId} ${intersectionId}`,
|
|
31732
31462
|
onMoveLine: delta => {
|
|
31733
31463
|
dispatch(actions.linearSystem.moveLine(i, delta));
|
|
31734
31464
|
},
|
|
@@ -31757,7 +31487,10 @@ const LinearSystemGraph = props => {
|
|
|
31757
31487
|
key: slopeDescriptionId,
|
|
31758
31488
|
id: slopeDescriptionId,
|
|
31759
31489
|
style: a11y.srOnly
|
|
31760
|
-
}, slopeDescription)))
|
|
31490
|
+
}, slopeDescription))), /*#__PURE__*/React.createElement("g", {
|
|
31491
|
+
id: intersectionId,
|
|
31492
|
+
style: a11y.srOnly
|
|
31493
|
+
}, intersectionDescription));
|
|
31761
31494
|
};
|
|
31762
31495
|
|
|
31763
31496
|
// Exported for testing
|
|
@@ -32418,7 +32151,8 @@ function QuadraticGraph(props) {
|
|
|
32418
32151
|
graphState
|
|
32419
32152
|
} = props;
|
|
32420
32153
|
const {
|
|
32421
|
-
coords
|
|
32154
|
+
coords,
|
|
32155
|
+
snapStep
|
|
32422
32156
|
} = graphState;
|
|
32423
32157
|
const {
|
|
32424
32158
|
strings,
|
|
@@ -32471,6 +32205,7 @@ function QuadraticGraph(props) {
|
|
|
32471
32205
|
ariaLabel: `${srQuadraticPoint}${srVertex}`,
|
|
32472
32206
|
point: coord,
|
|
32473
32207
|
sequenceNumber: i + 1,
|
|
32208
|
+
constrain: getQuadraticKeyboardConstraint(coords, snapStep, i),
|
|
32474
32209
|
onMove: destination => dispatch(actions.quadratic.movePoint(i, destination))
|
|
32475
32210
|
});
|
|
32476
32211
|
}), srQuadraticDirection && /*#__PURE__*/React.createElement("g", {
|
|
@@ -32559,6 +32294,60 @@ function describeQuadraticGraph(state, i18n) {
|
|
|
32559
32294
|
srQuadraticInteractiveElements
|
|
32560
32295
|
};
|
|
32561
32296
|
}
|
|
32297
|
+
const getQuadraticKeyboardConstraint = (coords, snapStep, pointMoved) => {
|
|
32298
|
+
// Make newCoords mutable
|
|
32299
|
+
const newCoords = [coords[0], coords[1], coords[2]];
|
|
32300
|
+
|
|
32301
|
+
// Get the point that is being moved
|
|
32302
|
+
const coordToBeMoved = newCoords[pointMoved];
|
|
32303
|
+
|
|
32304
|
+
// Create a function to validate and adjust the movement of the point
|
|
32305
|
+
const movePointWithConstraint = moveFunc => {
|
|
32306
|
+
// Move the point the desired direction
|
|
32307
|
+
let movedCoord = moveFunc(coordToBeMoved);
|
|
32308
|
+
newCoords[pointMoved] = movedCoord;
|
|
32309
|
+
|
|
32310
|
+
// If these new coordinates are valid, return the moved coord
|
|
32311
|
+
if (areCoordsValid(newCoords)) {
|
|
32312
|
+
return movedCoord;
|
|
32313
|
+
}
|
|
32314
|
+
|
|
32315
|
+
// If the new coordinates are invalid, we need to move the point an additional
|
|
32316
|
+
// snapStep to avoid creating an invalid quadratic graph.
|
|
32317
|
+
movedCoord = moveFunc(movedCoord);
|
|
32318
|
+
newCoords[pointMoved] = movedCoord;
|
|
32319
|
+
|
|
32320
|
+
// If these updated coordinates are valid now, we can return the new coordinates.
|
|
32321
|
+
if (areCoordsValid(newCoords)) {
|
|
32322
|
+
return movedCoord;
|
|
32323
|
+
}
|
|
32324
|
+
|
|
32325
|
+
// Otherwise, if the new coordinates are still invalid, we need to move the point one final snapStep.
|
|
32326
|
+
// This is to support the edge case where all of the points are each exactly one snapStep away
|
|
32327
|
+
// from each other.
|
|
32328
|
+
// Eg. When coords = [[0, 0], [1, 0], [2, 0]], snapStep = [1, 1], and we're moving coords[0] to the right.
|
|
32329
|
+
return moveFunc(movedCoord);
|
|
32330
|
+
};
|
|
32331
|
+
return {
|
|
32332
|
+
up: vec.add(coordToBeMoved, [0, snapStep[1]]),
|
|
32333
|
+
down: vec.sub(coordToBeMoved, [0, snapStep[1]]),
|
|
32334
|
+
// For horizontal movement, we need to ensure that the points are not on the same vertical line.
|
|
32335
|
+
left: movePointWithConstraint(coord => vec.sub(coord, [snapStep[0], 0])),
|
|
32336
|
+
right: movePointWithConstraint(coord => vec.add(coord, [snapStep[0], 0]))
|
|
32337
|
+
};
|
|
32338
|
+
};
|
|
32339
|
+
const areCoordsValid = coords => {
|
|
32340
|
+
const p1 = coords[0];
|
|
32341
|
+
const p2 = coords[1];
|
|
32342
|
+
const p3 = coords[2];
|
|
32343
|
+
|
|
32344
|
+
// If any of the points share the same x-coordinate,
|
|
32345
|
+
// we are unable to calculate the coefficients, and the graph is invalid.
|
|
32346
|
+
if (p1[0] === p2[0] || p2[0] === p3[0] || p1[0] === p3[0]) {
|
|
32347
|
+
return false;
|
|
32348
|
+
}
|
|
32349
|
+
return true;
|
|
32350
|
+
};
|
|
32562
32351
|
|
|
32563
32352
|
function renderRayGraph(state, dispatch, i18n) {
|
|
32564
32353
|
return {
|
|
@@ -32809,7 +32598,8 @@ function SinusoidGraph(props) {
|
|
|
32809
32598
|
// Note: The order of the coordinates is important:
|
|
32810
32599
|
// The coords[0] is the root and the coords[1] is the first peak
|
|
32811
32600
|
const {
|
|
32812
|
-
coords
|
|
32601
|
+
coords,
|
|
32602
|
+
snapStep
|
|
32813
32603
|
} = graphState;
|
|
32814
32604
|
|
|
32815
32605
|
// The coefficients are used to calculate the sinusoid equation, plot the graph, and to indicate
|
|
@@ -32848,11 +32638,37 @@ function SinusoidGraph(props) {
|
|
|
32848
32638
|
key: "point-" + i,
|
|
32849
32639
|
point: coord,
|
|
32850
32640
|
sequenceNumber: i + 1,
|
|
32641
|
+
constrain: getSinusoidKeyboardConstraint(coords, snapStep, i),
|
|
32851
32642
|
onMove: destination => dispatch(actions.sinusoid.movePoint(i, destination))
|
|
32852
32643
|
})), /*#__PURE__*/React.createElement("g", {
|
|
32853
32644
|
id: descriptionId
|
|
32854
32645
|
}, srSinusoidDescription));
|
|
32855
32646
|
}
|
|
32647
|
+
const getSinusoidKeyboardConstraint = (coords, snapStep, pointIndex) => {
|
|
32648
|
+
// Separate the two points into their own variables, and determine which point is being moved
|
|
32649
|
+
const coordToBeMoved = coords[pointIndex];
|
|
32650
|
+
const otherPoint = coords[1 - pointIndex];
|
|
32651
|
+
|
|
32652
|
+
// Create a helper function that checks if the new point is on the same
|
|
32653
|
+
// vertical line as the other point. If it is, we need to move the point
|
|
32654
|
+
// an additional snapStep.
|
|
32655
|
+
const movePointWithConstraint = moveFunc => {
|
|
32656
|
+
// Move the point
|
|
32657
|
+
let movedCoord = moveFunc(coordToBeMoved);
|
|
32658
|
+
// If the moved point overlaps with the other point in the line,
|
|
32659
|
+
// move the point again.
|
|
32660
|
+
if (movedCoord[X] === otherPoint[X]) {
|
|
32661
|
+
movedCoord = moveFunc(movedCoord);
|
|
32662
|
+
}
|
|
32663
|
+
return movedCoord;
|
|
32664
|
+
};
|
|
32665
|
+
return {
|
|
32666
|
+
up: movePointWithConstraint(coord => vec.add(coord, [0, snapStep[1]])),
|
|
32667
|
+
down: movePointWithConstraint(coord => vec.sub(coord, [0, snapStep[1]])),
|
|
32668
|
+
left: movePointWithConstraint(coord => vec.sub(coord, [snapStep[0], 0])),
|
|
32669
|
+
right: movePointWithConstraint(coord => vec.add(coord, [snapStep[0], 0]))
|
|
32670
|
+
};
|
|
32671
|
+
};
|
|
32856
32672
|
|
|
32857
32673
|
// Plot a sinusoid of the form: f(x) = a * sin(b * x - c) + d
|
|
32858
32674
|
const computeSine = function computeSine(x,
|
|
@@ -32904,6 +32720,14 @@ function describeSinusoidGraph(state, i18n) {
|
|
|
32904
32720
|
const [root, peak] = coords;
|
|
32905
32721
|
const diffX = Math.abs(peak[X] - root[X]);
|
|
32906
32722
|
const diffY = Math.abs(peak[Y] - root[Y]);
|
|
32723
|
+
const formattedRoot = {
|
|
32724
|
+
x: srFormatNumber(root[X], locale),
|
|
32725
|
+
y: srFormatNumber(root[Y], locale)
|
|
32726
|
+
};
|
|
32727
|
+
const formattedPeak = {
|
|
32728
|
+
x: srFormatNumber(peak[X], locale),
|
|
32729
|
+
y: srFormatNumber(peak[Y], locale)
|
|
32730
|
+
};
|
|
32907
32731
|
const srSinusoidGraph = strings.srSinusoidGraph;
|
|
32908
32732
|
const srSinusoidDescription = strings.srSinusoidDescription({
|
|
32909
32733
|
minValue: srFormatNumber(root[Y] - diffY, locale),
|
|
@@ -32911,14 +32735,8 @@ function describeSinusoidGraph(state, i18n) {
|
|
|
32911
32735
|
cycleStart: srFormatNumber(root[X] - 2 * diffX, locale),
|
|
32912
32736
|
cycleEnd: srFormatNumber(root[X] + 2 * diffX, locale)
|
|
32913
32737
|
});
|
|
32914
|
-
const srSinusoidRootPoint = strings.srSinusoidRootPoint(
|
|
32915
|
-
|
|
32916
|
-
y: srFormatNumber(root[Y], locale)
|
|
32917
|
-
});
|
|
32918
|
-
const srSinusoidPeakPoint = strings.srSinusoidPeakPoint({
|
|
32919
|
-
x: srFormatNumber(peak[X], locale),
|
|
32920
|
-
y: srFormatNumber(peak[Y], locale)
|
|
32921
|
-
});
|
|
32738
|
+
const srSinusoidRootPoint = strings.srSinusoidRootPoint(formattedRoot);
|
|
32739
|
+
const srSinusoidPeakPoint = peak[Y] === root[Y] ? strings.srSinusoidFlatPoint(formattedPeak) : peak[Y] > root[Y] ? strings.srSinusoidMaxPoint(formattedPeak) : strings.srSinusoidMinPoint(formattedPeak);
|
|
32922
32740
|
const srSinusoidInteractiveElements = strings.srInteractiveElements({
|
|
32923
32741
|
elements: strings.srSinusoidInteractiveElements({
|
|
32924
32742
|
point1X: srFormatNumber(root[X], locale),
|
|
@@ -32940,16 +32758,17 @@ const {
|
|
|
32940
32758
|
calculateAngleInDegrees,
|
|
32941
32759
|
convertDegreesToRadians
|
|
32942
32760
|
} = angles;
|
|
32943
|
-
const protractorImage = "https://
|
|
32761
|
+
const protractorImage = "https://cdn.kastatic.org/images/perseus/protractor.svg";
|
|
32944
32762
|
|
|
32945
32763
|
// The vector from the center of the protractor to the top left corner of the
|
|
32946
32764
|
// protractor image, in pixels. Used for positioning.
|
|
32947
|
-
const centerToTopLeft = [-
|
|
32765
|
+
const centerToTopLeft = [-195, -190];
|
|
32948
32766
|
|
|
32949
32767
|
// The vector from the center of the protractor to the center of the rotation
|
|
32950
32768
|
// handle.
|
|
32951
|
-
const centerToRotationHandle = [-
|
|
32769
|
+
const centerToRotationHandle = [-201, -15];
|
|
32952
32770
|
function Protractor() {
|
|
32771
|
+
const staticUrl = getDependencies().staticUrl;
|
|
32953
32772
|
const {
|
|
32954
32773
|
range,
|
|
32955
32774
|
snapStep
|
|
@@ -32961,7 +32780,9 @@ function Protractor() {
|
|
|
32961
32780
|
const [center, setCenter] = useState(initialCenter);
|
|
32962
32781
|
const [rotationHandleOffset, setRotationHandleOffset] = useState(centerToRotationHandle);
|
|
32963
32782
|
const draggableRef = useRef(null);
|
|
32964
|
-
|
|
32783
|
+
const {
|
|
32784
|
+
dragging
|
|
32785
|
+
} = useDraggable({
|
|
32965
32786
|
gestureTarget: draggableRef,
|
|
32966
32787
|
onMove: setCenter,
|
|
32967
32788
|
point: center,
|
|
@@ -32985,10 +32806,11 @@ function Protractor() {
|
|
|
32985
32806
|
ref: draggableRef,
|
|
32986
32807
|
transform: `translate(${topLeftPx[X]}, ${topLeftPx[Y]}), rotate(${angle})`,
|
|
32987
32808
|
style: {
|
|
32988
|
-
transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px
|
|
32809
|
+
transformOrigin: `${-centerToTopLeft[X]}px ${-centerToTopLeft[Y]}px`,
|
|
32810
|
+
cursor: dragging ? "grabbing" : "grab"
|
|
32989
32811
|
}
|
|
32990
32812
|
}, /*#__PURE__*/React.createElement("image", {
|
|
32991
|
-
href: protractorImage
|
|
32813
|
+
href: staticUrl(protractorImage)
|
|
32992
32814
|
}), /*#__PURE__*/React.createElement("g", {
|
|
32993
32815
|
transform: `translate(5, ${-centerToTopLeft[1]})`,
|
|
32994
32816
|
ref: rotationHandleRef
|
|
@@ -34863,7 +34685,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
34863
34685
|
|
|
34864
34686
|
// If the graph is static, it always displays the correct answer. This is
|
|
34865
34687
|
// standard behavior for Perseus widgets (e.g. compare the Radio widget).
|
|
34866
|
-
if (props.static) {
|
|
34688
|
+
if (props.static && props.correct) {
|
|
34867
34689
|
return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
|
|
34868
34690
|
state: initializeGraphState(_extends({}, props, {
|
|
34869
34691
|
graph: props.correct
|
|
@@ -34882,7 +34704,7 @@ const {
|
|
|
34882
34704
|
} = angles;
|
|
34883
34705
|
const {
|
|
34884
34706
|
getLineEquation,
|
|
34885
|
-
|
|
34707
|
+
getLineIntersectionString,
|
|
34886
34708
|
magnitude,
|
|
34887
34709
|
vector
|
|
34888
34710
|
} = geometry;
|
|
@@ -35240,7 +35062,7 @@ class InteractiveGraph extends React.Component {
|
|
|
35240
35062
|
}
|
|
35241
35063
|
static getLinearSystemEquationString(props) {
|
|
35242
35064
|
const coords = InteractiveGraph.getLinearSystemCoords(props.graph, props);
|
|
35243
|
-
return "\n" + getLineEquation(coords[0][0], coords[0][1]) + "\n" + getLineEquation(coords[1][0], coords[1][1]) + "\n" +
|
|
35065
|
+
return "\n" + getLineEquation(coords[0][0], coords[0][1]) + "\n" + getLineEquation(coords[1][0], coords[1][1]) + "\n" + getLineIntersectionString(coords[0], coords[1]);
|
|
35244
35066
|
}
|
|
35245
35067
|
static getPointEquationString(props) {
|
|
35246
35068
|
if (props.graph.type !== "point") {
|
|
@@ -35688,7 +35510,6 @@ const styles$a = StyleSheet.create({
|
|
|
35688
35510
|
markerCorrect: {
|
|
35689
35511
|
background: "#00880b" // WB green darkened by 18%
|
|
35690
35512
|
},
|
|
35691
|
-
|
|
35692
35513
|
markerIncorrect: {
|
|
35693
35514
|
background: color.offBlack64
|
|
35694
35515
|
}
|
|
@@ -36081,7 +35902,6 @@ class LabelImage extends React.Component {
|
|
|
36081
35902
|
const adjustPillDistance = {
|
|
36082
35903
|
[`margin${markerPosition.charAt(0).toUpperCase() + markerPosition.slice(1)}`]: 10 // move pill further from marker
|
|
36083
35904
|
};
|
|
36084
|
-
|
|
36085
35905
|
const answerChoicesActive = index === activeMarkerIndex;
|
|
36086
35906
|
const showAnswerChoice = marker.selected && !answerChoicesActive && !this.state.hideAnswers;
|
|
36087
35907
|
return /*#__PURE__*/React.createElement(View, {
|
|
@@ -36526,7 +36346,6 @@ class Draggable extends React.Component {
|
|
|
36526
36346
|
if (this.props.width) {
|
|
36527
36347
|
style.width = this.props.width + 1; // Fix for non-integer widths
|
|
36528
36348
|
}
|
|
36529
|
-
|
|
36530
36349
|
if (this.props.height) {
|
|
36531
36350
|
style.height = this.props.height;
|
|
36532
36351
|
}
|
|
@@ -36868,8 +36687,7 @@ class Sortable extends React.Component {
|
|
|
36868
36687
|
// @ts-expect-error - TS2683 - 'this' implicitly has type 'any' because it does not have a type annotation.
|
|
36869
36688
|
margin = "0 0 " + this.props.margin + "px 0"; // bottom
|
|
36870
36689
|
}
|
|
36871
|
-
|
|
36872
|
-
cards.push( /*#__PURE__*/React.createElement(Draggable, {
|
|
36690
|
+
cards.push(/*#__PURE__*/React.createElement(Draggable, {
|
|
36873
36691
|
content: item.option,
|
|
36874
36692
|
key: item.key,
|
|
36875
36693
|
state: item.state
|
|
@@ -36922,7 +36740,7 @@ class Sortable extends React.Component {
|
|
|
36922
36740
|
this, item.key)
|
|
36923
36741
|
}));
|
|
36924
36742
|
if (item.state === ItemState.DRAGGING || item.state === ItemState.ANIMATING) {
|
|
36925
|
-
cards.push(
|
|
36743
|
+
cards.push(/*#__PURE__*/React.createElement(Placeholder, {
|
|
36926
36744
|
key: "placeholder_" + item.key,
|
|
36927
36745
|
ref: "placeholder_" + item.key,
|
|
36928
36746
|
width: item.width,
|
|
@@ -37013,10 +36831,6 @@ const getPromptJSON$9 = (renderProps, userInput) => {
|
|
|
37013
36831
|
};
|
|
37014
36832
|
};
|
|
37015
36833
|
|
|
37016
|
-
const {
|
|
37017
|
-
shuffle: shuffle$1,
|
|
37018
|
-
seededRNG
|
|
37019
|
-
} = Util;
|
|
37020
36834
|
const HACKY_CSS_CLASSNAME = "perseus-widget-matcher";
|
|
37021
36835
|
class Matcher extends React.Component {
|
|
37022
36836
|
constructor(...args) {
|
|
@@ -37108,16 +36922,16 @@ class Matcher extends React.Component {
|
|
|
37108
36922
|
}, "1")));
|
|
37109
36923
|
}
|
|
37110
36924
|
|
|
37111
|
-
|
|
37112
|
-
|
|
37113
|
-
|
|
37114
|
-
|
|
37115
|
-
|
|
37116
|
-
|
|
37117
|
-
|
|
37118
|
-
left
|
|
37119
|
-
|
|
37120
|
-
|
|
36925
|
+
/* TODO(LEMS-2841):
|
|
36926
|
+
Once the getMatcherPublicWidgetOptions function gets connected to the
|
|
36927
|
+
widget, we'll need to update this to the line below to only shuffle
|
|
36928
|
+
on the server.
|
|
36929
|
+
const {left, right} = this.props;
|
|
36930
|
+
*/
|
|
36931
|
+
const {
|
|
36932
|
+
left,
|
|
36933
|
+
right
|
|
36934
|
+
} = shuffleMatcher(this.props);
|
|
37121
36935
|
const showLabels = _.any(this.props.labels);
|
|
37122
36936
|
const constraints = {
|
|
37123
36937
|
height: _.max([this.state.leftHeight, this.state.rightHeight])
|
|
@@ -38716,7 +38530,6 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
|
|
|
38716
38530
|
if (!_.isFinite(props.tickStep) || props.tickStep <= 0) {
|
|
38717
38531
|
return []; // this has screwed me for the last time!
|
|
38718
38532
|
}
|
|
38719
|
-
|
|
38720
38533
|
const results = [];
|
|
38721
38534
|
|
|
38722
38535
|
// For convenience, extract some props into separate variables
|
|
@@ -38787,6 +38600,7 @@ const TickMarks = Graphie.createSimpleClass((graphie, props) => {
|
|
|
38787
38600
|
|
|
38788
38601
|
// TODO: most widgets use some like Widget<Something, PerseusNumberLineWidgetOptions>
|
|
38789
38602
|
// should this one?
|
|
38603
|
+
|
|
38790
38604
|
class NumberLine extends React.Component {
|
|
38791
38605
|
constructor(..._args) {
|
|
38792
38606
|
super(..._args);
|
|
@@ -39152,7 +38966,6 @@ const numberLineTransform = editorProps => {
|
|
|
39152
38966
|
} else {
|
|
39153
38967
|
numDivisions = undefined; // send to getDefaultProps()
|
|
39154
38968
|
}
|
|
39155
|
-
|
|
39156
38969
|
_.extend(props, {
|
|
39157
38970
|
numLinePosition: numLinePosition,
|
|
39158
38971
|
numDivisions: numDivisions,
|
|
@@ -39177,7 +38990,6 @@ const staticTransform$1 = editorProps => {
|
|
|
39177
38990
|
} else {
|
|
39178
38991
|
numDivisions = undefined; // send to getDefaultProps()
|
|
39179
38992
|
}
|
|
39180
|
-
|
|
39181
38993
|
_.extend(props, {
|
|
39182
38994
|
numLinePosition: numLinePosition,
|
|
39183
38995
|
numDivisions: numDivisions,
|
|
@@ -40166,7 +39978,6 @@ class Plotter extends React.Component {
|
|
|
40166
39978
|
const initialY = isMobile ? c.scaleY : 0;
|
|
40167
39979
|
for (let y = initialY; y <= c.dimY; y += c.scaleY) {
|
|
40168
39980
|
graphie.label([0, y], KhanMath.roundToApprox(y, 2), "left", /* isTeX */true /* for the \approx symbol */);
|
|
40169
|
-
|
|
40170
39981
|
graphie.style({
|
|
40171
39982
|
stroke: isMobile ? "#e9ebec" : "#000",
|
|
40172
39983
|
strokeWidth: 1,
|
|
@@ -40893,9 +40704,6 @@ const getPromptJSON$1 = userInput => {
|
|
|
40893
40704
|
};
|
|
40894
40705
|
};
|
|
40895
40706
|
|
|
40896
|
-
const {
|
|
40897
|
-
shuffle
|
|
40898
|
-
} = Util;
|
|
40899
40707
|
class Sorter extends React.Component {
|
|
40900
40708
|
constructor(...args) {
|
|
40901
40709
|
super(...args);
|
|
@@ -40947,6 +40755,10 @@ class Sorter extends React.Component {
|
|
|
40947
40755
|
return getPromptJSON$1(this.getUserInput());
|
|
40948
40756
|
}
|
|
40949
40757
|
render() {
|
|
40758
|
+
// TODO(LEMS-2841): Remove client-side shuffle once receiving public
|
|
40759
|
+
// options. The correct field will already be shuffled.
|
|
40760
|
+
// Probably easiest to replace "options" with "this.props.correct" when
|
|
40761
|
+
// setting up Sortable below. Or use the assignment to rename for clarity.
|
|
40950
40762
|
const options = shuffle(this.props.correct, this.props.problemNum, /* ensurePermuted */true);
|
|
40951
40763
|
const {
|
|
40952
40764
|
apiOptions
|
|
@@ -41375,9 +41187,51 @@ const init = function init() {
|
|
|
41375
41187
|
replaceDeprecatedWidgets();
|
|
41376
41188
|
};
|
|
41377
41189
|
|
|
41190
|
+
/**
|
|
41191
|
+
* Adds the given perseus library version information to the __perseus_debug__
|
|
41192
|
+
* object and ensures that the object is attached to `globalThis` (`window` in
|
|
41193
|
+
* browser environments).
|
|
41194
|
+
*
|
|
41195
|
+
* This allows each library to provide runtime version information to assist in
|
|
41196
|
+
* debugging in production environments.
|
|
41197
|
+
*/
|
|
41198
|
+
const addLibraryVersionToPerseusDebug = (libraryName, libraryVersion) => {
|
|
41199
|
+
// If the library version is the default value, then we don't want to
|
|
41200
|
+
// prefix it with a "v" to indicate that it is a version number.
|
|
41201
|
+
let prefix = "v";
|
|
41202
|
+
if (libraryVersion === "__lib_version__") {
|
|
41203
|
+
prefix = "";
|
|
41204
|
+
}
|
|
41205
|
+
const formattedVersion = `${prefix}${libraryVersion}`;
|
|
41206
|
+
if (typeof globalThis !== "undefined") {
|
|
41207
|
+
var _globalThis$__perseus;
|
|
41208
|
+
globalThis.__perseus_debug__ = (_globalThis$__perseus = globalThis.__perseus_debug__) != null ? _globalThis$__perseus : {};
|
|
41209
|
+
const existingVersionEntry = globalThis.__perseus_debug__[libraryName];
|
|
41210
|
+
if (existingVersionEntry) {
|
|
41211
|
+
// If we already have an entry and it doesn't match the registered
|
|
41212
|
+
// version, we morph the entry into an array and log a warning.
|
|
41213
|
+
if (existingVersionEntry !== formattedVersion) {
|
|
41214
|
+
// Existing entry might be an array already (oops, at least 2
|
|
41215
|
+
// versions of the library already loaded!).
|
|
41216
|
+
const allVersions = Array.isArray(existingVersionEntry) ? existingVersionEntry : [existingVersionEntry];
|
|
41217
|
+
allVersions.push(formattedVersion);
|
|
41218
|
+
globalThis.__perseus_debug__[libraryName] = allVersions;
|
|
41219
|
+
|
|
41220
|
+
// eslint-disable-next-line no-console
|
|
41221
|
+
console.warn(`Multiple versions of ${libraryName} loaded on this page: ${allVersions.sort().join(", ")}`);
|
|
41222
|
+
}
|
|
41223
|
+
} else {
|
|
41224
|
+
globalThis.__perseus_debug__[libraryName] = formattedVersion;
|
|
41225
|
+
}
|
|
41226
|
+
} else {
|
|
41227
|
+
// eslint-disable-next-line no-console
|
|
41228
|
+
console.warn(`globalThis not found found (${formattedVersion})`);
|
|
41229
|
+
}
|
|
41230
|
+
};
|
|
41231
|
+
|
|
41378
41232
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
41379
41233
|
const libName = "@khanacademy/perseus";
|
|
41380
|
-
const libVersion = "
|
|
41234
|
+
const libVersion = "55.0.0";
|
|
41381
41235
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
41382
41236
|
|
|
41383
41237
|
const apiVersion = {
|
|
@@ -41771,7 +41625,6 @@ class HintsRenderer extends React.Component {
|
|
|
41771
41625
|
ReactDOM__default.findDOMNode(this.refs["hintRenderer" + pos]).focus(); // eslint-disable-line react/no-string-refs
|
|
41772
41626
|
}
|
|
41773
41627
|
}
|
|
41774
|
-
|
|
41775
41628
|
render() {
|
|
41776
41629
|
const apiOptions = this.getApiOptions();
|
|
41777
41630
|
const hintsVisible = this._hintsVisible();
|