@khanacademy/perseus 23.5.0 → 24.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/dist/components/input-with-examples.d.ts +0 -4
- package/dist/es/index.css +0 -17
- package/dist/es/index.js +220 -330
- package/dist/es/index.js.map +1 -1
- package/dist/index.css +0 -17
- package/dist/index.js +220 -327
- package/dist/index.js.map +1 -1
- package/dist/perseus-api.d.ts +1 -0
- package/dist/server-item-renderer.d.ts +1 -0
- package/dist/types.d.ts +1 -1
- package/dist/widgets/expression.d.ts +6 -0
- package/dist/widgets/interactive-graphs/graphs/sinusoid.d.ts +1 -1
- package/dist/widgets/interactive-graphs/graphs/use-draggable.d.ts +13 -0
- package/dist/widgets/interactive-graphs/interactive-graph-question-builder.d.ts +10 -3
- package/dist/widgets/interactive-graphs/{locked-ellipse.d.ts → locked-figures/locked-ellipse.d.ts} +1 -1
- package/dist/widgets/interactive-graphs/{locked-line.d.ts → locked-figures/locked-line.d.ts} +1 -1
- package/dist/widgets/interactive-graphs/{locked-point.d.ts → locked-figures/locked-point.d.ts} +1 -1
- package/dist/widgets/interactive-graphs/{locked-polygon.d.ts → locked-figures/locked-polygon.d.ts} +1 -1
- package/dist/widgets/interactive-graphs/{locked-vector.d.ts → locked-figures/locked-vector.d.ts} +1 -1
- package/dist/widgets/label-image.d.ts +2 -0
- package/package.json +1 -1
- package/dist/components/math-output.d.ts +0 -27
package/dist/es/index.js
CHANGED
|
@@ -520,8 +520,7 @@ class InnerMathInput extends React.Component {
|
|
|
520
520
|
active: false
|
|
521
521
|
}) : /*#__PURE__*/React.createElement(Clickable, {
|
|
522
522
|
"aria-label": this.state.keypadOpen ? this.context.strings.closeKeypad : this.context.strings.openKeypad,
|
|
523
|
-
"
|
|
524
|
-
role: "switch",
|
|
523
|
+
role: "button",
|
|
525
524
|
onClick: () => this.state.keypadOpen ? this.closeKeypad() : this.openKeypad()
|
|
526
525
|
}, props => /*#__PURE__*/React.createElement(MathInputIcon, _extends({
|
|
527
526
|
active: this.state.keypadOpen
|
|
@@ -20964,254 +20963,6 @@ const styles$B = {
|
|
|
20964
20963
|
}
|
|
20965
20964
|
};
|
|
20966
20965
|
|
|
20967
|
-
/*
|
|
20968
|
-
* In this file, an `expression` is some portion of valid TeX enclosed in
|
|
20969
|
-
* curly brackets.
|
|
20970
|
-
*/
|
|
20971
|
-
|
|
20972
|
-
/*
|
|
20973
|
-
* Find the index at which an expression ends, i.e., has an unmatched
|
|
20974
|
-
* closing curly bracket. This method assumes that we start with a non-open
|
|
20975
|
-
* bracket character and end when we've seen more left than right brackets
|
|
20976
|
-
* (rather than assuming that we start with a bracket character and wait for
|
|
20977
|
-
* bracket equality).
|
|
20978
|
-
*/
|
|
20979
|
-
function findEndpoint(tex, currentIndex) {
|
|
20980
|
-
let bracketDepth = 0;
|
|
20981
|
-
for (let i = currentIndex, len = tex.length; i < len; i++) {
|
|
20982
|
-
const c = tex[i];
|
|
20983
|
-
if (c === "{") {
|
|
20984
|
-
bracketDepth++;
|
|
20985
|
-
} else if (c === "}") {
|
|
20986
|
-
bracketDepth--;
|
|
20987
|
-
}
|
|
20988
|
-
if (bracketDepth < 0) {
|
|
20989
|
-
return i;
|
|
20990
|
-
}
|
|
20991
|
-
}
|
|
20992
|
-
// If we never see unbalanced curly brackets, default to the
|
|
20993
|
-
// entire string
|
|
20994
|
-
return tex.length;
|
|
20995
|
-
}
|
|
20996
|
-
|
|
20997
|
-
/*
|
|
20998
|
-
* Parses an individual set of curly brackets into TeX.
|
|
20999
|
-
*/
|
|
21000
|
-
function parseNextExpression(tex, currentIndex, handler) {
|
|
21001
|
-
// Find the first '{' and grab subsequent TeX
|
|
21002
|
-
// Ex) tex: '{3}{7}', and we want the '3'
|
|
21003
|
-
const openBracketIndex = tex.indexOf("{", currentIndex);
|
|
21004
|
-
const nextExpIndex = openBracketIndex + 1;
|
|
21005
|
-
|
|
21006
|
-
// Truncate to only contain remaining TeX
|
|
21007
|
-
const endpoint = findEndpoint(tex, nextExpIndex);
|
|
21008
|
-
const expressionTeX = tex.substring(nextExpIndex, endpoint);
|
|
21009
|
-
const parsedExp = walkTex(expressionTeX, handler);
|
|
21010
|
-
return {
|
|
21011
|
-
endpoint: endpoint,
|
|
21012
|
-
expression: parsedExp
|
|
21013
|
-
};
|
|
21014
|
-
}
|
|
21015
|
-
function getNextFracIndex(tex, currentIndex) {
|
|
21016
|
-
const dfrac = "\\dfrac";
|
|
21017
|
-
const frac = "\\frac";
|
|
21018
|
-
const nextFrac = tex.indexOf(frac, currentIndex);
|
|
21019
|
-
const nextDFrac = tex.indexOf(dfrac, currentIndex);
|
|
21020
|
-
if (nextFrac > -1 && nextDFrac > -1) {
|
|
21021
|
-
return Math.min(nextFrac, nextDFrac);
|
|
21022
|
-
}
|
|
21023
|
-
if (nextFrac > -1) {
|
|
21024
|
-
return nextFrac;
|
|
21025
|
-
}
|
|
21026
|
-
if (nextDFrac > -1) {
|
|
21027
|
-
return nextDFrac;
|
|
21028
|
-
}
|
|
21029
|
-
return -1;
|
|
21030
|
-
}
|
|
21031
|
-
function walkTex(tex, handler) {
|
|
21032
|
-
if (!tex) {
|
|
21033
|
-
return "";
|
|
21034
|
-
}
|
|
21035
|
-
|
|
21036
|
-
// Ex) tex: '2 \dfrac {3}{7}'
|
|
21037
|
-
let parsedString = "";
|
|
21038
|
-
let currentIndex = 0;
|
|
21039
|
-
let nextFrac = getNextFracIndex(tex, currentIndex);
|
|
21040
|
-
|
|
21041
|
-
// For each \dfrac, find the two expressions (wrapped in {}) and recur
|
|
21042
|
-
while (nextFrac > -1) {
|
|
21043
|
-
// Gather first fragment, preceding \dfrac
|
|
21044
|
-
// Ex) parsedString: '2 '
|
|
21045
|
-
parsedString += tex.substring(currentIndex, nextFrac);
|
|
21046
|
-
|
|
21047
|
-
// Remove everything preceding \dfrac, which has been parsed
|
|
21048
|
-
currentIndex = nextFrac;
|
|
21049
|
-
|
|
21050
|
-
// Parse first expression and move index past it
|
|
21051
|
-
// Ex) firstParsedExpression.expression: '3'
|
|
21052
|
-
const firstParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
21053
|
-
currentIndex = firstParsedExpression.endpoint + 1;
|
|
21054
|
-
|
|
21055
|
-
// Parse second expression
|
|
21056
|
-
// Ex) secondParsedExpression.expression: '7'
|
|
21057
|
-
const secondParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
21058
|
-
currentIndex = secondParsedExpression.endpoint + 1;
|
|
21059
|
-
|
|
21060
|
-
// Add expressions to running total of parsed expressions
|
|
21061
|
-
if (parsedString.length) {
|
|
21062
|
-
parsedString += " ";
|
|
21063
|
-
}
|
|
21064
|
-
|
|
21065
|
-
// Apply a custom handler based on the parsed subexpressions
|
|
21066
|
-
parsedString += handler(firstParsedExpression.expression, secondParsedExpression.expression);
|
|
21067
|
-
|
|
21068
|
-
// Find next DFrac, relative to currentIndex
|
|
21069
|
-
nextFrac = getNextFracIndex(tex, currentIndex);
|
|
21070
|
-
}
|
|
21071
|
-
|
|
21072
|
-
// Add remaining TeX, which is \dfrac-free
|
|
21073
|
-
parsedString += tex.slice(currentIndex);
|
|
21074
|
-
return parsedString;
|
|
21075
|
-
}
|
|
21076
|
-
|
|
21077
|
-
/*
|
|
21078
|
-
* Modify a TeX expression, returning another TeX expression. The resulting
|
|
21079
|
-
* expression will have its innermost fractions stubbed out with \fracs
|
|
21080
|
-
* (as opposed to \dfracs). All other content will remain untouched.
|
|
21081
|
-
*/
|
|
21082
|
-
function modifyTex(tex) {
|
|
21083
|
-
function isNestedFraction(tex) {
|
|
21084
|
-
return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
|
|
21085
|
-
}
|
|
21086
|
-
const handler = function handler(exp1, exp2) {
|
|
21087
|
-
let prefix;
|
|
21088
|
-
if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
|
|
21089
|
-
prefix = "\\dfrac";
|
|
21090
|
-
} else {
|
|
21091
|
-
prefix = "\\frac";
|
|
21092
|
-
}
|
|
21093
|
-
return prefix + " {" + exp1 + "}{" + exp2 + "}";
|
|
21094
|
-
};
|
|
21095
|
-
return walkTex(tex, handler);
|
|
21096
|
-
}
|
|
21097
|
-
|
|
21098
|
-
/*
|
|
21099
|
-
* Parse a TeX expression into something interpretable by input-number.
|
|
21100
|
-
* The process is concerned with: (1) parsing fractions, i.e., \dfracs; and
|
|
21101
|
-
* (2) removing backslash-escaping from certain characters (right now, only
|
|
21102
|
-
* percent signs).
|
|
21103
|
-
*
|
|
21104
|
-
* The basic algorithm for handling \dfracs splits on \dfracs and then recurs
|
|
21105
|
-
* on the subsequent "expressions", i.e., the {} pairs that follow \dfrac. The
|
|
21106
|
-
* recursion is to allow for nested \dfrac elements.
|
|
21107
|
-
*
|
|
21108
|
-
* Backslash-escapes are removed with a simple search-and-replace.
|
|
21109
|
-
*/
|
|
21110
|
-
function parseTex(tex) {
|
|
21111
|
-
const handler = function handler(exp1, exp2) {
|
|
21112
|
-
return exp1 + "/" + exp2;
|
|
21113
|
-
};
|
|
21114
|
-
const texWithoutFracs = walkTex(tex, handler);
|
|
21115
|
-
return texWithoutFracs.replace("\\%", "%");
|
|
21116
|
-
}
|
|
21117
|
-
var TexWrangler = {
|
|
21118
|
-
parseTex,
|
|
21119
|
-
modifyTex
|
|
21120
|
-
};
|
|
21121
|
-
|
|
21122
|
-
/* eslint-disable react/sort-comp */
|
|
21123
|
-
const ModifyTex = TexWrangler.modifyTex;
|
|
21124
|
-
class MathOutput extends React.Component {
|
|
21125
|
-
constructor(...args) {
|
|
21126
|
-
super(...args);
|
|
21127
|
-
this.state = {
|
|
21128
|
-
focused: false,
|
|
21129
|
-
selectorNamespace: _.uniqueId("math-output")
|
|
21130
|
-
};
|
|
21131
|
-
this._getInputClassName = () => {
|
|
21132
|
-
let className = "math-output " + ClassNames.INPUT + " " + ClassNames.INTERACTIVE;
|
|
21133
|
-
if (this.state.focused) {
|
|
21134
|
-
className += " " + ClassNames.FOCUSED;
|
|
21135
|
-
}
|
|
21136
|
-
if (this.props.className) {
|
|
21137
|
-
className += " " + this.props.className;
|
|
21138
|
-
}
|
|
21139
|
-
return className;
|
|
21140
|
-
};
|
|
21141
|
-
this._getDisplayValue = value => {
|
|
21142
|
-
// Cast from (potentially a) number to string
|
|
21143
|
-
let displayText;
|
|
21144
|
-
if (value != null) {
|
|
21145
|
-
displayText = "" + value;
|
|
21146
|
-
} else {
|
|
21147
|
-
displayText = "";
|
|
21148
|
-
}
|
|
21149
|
-
return ModifyTex(displayText);
|
|
21150
|
-
};
|
|
21151
|
-
this.getValue = () => {
|
|
21152
|
-
return this.props.value;
|
|
21153
|
-
};
|
|
21154
|
-
this.focus = () => {
|
|
21155
|
-
if (!this.state.focused) {
|
|
21156
|
-
this.props.onFocus();
|
|
21157
|
-
this._bindBlurHandler();
|
|
21158
|
-
this.setState({
|
|
21159
|
-
focused: true
|
|
21160
|
-
});
|
|
21161
|
-
}
|
|
21162
|
-
};
|
|
21163
|
-
this.blur = () => {
|
|
21164
|
-
if (this.state.focused) {
|
|
21165
|
-
this.props.onBlur();
|
|
21166
|
-
this._unbindBlurHandler();
|
|
21167
|
-
this.setState({
|
|
21168
|
-
focused: false
|
|
21169
|
-
});
|
|
21170
|
-
}
|
|
21171
|
-
};
|
|
21172
|
-
this._bindBlurHandler = () => {
|
|
21173
|
-
$(document).bind("vclick." + this.state.selectorNamespace, e => {
|
|
21174
|
-
// Detect whether the target has our React DOM node as a parent
|
|
21175
|
-
const $closestWidget = $(e.target).closest(
|
|
21176
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
21177
|
-
ReactDOM__default.findDOMNode(this));
|
|
21178
|
-
if (!$closestWidget.length) {
|
|
21179
|
-
this.blur();
|
|
21180
|
-
}
|
|
21181
|
-
});
|
|
21182
|
-
};
|
|
21183
|
-
this._unbindBlurHandler = () => {
|
|
21184
|
-
$(document).unbind("." + this.state.selectorNamespace);
|
|
21185
|
-
};
|
|
21186
|
-
}
|
|
21187
|
-
render() {
|
|
21188
|
-
const divStyle = {
|
|
21189
|
-
textAlign: "center"
|
|
21190
|
-
};
|
|
21191
|
-
const {
|
|
21192
|
-
TeX
|
|
21193
|
-
} = getDependencies();
|
|
21194
|
-
return /*#__PURE__*/React.createElement("span", {
|
|
21195
|
-
// eslint-disable-next-line react/no-string-refs
|
|
21196
|
-
ref: "input",
|
|
21197
|
-
className: this._getInputClassName(),
|
|
21198
|
-
"aria-label": this.props.labelText,
|
|
21199
|
-
onMouseDown: this.focus,
|
|
21200
|
-
onTouchStart: this.focus
|
|
21201
|
-
}, /*#__PURE__*/React.createElement("div", {
|
|
21202
|
-
style: divStyle
|
|
21203
|
-
}, /*#__PURE__*/React.createElement(TeX, null, this._getDisplayValue(this.props.value))));
|
|
21204
|
-
}
|
|
21205
|
-
componentWillUnmount() {
|
|
21206
|
-
this._unbindBlurHandler();
|
|
21207
|
-
}
|
|
21208
|
-
}
|
|
21209
|
-
MathOutput.defaultProps = {
|
|
21210
|
-
value: "",
|
|
21211
|
-
onFocus: function () {},
|
|
21212
|
-
onBlur: function () {}
|
|
21213
|
-
};
|
|
21214
|
-
|
|
21215
20966
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
21216
20967
|
let lastId = 0;
|
|
21217
20968
|
function uniqueIdForInput(prefix = "input-") {
|
|
@@ -21566,12 +21317,6 @@ class InputWithExamples extends React.Component {
|
|
|
21566
21317
|
return `input-with-examples-${btoa(this.props.id).replace(/=/g, "")}`;
|
|
21567
21318
|
};
|
|
21568
21319
|
this._getInputClassName = () => {
|
|
21569
|
-
// <MathOutput> is a special component that manages its own class and
|
|
21570
|
-
// state, as it's a <span> that wants to act like an <input>.
|
|
21571
|
-
if (this.props.type === "tex") {
|
|
21572
|
-
return this.props.className;
|
|
21573
|
-
}
|
|
21574
|
-
|
|
21575
21320
|
// Otherwise, we need to add these INPUT and FOCUSED tags here.
|
|
21576
21321
|
let className = ClassNames.INPUT + " " + ClassNames.INTERACTIVE;
|
|
21577
21322
|
if (this.state.focused) {
|
|
@@ -21582,8 +21327,7 @@ class InputWithExamples extends React.Component {
|
|
|
21582
21327
|
}
|
|
21583
21328
|
return className;
|
|
21584
21329
|
};
|
|
21585
|
-
this.
|
|
21586
|
-
// Minimal set of props, used by each input type
|
|
21330
|
+
this._renderInput = () => {
|
|
21587
21331
|
const id = this._getUniqueId();
|
|
21588
21332
|
const inputProps = {
|
|
21589
21333
|
id: id,
|
|
@@ -21595,52 +21339,15 @@ class InputWithExamples extends React.Component {
|
|
|
21595
21339
|
onFocus: this._handleFocus,
|
|
21596
21340
|
onBlur: this._handleBlur,
|
|
21597
21341
|
disabled: this.props.disabled,
|
|
21598
|
-
style: this.props.style
|
|
21599
|
-
};
|
|
21600
|
-
if (this.props.type === "tex") {
|
|
21601
|
-
return inputProps;
|
|
21602
|
-
}
|
|
21603
|
-
|
|
21604
|
-
// Add useful props required for MATH and TEXT modes
|
|
21605
|
-
_.extend(inputProps, {
|
|
21342
|
+
style: this.props.style,
|
|
21606
21343
|
onChange: this.props.onChange,
|
|
21607
|
-
onTouchStart: captureScratchpadTouchStart$2
|
|
21608
|
-
|
|
21609
|
-
|
|
21610
|
-
|
|
21611
|
-
|
|
21612
|
-
|
|
21613
|
-
|
|
21614
|
-
buttonsVisible: this.props.buttonsVisible,
|
|
21615
|
-
convertDotToTimes: this.props.convertDotToTimes
|
|
21616
|
-
}, inputProps);
|
|
21617
|
-
}
|
|
21618
|
-
if (this.props.type === "text") {
|
|
21619
|
-
return _.extend({
|
|
21620
|
-
autoCapitalize: "off",
|
|
21621
|
-
autoComplete: "off",
|
|
21622
|
-
autoCorrect: "off",
|
|
21623
|
-
spellCheck: "false"
|
|
21624
|
-
}, inputProps);
|
|
21625
|
-
}
|
|
21626
|
-
};
|
|
21627
|
-
this._getComponentForInputType = () => {
|
|
21628
|
-
switch (this.props.type) {
|
|
21629
|
-
case "tex":
|
|
21630
|
-
return MathOutput;
|
|
21631
|
-
case "math":
|
|
21632
|
-
return MathInput;
|
|
21633
|
-
case "text":
|
|
21634
|
-
return TextInput;
|
|
21635
|
-
default:
|
|
21636
|
-
this.props.type;
|
|
21637
|
-
return null;
|
|
21638
|
-
}
|
|
21639
|
-
};
|
|
21640
|
-
this._renderInput = () => {
|
|
21641
|
-
const inputProps = this._getPropsForInputType();
|
|
21642
|
-
const InputComponent = this._getComponentForInputType();
|
|
21643
|
-
return /*#__PURE__*/React.createElement(InputComponent, inputProps);
|
|
21344
|
+
onTouchStart: captureScratchpadTouchStart$2,
|
|
21345
|
+
autoCapitalize: "off",
|
|
21346
|
+
autoComplete: "off",
|
|
21347
|
+
autoCorrect: "off",
|
|
21348
|
+
spellCheck: "false"
|
|
21349
|
+
};
|
|
21350
|
+
return /*#__PURE__*/React.createElement(TextInput, inputProps);
|
|
21644
21351
|
};
|
|
21645
21352
|
this._handleFocus = () => {
|
|
21646
21353
|
this.props.onFocus();
|
|
@@ -21682,14 +21389,6 @@ class InputWithExamples extends React.Component {
|
|
|
21682
21389
|
}
|
|
21683
21390
|
render() {
|
|
21684
21391
|
const input = this._renderInput();
|
|
21685
|
-
|
|
21686
|
-
// Static rendering, which doesn't include the 'tooltip' logic that the
|
|
21687
|
-
// other types require, and is hence handled separately.
|
|
21688
|
-
if (this.props.type === "tex") {
|
|
21689
|
-
return input;
|
|
21690
|
-
}
|
|
21691
|
-
|
|
21692
|
-
// Else, we need to be able to show examples
|
|
21693
21392
|
const examplesContent = _.map(this.props.examples, example => {
|
|
21694
21393
|
return "- " + example;
|
|
21695
21394
|
}).join("\n");
|
|
@@ -21716,7 +21415,6 @@ class InputWithExamples extends React.Component {
|
|
|
21716
21415
|
}
|
|
21717
21416
|
InputWithExamples.contextType = PerseusI18nContext;
|
|
21718
21417
|
InputWithExamples.defaultProps = {
|
|
21719
|
-
type: "text",
|
|
21720
21418
|
shouldShowExamples: true,
|
|
21721
21419
|
onFocus: function () {},
|
|
21722
21420
|
onBlur: function () {},
|
|
@@ -21805,6 +21503,161 @@ SimpleKeypadInput.propTypes = {
|
|
|
21805
21503
|
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
|
|
21806
21504
|
};
|
|
21807
21505
|
|
|
21506
|
+
/*
|
|
21507
|
+
* In this file, an `expression` is some portion of valid TeX enclosed in
|
|
21508
|
+
* curly brackets.
|
|
21509
|
+
*/
|
|
21510
|
+
|
|
21511
|
+
/*
|
|
21512
|
+
* Find the index at which an expression ends, i.e., has an unmatched
|
|
21513
|
+
* closing curly bracket. This method assumes that we start with a non-open
|
|
21514
|
+
* bracket character and end when we've seen more left than right brackets
|
|
21515
|
+
* (rather than assuming that we start with a bracket character and wait for
|
|
21516
|
+
* bracket equality).
|
|
21517
|
+
*/
|
|
21518
|
+
function findEndpoint(tex, currentIndex) {
|
|
21519
|
+
let bracketDepth = 0;
|
|
21520
|
+
for (let i = currentIndex, len = tex.length; i < len; i++) {
|
|
21521
|
+
const c = tex[i];
|
|
21522
|
+
if (c === "{") {
|
|
21523
|
+
bracketDepth++;
|
|
21524
|
+
} else if (c === "}") {
|
|
21525
|
+
bracketDepth--;
|
|
21526
|
+
}
|
|
21527
|
+
if (bracketDepth < 0) {
|
|
21528
|
+
return i;
|
|
21529
|
+
}
|
|
21530
|
+
}
|
|
21531
|
+
// If we never see unbalanced curly brackets, default to the
|
|
21532
|
+
// entire string
|
|
21533
|
+
return tex.length;
|
|
21534
|
+
}
|
|
21535
|
+
|
|
21536
|
+
/*
|
|
21537
|
+
* Parses an individual set of curly brackets into TeX.
|
|
21538
|
+
*/
|
|
21539
|
+
function parseNextExpression(tex, currentIndex, handler) {
|
|
21540
|
+
// Find the first '{' and grab subsequent TeX
|
|
21541
|
+
// Ex) tex: '{3}{7}', and we want the '3'
|
|
21542
|
+
const openBracketIndex = tex.indexOf("{", currentIndex);
|
|
21543
|
+
const nextExpIndex = openBracketIndex + 1;
|
|
21544
|
+
|
|
21545
|
+
// Truncate to only contain remaining TeX
|
|
21546
|
+
const endpoint = findEndpoint(tex, nextExpIndex);
|
|
21547
|
+
const expressionTeX = tex.substring(nextExpIndex, endpoint);
|
|
21548
|
+
const parsedExp = walkTex(expressionTeX, handler);
|
|
21549
|
+
return {
|
|
21550
|
+
endpoint: endpoint,
|
|
21551
|
+
expression: parsedExp
|
|
21552
|
+
};
|
|
21553
|
+
}
|
|
21554
|
+
function getNextFracIndex(tex, currentIndex) {
|
|
21555
|
+
const dfrac = "\\dfrac";
|
|
21556
|
+
const frac = "\\frac";
|
|
21557
|
+
const nextFrac = tex.indexOf(frac, currentIndex);
|
|
21558
|
+
const nextDFrac = tex.indexOf(dfrac, currentIndex);
|
|
21559
|
+
if (nextFrac > -1 && nextDFrac > -1) {
|
|
21560
|
+
return Math.min(nextFrac, nextDFrac);
|
|
21561
|
+
}
|
|
21562
|
+
if (nextFrac > -1) {
|
|
21563
|
+
return nextFrac;
|
|
21564
|
+
}
|
|
21565
|
+
if (nextDFrac > -1) {
|
|
21566
|
+
return nextDFrac;
|
|
21567
|
+
}
|
|
21568
|
+
return -1;
|
|
21569
|
+
}
|
|
21570
|
+
function walkTex(tex, handler) {
|
|
21571
|
+
if (!tex) {
|
|
21572
|
+
return "";
|
|
21573
|
+
}
|
|
21574
|
+
|
|
21575
|
+
// Ex) tex: '2 \dfrac {3}{7}'
|
|
21576
|
+
let parsedString = "";
|
|
21577
|
+
let currentIndex = 0;
|
|
21578
|
+
let nextFrac = getNextFracIndex(tex, currentIndex);
|
|
21579
|
+
|
|
21580
|
+
// For each \dfrac, find the two expressions (wrapped in {}) and recur
|
|
21581
|
+
while (nextFrac > -1) {
|
|
21582
|
+
// Gather first fragment, preceding \dfrac
|
|
21583
|
+
// Ex) parsedString: '2 '
|
|
21584
|
+
parsedString += tex.substring(currentIndex, nextFrac);
|
|
21585
|
+
|
|
21586
|
+
// Remove everything preceding \dfrac, which has been parsed
|
|
21587
|
+
currentIndex = nextFrac;
|
|
21588
|
+
|
|
21589
|
+
// Parse first expression and move index past it
|
|
21590
|
+
// Ex) firstParsedExpression.expression: '3'
|
|
21591
|
+
const firstParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
21592
|
+
currentIndex = firstParsedExpression.endpoint + 1;
|
|
21593
|
+
|
|
21594
|
+
// Parse second expression
|
|
21595
|
+
// Ex) secondParsedExpression.expression: '7'
|
|
21596
|
+
const secondParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
21597
|
+
currentIndex = secondParsedExpression.endpoint + 1;
|
|
21598
|
+
|
|
21599
|
+
// Add expressions to running total of parsed expressions
|
|
21600
|
+
if (parsedString.length) {
|
|
21601
|
+
parsedString += " ";
|
|
21602
|
+
}
|
|
21603
|
+
|
|
21604
|
+
// Apply a custom handler based on the parsed subexpressions
|
|
21605
|
+
parsedString += handler(firstParsedExpression.expression, secondParsedExpression.expression);
|
|
21606
|
+
|
|
21607
|
+
// Find next DFrac, relative to currentIndex
|
|
21608
|
+
nextFrac = getNextFracIndex(tex, currentIndex);
|
|
21609
|
+
}
|
|
21610
|
+
|
|
21611
|
+
// Add remaining TeX, which is \dfrac-free
|
|
21612
|
+
parsedString += tex.slice(currentIndex);
|
|
21613
|
+
return parsedString;
|
|
21614
|
+
}
|
|
21615
|
+
|
|
21616
|
+
/*
|
|
21617
|
+
* Modify a TeX expression, returning another TeX expression. The resulting
|
|
21618
|
+
* expression will have its innermost fractions stubbed out with \fracs
|
|
21619
|
+
* (as opposed to \dfracs). All other content will remain untouched.
|
|
21620
|
+
*/
|
|
21621
|
+
function modifyTex(tex) {
|
|
21622
|
+
function isNestedFraction(tex) {
|
|
21623
|
+
return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
|
|
21624
|
+
}
|
|
21625
|
+
const handler = function handler(exp1, exp2) {
|
|
21626
|
+
let prefix;
|
|
21627
|
+
if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
|
|
21628
|
+
prefix = "\\dfrac";
|
|
21629
|
+
} else {
|
|
21630
|
+
prefix = "\\frac";
|
|
21631
|
+
}
|
|
21632
|
+
return prefix + " {" + exp1 + "}{" + exp2 + "}";
|
|
21633
|
+
};
|
|
21634
|
+
return walkTex(tex, handler);
|
|
21635
|
+
}
|
|
21636
|
+
|
|
21637
|
+
/*
|
|
21638
|
+
* Parse a TeX expression into something interpretable by input-number.
|
|
21639
|
+
* The process is concerned with: (1) parsing fractions, i.e., \dfracs; and
|
|
21640
|
+
* (2) removing backslash-escaping from certain characters (right now, only
|
|
21641
|
+
* percent signs).
|
|
21642
|
+
*
|
|
21643
|
+
* The basic algorithm for handling \dfracs splits on \dfracs and then recurs
|
|
21644
|
+
* on the subsequent "expressions", i.e., the {} pairs that follow \dfrac. The
|
|
21645
|
+
* recursion is to allow for nested \dfrac elements.
|
|
21646
|
+
*
|
|
21647
|
+
* Backslash-escapes are removed with a simple search-and-replace.
|
|
21648
|
+
*/
|
|
21649
|
+
function parseTex(tex) {
|
|
21650
|
+
const handler = function handler(exp1, exp2) {
|
|
21651
|
+
return exp1 + "/" + exp2;
|
|
21652
|
+
};
|
|
21653
|
+
const texWithoutFracs = walkTex(tex, handler);
|
|
21654
|
+
return texWithoutFracs.replace("\\%", "%");
|
|
21655
|
+
}
|
|
21656
|
+
var TexWrangler = {
|
|
21657
|
+
parseTex,
|
|
21658
|
+
modifyTex
|
|
21659
|
+
};
|
|
21660
|
+
|
|
21808
21661
|
/* eslint-disable react/sort-comp */
|
|
21809
21662
|
const ParseTex$1 = TexWrangler.parseTex;
|
|
21810
21663
|
const answerTypes = {
|
|
@@ -21983,7 +21836,6 @@ class InputNumber extends React.Component {
|
|
|
21983
21836
|
value: this.props.currentValue,
|
|
21984
21837
|
onChange: this.handleChange,
|
|
21985
21838
|
style: inputStyles,
|
|
21986
|
-
type: "text",
|
|
21987
21839
|
examples: this.examples(),
|
|
21988
21840
|
shouldShowExamples: this.shouldShowExamples(),
|
|
21989
21841
|
onFocus: this._handleFocus,
|
|
@@ -22392,7 +22244,6 @@ class NumericInput extends React.Component {
|
|
|
22392
22244
|
value: this.props.currentValue,
|
|
22393
22245
|
onChange: this.handleChange,
|
|
22394
22246
|
labelText: labelText,
|
|
22395
|
-
type: "text",
|
|
22396
22247
|
examples: this.examples(),
|
|
22397
22248
|
shouldShowExamples: this.shouldShowExamples(),
|
|
22398
22249
|
onFocus: this._handleFocus,
|
|
@@ -29120,6 +28971,10 @@ function snap(snapStep, point) {
|
|
|
29120
28971
|
return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
|
|
29121
28972
|
}
|
|
29122
28973
|
|
|
28974
|
+
function useDraggable(params) {
|
|
28975
|
+
return useMovable(params);
|
|
28976
|
+
}
|
|
28977
|
+
|
|
29123
28978
|
// The hitbox size of 48px by 48px is preserved from the legacy interactive
|
|
29124
28979
|
// graph.
|
|
29125
28980
|
const hitboxSizePx = 48;
|
|
@@ -29273,7 +29128,7 @@ function useControlPoint(point, color, onMovePoint) {
|
|
|
29273
29128
|
} = useGraphConfig();
|
|
29274
29129
|
const [focused, setFocused] = useState(false);
|
|
29275
29130
|
const keyboardHandleRef = useRef(null);
|
|
29276
|
-
|
|
29131
|
+
useDraggable({
|
|
29277
29132
|
gestureTarget: keyboardHandleRef,
|
|
29278
29133
|
point,
|
|
29279
29134
|
onMove: onMovePoint,
|
|
@@ -29282,7 +29137,7 @@ function useControlPoint(point, color, onMovePoint) {
|
|
|
29282
29137
|
const visiblePointRef = useRef(null);
|
|
29283
29138
|
const {
|
|
29284
29139
|
dragging
|
|
29285
|
-
} =
|
|
29140
|
+
} = useDraggable({
|
|
29286
29141
|
gestureTarget: visiblePointRef,
|
|
29287
29142
|
point,
|
|
29288
29143
|
onMove: onMovePoint,
|
|
@@ -29336,7 +29191,7 @@ const Line$1 = props => {
|
|
|
29336
29191
|
const line = useRef(null);
|
|
29337
29192
|
const {
|
|
29338
29193
|
dragging
|
|
29339
|
-
} =
|
|
29194
|
+
} = useDraggable({
|
|
29340
29195
|
gestureTarget: line,
|
|
29341
29196
|
point: start,
|
|
29342
29197
|
onMove: newPoint => {
|
|
@@ -29676,7 +29531,7 @@ const StyledMovablePoint = props => {
|
|
|
29676
29531
|
const snapToValue = snapTo != null ? snapTo : "grid";
|
|
29677
29532
|
const {
|
|
29678
29533
|
dragging
|
|
29679
|
-
} =
|
|
29534
|
+
} = useDraggable({
|
|
29680
29535
|
gestureTarget: elementRef,
|
|
29681
29536
|
point,
|
|
29682
29537
|
onMove,
|
|
@@ -29718,7 +29573,7 @@ const PolygonGraph = props => {
|
|
|
29718
29573
|
const snapToValue = snapTo != null ? snapTo : "grid";
|
|
29719
29574
|
const {
|
|
29720
29575
|
dragging
|
|
29721
|
-
} =
|
|
29576
|
+
} = useDraggable({
|
|
29722
29577
|
gestureTarget: ref,
|
|
29723
29578
|
point: dragReferencePoint,
|
|
29724
29579
|
onMove: newPoint => {
|
|
@@ -29881,7 +29736,7 @@ function MovableCircle(props) {
|
|
|
29881
29736
|
const draggableRef = useRef(null);
|
|
29882
29737
|
const {
|
|
29883
29738
|
dragging
|
|
29884
|
-
} =
|
|
29739
|
+
} = useDraggable({
|
|
29885
29740
|
gestureTarget: draggableRef,
|
|
29886
29741
|
point: center,
|
|
29887
29742
|
onMove,
|
|
@@ -30012,10 +29867,24 @@ function SinusoidGraph(props) {
|
|
|
30012
29867
|
coords
|
|
30013
29868
|
} = graphState;
|
|
30014
29869
|
|
|
30015
|
-
//
|
|
29870
|
+
// The coefficients are used to calculate the sinusoid equation, plot the graph, and to indicate
|
|
29871
|
+
// to content creators the currently selected "correct answer" in the Content Editor.
|
|
29872
|
+
// While we should technically never have invalid coordinates, we want to ensure that
|
|
29873
|
+
// we have a fallback so that the graph can still be plotted without crashing.
|
|
29874
|
+
const coeffRef = React.useRef({
|
|
29875
|
+
amplitude: 1,
|
|
29876
|
+
angularFrequency: 1,
|
|
29877
|
+
phase: 1,
|
|
29878
|
+
verticalOffset: 0
|
|
29879
|
+
});
|
|
30016
29880
|
const coeffs = getSinusoidCoefficients(coords);
|
|
29881
|
+
|
|
29882
|
+
// If the coefficients are valid, update the reference
|
|
29883
|
+
if (coeffs !== undefined) {
|
|
29884
|
+
coeffRef.current = coeffs;
|
|
29885
|
+
}
|
|
30017
29886
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Plot$3.OfX, {
|
|
30018
|
-
y: x => computeSine(x,
|
|
29887
|
+
y: x => computeSine(x, coeffRef.current),
|
|
30019
29888
|
color: color.blue
|
|
30020
29889
|
}), coords.map((coord, i) => /*#__PURE__*/React.createElement(StyledMovablePoint, {
|
|
30021
29890
|
key: "point-" + i,
|
|
@@ -30042,6 +29911,11 @@ const getSinusoidCoefficients = coords => {
|
|
|
30042
29911
|
const p1 = coords[0];
|
|
30043
29912
|
const p2 = coords[1];
|
|
30044
29913
|
|
|
29914
|
+
// If the x-coordinates are the same, we are unable to calculate the coefficients
|
|
29915
|
+
if (p2[0] === p1[0]) {
|
|
29916
|
+
return;
|
|
29917
|
+
}
|
|
29918
|
+
|
|
30045
29919
|
// Resulting coefficients are canonical for this sine curve
|
|
30046
29920
|
const amplitude = p2[1] - p1[1];
|
|
30047
29921
|
const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
|
|
@@ -31515,11 +31389,14 @@ function doMovePoint(state, action) {
|
|
|
31515
31389
|
}
|
|
31516
31390
|
case "sinusoid":
|
|
31517
31391
|
{
|
|
31518
|
-
// First, we need to
|
|
31519
|
-
// If they are, we don't want to move the point
|
|
31392
|
+
// First, we need to make sure to bound the new coordinates to the graph range
|
|
31520
31393
|
const destination = action.destination;
|
|
31394
|
+
const boundDestination = boundAndSnapToGrid(destination, state);
|
|
31395
|
+
|
|
31396
|
+
// Then, we need to verify that the new coordinates are not on the same
|
|
31397
|
+
// vertical line. If they are, then we don't want to move the point
|
|
31521
31398
|
const _newCoords = [...state.coords];
|
|
31522
|
-
_newCoords[action.index] =
|
|
31399
|
+
_newCoords[action.index] = boundDestination;
|
|
31523
31400
|
if (_newCoords[0][0] === _newCoords[1][0]) {
|
|
31524
31401
|
return state;
|
|
31525
31402
|
}
|
|
@@ -31528,7 +31405,7 @@ function doMovePoint(state, action) {
|
|
|
31528
31405
|
coords: setAtIndex({
|
|
31529
31406
|
array: state.coords,
|
|
31530
31407
|
index: action.index,
|
|
31531
|
-
newValue:
|
|
31408
|
+
newValue: boundDestination
|
|
31532
31409
|
})
|
|
31533
31410
|
});
|
|
31534
31411
|
}
|
|
@@ -31536,7 +31413,10 @@ function doMovePoint(state, action) {
|
|
|
31536
31413
|
{
|
|
31537
31414
|
// Set up the new coords and check if the quadratic coefficients are valid
|
|
31538
31415
|
const _newCoords2 = [...state.coords];
|
|
31539
|
-
|
|
31416
|
+
|
|
31417
|
+
// Bind the new destination to the graph range/snapStep and then get the quadratic coefficients
|
|
31418
|
+
const boundDestination = boundAndSnapToGrid(action.destination, state);
|
|
31419
|
+
_newCoords2[action.index] = boundDestination;
|
|
31540
31420
|
const QuadraticCoefficients = getQuadraticCoefficients(_newCoords2);
|
|
31541
31421
|
|
|
31542
31422
|
// If the new destination results in an invalid quadratic equation, we don't want to move the point
|
|
@@ -31548,7 +31428,7 @@ function doMovePoint(state, action) {
|
|
|
31548
31428
|
coords: setAtIndex({
|
|
31549
31429
|
array: state.coords,
|
|
31550
31430
|
index: action.index,
|
|
31551
|
-
newValue:
|
|
31431
|
+
newValue: boundDestination
|
|
31552
31432
|
})
|
|
31553
31433
|
});
|
|
31554
31434
|
}
|
|
@@ -31975,6 +31855,10 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
31975
31855
|
dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
|
|
31976
31856
|
}, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
|
|
31977
31857
|
const numSegments = graph.type === "segment" ? graph.numSegments : null;
|
|
31858
|
+
const numSides = graph.type === "polygon" ? graph.numSides : null;
|
|
31859
|
+
const snapTo = graph.type === "polygon" ? graph.snapTo : null;
|
|
31860
|
+
const showAngles = graph.type === "polygon" ? graph.showAngles : null;
|
|
31861
|
+
const showSides = graph.type === "polygon" ? graph.showSides : null;
|
|
31978
31862
|
const originalPropsRef = useRef(props);
|
|
31979
31863
|
const latestPropsRef = useLatestRef(props);
|
|
31980
31864
|
useEffect(() => {
|
|
@@ -31985,7 +31869,7 @@ const StatefulMafsGraph = /*#__PURE__*/React.forwardRef((props, ref) => {
|
|
|
31985
31869
|
if (latestPropsRef.current !== originalPropsRef.current) {
|
|
31986
31870
|
dispatch(reinitialize(latestPropsRef.current));
|
|
31987
31871
|
}
|
|
31988
|
-
}, [graph.type, numSegments, latestPropsRef]);
|
|
31872
|
+
}, [graph.type, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef]);
|
|
31989
31873
|
return /*#__PURE__*/React.createElement(MafsGraph, _extends({}, props, {
|
|
31990
31874
|
state: state,
|
|
31991
31875
|
dispatch: dispatch
|
|
@@ -33360,6 +33244,12 @@ class InteractiveGraph extends React.Component {
|
|
|
33360
33244
|
}
|
|
33361
33245
|
render() {
|
|
33362
33246
|
var _this$props$apiOption;
|
|
33247
|
+
if (this.props.graph.type === "polygon" && this.props.graph.numSides === "unlimited") {
|
|
33248
|
+
return /*#__PURE__*/React.createElement(LegacyInteractiveGraph, _extends({
|
|
33249
|
+
ref: this.legacyGraphRef
|
|
33250
|
+
}, this.props));
|
|
33251
|
+
}
|
|
33252
|
+
|
|
33363
33253
|
// Mafs shim
|
|
33364
33254
|
if ((_this$props$apiOption = this.props.apiOptions) != null && (_this$props$apiOption = _this$props$apiOption.flags) != null && (_this$props$apiOption = _this$props$apiOption["mafs"]) != null && _this$props$apiOption[this.props.graph.type]) {
|
|
33365
33255
|
const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
|
|
@@ -44152,7 +44042,7 @@ const init = function init(options) {
|
|
|
44152
44042
|
|
|
44153
44043
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44154
44044
|
const libName = "@khanacademy/perseus";
|
|
44155
|
-
const libVersion = "
|
|
44045
|
+
const libVersion = "24.0.0";
|
|
44156
44046
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44157
44047
|
|
|
44158
44048
|
const apiVersion = {
|