@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/index.js
CHANGED
|
@@ -634,8 +634,7 @@ class InnerMathInput extends React__namespace.Component {
|
|
|
634
634
|
active: false
|
|
635
635
|
}) : /*#__PURE__*/React__namespace.createElement(Clickable__default["default"], {
|
|
636
636
|
"aria-label": this.state.keypadOpen ? this.context.strings.closeKeypad : this.context.strings.openKeypad,
|
|
637
|
-
"
|
|
638
|
-
role: "switch",
|
|
637
|
+
role: "button",
|
|
639
638
|
onClick: () => this.state.keypadOpen ? this.closeKeypad() : this.openKeypad()
|
|
640
639
|
}, props => /*#__PURE__*/React__namespace.createElement(MathInputIcon, _extends({
|
|
641
640
|
active: this.state.keypadOpen
|
|
@@ -21203,251 +21202,6 @@ const styles$B = {
|
|
|
21203
21202
|
}
|
|
21204
21203
|
};
|
|
21205
21204
|
|
|
21206
|
-
/*
|
|
21207
|
-
* In this file, an `expression` is some portion of valid TeX enclosed in
|
|
21208
|
-
* curly brackets.
|
|
21209
|
-
*/
|
|
21210
|
-
|
|
21211
|
-
/*
|
|
21212
|
-
* Find the index at which an expression ends, i.e., has an unmatched
|
|
21213
|
-
* closing curly bracket. This method assumes that we start with a non-open
|
|
21214
|
-
* bracket character and end when we've seen more left than right brackets
|
|
21215
|
-
* (rather than assuming that we start with a bracket character and wait for
|
|
21216
|
-
* bracket equality).
|
|
21217
|
-
*/
|
|
21218
|
-
function findEndpoint(tex, currentIndex) {
|
|
21219
|
-
let bracketDepth = 0;
|
|
21220
|
-
for (let i = currentIndex, len = tex.length; i < len; i++) {
|
|
21221
|
-
const c = tex[i];
|
|
21222
|
-
if (c === "{") {
|
|
21223
|
-
bracketDepth++;
|
|
21224
|
-
} else if (c === "}") {
|
|
21225
|
-
bracketDepth--;
|
|
21226
|
-
}
|
|
21227
|
-
if (bracketDepth < 0) {
|
|
21228
|
-
return i;
|
|
21229
|
-
}
|
|
21230
|
-
}
|
|
21231
|
-
// If we never see unbalanced curly brackets, default to the
|
|
21232
|
-
// entire string
|
|
21233
|
-
return tex.length;
|
|
21234
|
-
}
|
|
21235
|
-
|
|
21236
|
-
/*
|
|
21237
|
-
* Parses an individual set of curly brackets into TeX.
|
|
21238
|
-
*/
|
|
21239
|
-
function parseNextExpression(tex, currentIndex, handler) {
|
|
21240
|
-
// Find the first '{' and grab subsequent TeX
|
|
21241
|
-
// Ex) tex: '{3}{7}', and we want the '3'
|
|
21242
|
-
const openBracketIndex = tex.indexOf("{", currentIndex);
|
|
21243
|
-
const nextExpIndex = openBracketIndex + 1;
|
|
21244
|
-
|
|
21245
|
-
// Truncate to only contain remaining TeX
|
|
21246
|
-
const endpoint = findEndpoint(tex, nextExpIndex);
|
|
21247
|
-
const expressionTeX = tex.substring(nextExpIndex, endpoint);
|
|
21248
|
-
const parsedExp = walkTex(expressionTeX, handler);
|
|
21249
|
-
return {
|
|
21250
|
-
endpoint: endpoint,
|
|
21251
|
-
expression: parsedExp
|
|
21252
|
-
};
|
|
21253
|
-
}
|
|
21254
|
-
function getNextFracIndex(tex, currentIndex) {
|
|
21255
|
-
const dfrac = "\\dfrac";
|
|
21256
|
-
const frac = "\\frac";
|
|
21257
|
-
const nextFrac = tex.indexOf(frac, currentIndex);
|
|
21258
|
-
const nextDFrac = tex.indexOf(dfrac, currentIndex);
|
|
21259
|
-
if (nextFrac > -1 && nextDFrac > -1) {
|
|
21260
|
-
return Math.min(nextFrac, nextDFrac);
|
|
21261
|
-
}
|
|
21262
|
-
if (nextFrac > -1) {
|
|
21263
|
-
return nextFrac;
|
|
21264
|
-
}
|
|
21265
|
-
if (nextDFrac > -1) {
|
|
21266
|
-
return nextDFrac;
|
|
21267
|
-
}
|
|
21268
|
-
return -1;
|
|
21269
|
-
}
|
|
21270
|
-
function walkTex(tex, handler) {
|
|
21271
|
-
if (!tex) {
|
|
21272
|
-
return "";
|
|
21273
|
-
}
|
|
21274
|
-
|
|
21275
|
-
// Ex) tex: '2 \dfrac {3}{7}'
|
|
21276
|
-
let parsedString = "";
|
|
21277
|
-
let currentIndex = 0;
|
|
21278
|
-
let nextFrac = getNextFracIndex(tex, currentIndex);
|
|
21279
|
-
|
|
21280
|
-
// For each \dfrac, find the two expressions (wrapped in {}) and recur
|
|
21281
|
-
while (nextFrac > -1) {
|
|
21282
|
-
// Gather first fragment, preceding \dfrac
|
|
21283
|
-
// Ex) parsedString: '2 '
|
|
21284
|
-
parsedString += tex.substring(currentIndex, nextFrac);
|
|
21285
|
-
|
|
21286
|
-
// Remove everything preceding \dfrac, which has been parsed
|
|
21287
|
-
currentIndex = nextFrac;
|
|
21288
|
-
|
|
21289
|
-
// Parse first expression and move index past it
|
|
21290
|
-
// Ex) firstParsedExpression.expression: '3'
|
|
21291
|
-
const firstParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
21292
|
-
currentIndex = firstParsedExpression.endpoint + 1;
|
|
21293
|
-
|
|
21294
|
-
// Parse second expression
|
|
21295
|
-
// Ex) secondParsedExpression.expression: '7'
|
|
21296
|
-
const secondParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
21297
|
-
currentIndex = secondParsedExpression.endpoint + 1;
|
|
21298
|
-
|
|
21299
|
-
// Add expressions to running total of parsed expressions
|
|
21300
|
-
if (parsedString.length) {
|
|
21301
|
-
parsedString += " ";
|
|
21302
|
-
}
|
|
21303
|
-
|
|
21304
|
-
// Apply a custom handler based on the parsed subexpressions
|
|
21305
|
-
parsedString += handler(firstParsedExpression.expression, secondParsedExpression.expression);
|
|
21306
|
-
|
|
21307
|
-
// Find next DFrac, relative to currentIndex
|
|
21308
|
-
nextFrac = getNextFracIndex(tex, currentIndex);
|
|
21309
|
-
}
|
|
21310
|
-
|
|
21311
|
-
// Add remaining TeX, which is \dfrac-free
|
|
21312
|
-
parsedString += tex.slice(currentIndex);
|
|
21313
|
-
return parsedString;
|
|
21314
|
-
}
|
|
21315
|
-
|
|
21316
|
-
/*
|
|
21317
|
-
* Modify a TeX expression, returning another TeX expression. The resulting
|
|
21318
|
-
* expression will have its innermost fractions stubbed out with \fracs
|
|
21319
|
-
* (as opposed to \dfracs). All other content will remain untouched.
|
|
21320
|
-
*/
|
|
21321
|
-
function modifyTex(tex) {
|
|
21322
|
-
function isNestedFraction(tex) {
|
|
21323
|
-
return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
|
|
21324
|
-
}
|
|
21325
|
-
const handler = function (exp1, exp2) {
|
|
21326
|
-
let prefix;
|
|
21327
|
-
if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
|
|
21328
|
-
prefix = "\\dfrac";
|
|
21329
|
-
} else {
|
|
21330
|
-
prefix = "\\frac";
|
|
21331
|
-
}
|
|
21332
|
-
return prefix + " {" + exp1 + "}{" + exp2 + "}";
|
|
21333
|
-
};
|
|
21334
|
-
return walkTex(tex, handler);
|
|
21335
|
-
}
|
|
21336
|
-
|
|
21337
|
-
/*
|
|
21338
|
-
* Parse a TeX expression into something interpretable by input-number.
|
|
21339
|
-
* The process is concerned with: (1) parsing fractions, i.e., \dfracs; and
|
|
21340
|
-
* (2) removing backslash-escaping from certain characters (right now, only
|
|
21341
|
-
* percent signs).
|
|
21342
|
-
*
|
|
21343
|
-
* The basic algorithm for handling \dfracs splits on \dfracs and then recurs
|
|
21344
|
-
* on the subsequent "expressions", i.e., the {} pairs that follow \dfrac. The
|
|
21345
|
-
* recursion is to allow for nested \dfrac elements.
|
|
21346
|
-
*
|
|
21347
|
-
* Backslash-escapes are removed with a simple search-and-replace.
|
|
21348
|
-
*/
|
|
21349
|
-
function parseTex(tex) {
|
|
21350
|
-
const handler = function (exp1, exp2) {
|
|
21351
|
-
return exp1 + "/" + exp2;
|
|
21352
|
-
};
|
|
21353
|
-
const texWithoutFracs = walkTex(tex, handler);
|
|
21354
|
-
return texWithoutFracs.replace("\\%", "%");
|
|
21355
|
-
}
|
|
21356
|
-
var TexWrangler = {
|
|
21357
|
-
parseTex,
|
|
21358
|
-
modifyTex
|
|
21359
|
-
};
|
|
21360
|
-
|
|
21361
|
-
/* eslint-disable react/sort-comp */
|
|
21362
|
-
const ModifyTex = TexWrangler.modifyTex;
|
|
21363
|
-
class MathOutput extends React__namespace.Component {
|
|
21364
|
-
static defaultProps = {
|
|
21365
|
-
value: "",
|
|
21366
|
-
onFocus: function () {},
|
|
21367
|
-
onBlur: function () {}
|
|
21368
|
-
};
|
|
21369
|
-
state = {
|
|
21370
|
-
focused: false,
|
|
21371
|
-
selectorNamespace: ___default["default"].uniqueId("math-output")
|
|
21372
|
-
};
|
|
21373
|
-
_getInputClassName = () => {
|
|
21374
|
-
let className = "math-output " + ClassNames.INPUT + " " + ClassNames.INTERACTIVE;
|
|
21375
|
-
if (this.state.focused) {
|
|
21376
|
-
className += " " + ClassNames.FOCUSED;
|
|
21377
|
-
}
|
|
21378
|
-
if (this.props.className) {
|
|
21379
|
-
className += " " + this.props.className;
|
|
21380
|
-
}
|
|
21381
|
-
return className;
|
|
21382
|
-
};
|
|
21383
|
-
_getDisplayValue = value => {
|
|
21384
|
-
// Cast from (potentially a) number to string
|
|
21385
|
-
let displayText;
|
|
21386
|
-
if (value != null) {
|
|
21387
|
-
displayText = "" + value;
|
|
21388
|
-
} else {
|
|
21389
|
-
displayText = "";
|
|
21390
|
-
}
|
|
21391
|
-
return ModifyTex(displayText);
|
|
21392
|
-
};
|
|
21393
|
-
render() {
|
|
21394
|
-
const divStyle = {
|
|
21395
|
-
textAlign: "center"
|
|
21396
|
-
};
|
|
21397
|
-
const {
|
|
21398
|
-
TeX
|
|
21399
|
-
} = getDependencies();
|
|
21400
|
-
return /*#__PURE__*/React__namespace.createElement("span", {
|
|
21401
|
-
// eslint-disable-next-line react/no-string-refs
|
|
21402
|
-
ref: "input",
|
|
21403
|
-
className: this._getInputClassName(),
|
|
21404
|
-
"aria-label": this.props.labelText,
|
|
21405
|
-
onMouseDown: this.focus,
|
|
21406
|
-
onTouchStart: this.focus
|
|
21407
|
-
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
21408
|
-
style: divStyle
|
|
21409
|
-
}, /*#__PURE__*/React__namespace.createElement(TeX, null, this._getDisplayValue(this.props.value))));
|
|
21410
|
-
}
|
|
21411
|
-
getValue = () => {
|
|
21412
|
-
return this.props.value;
|
|
21413
|
-
};
|
|
21414
|
-
focus = () => {
|
|
21415
|
-
if (!this.state.focused) {
|
|
21416
|
-
this.props.onFocus();
|
|
21417
|
-
this._bindBlurHandler();
|
|
21418
|
-
this.setState({
|
|
21419
|
-
focused: true
|
|
21420
|
-
});
|
|
21421
|
-
}
|
|
21422
|
-
};
|
|
21423
|
-
blur = () => {
|
|
21424
|
-
if (this.state.focused) {
|
|
21425
|
-
this.props.onBlur();
|
|
21426
|
-
this._unbindBlurHandler();
|
|
21427
|
-
this.setState({
|
|
21428
|
-
focused: false
|
|
21429
|
-
});
|
|
21430
|
-
}
|
|
21431
|
-
};
|
|
21432
|
-
_bindBlurHandler = () => {
|
|
21433
|
-
$__default["default"](document).bind("vclick." + this.state.selectorNamespace, e => {
|
|
21434
|
-
// Detect whether the target has our React DOM node as a parent
|
|
21435
|
-
const $closestWidget = $__default["default"](e.target).closest(
|
|
21436
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
21437
|
-
ReactDOM__default["default"].findDOMNode(this));
|
|
21438
|
-
if (!$closestWidget.length) {
|
|
21439
|
-
this.blur();
|
|
21440
|
-
}
|
|
21441
|
-
});
|
|
21442
|
-
};
|
|
21443
|
-
_unbindBlurHandler = () => {
|
|
21444
|
-
$__default["default"](document).unbind("." + this.state.selectorNamespace);
|
|
21445
|
-
};
|
|
21446
|
-
componentWillUnmount() {
|
|
21447
|
-
this._unbindBlurHandler();
|
|
21448
|
-
}
|
|
21449
|
-
}
|
|
21450
|
-
|
|
21451
21205
|
/* eslint-disable @khanacademy/ts-no-error-suppressions */
|
|
21452
21206
|
let lastId = 0;
|
|
21453
21207
|
function uniqueIdForInput() {
|
|
@@ -21788,7 +21542,6 @@ const {
|
|
|
21788
21542
|
class InputWithExamples extends React__namespace.Component {
|
|
21789
21543
|
static contextType = PerseusI18nContext;
|
|
21790
21544
|
static defaultProps = {
|
|
21791
|
-
type: "text",
|
|
21792
21545
|
shouldShowExamples: true,
|
|
21793
21546
|
onFocus: function () {},
|
|
21794
21547
|
onBlur: function () {},
|
|
@@ -21804,12 +21557,6 @@ class InputWithExamples extends React__namespace.Component {
|
|
|
21804
21557
|
return `input-with-examples-${btoa(this.props.id).replace(/=/g, "")}`;
|
|
21805
21558
|
};
|
|
21806
21559
|
_getInputClassName = () => {
|
|
21807
|
-
// <MathOutput> is a special component that manages its own class and
|
|
21808
|
-
// state, as it's a <span> that wants to act like an <input>.
|
|
21809
|
-
if (this.props.type === "tex") {
|
|
21810
|
-
return this.props.className;
|
|
21811
|
-
}
|
|
21812
|
-
|
|
21813
21560
|
// Otherwise, we need to add these INPUT and FOCUSED tags here.
|
|
21814
21561
|
let className = ClassNames.INPUT + " " + ClassNames.INTERACTIVE;
|
|
21815
21562
|
if (this.state.focused) {
|
|
@@ -21820,8 +21567,7 @@ class InputWithExamples extends React__namespace.Component {
|
|
|
21820
21567
|
}
|
|
21821
21568
|
return className;
|
|
21822
21569
|
};
|
|
21823
|
-
|
|
21824
|
-
// Minimal set of props, used by each input type
|
|
21570
|
+
_renderInput = () => {
|
|
21825
21571
|
const id = this._getUniqueId();
|
|
21826
21572
|
const inputProps = {
|
|
21827
21573
|
id: id,
|
|
@@ -21833,63 +21579,18 @@ class InputWithExamples extends React__namespace.Component {
|
|
|
21833
21579
|
onFocus: this._handleFocus,
|
|
21834
21580
|
onBlur: this._handleBlur,
|
|
21835
21581
|
disabled: this.props.disabled,
|
|
21836
|
-
style: this.props.style
|
|
21837
|
-
};
|
|
21838
|
-
if (this.props.type === "tex") {
|
|
21839
|
-
return inputProps;
|
|
21840
|
-
}
|
|
21841
|
-
|
|
21842
|
-
// Add useful props required for MATH and TEXT modes
|
|
21843
|
-
___default["default"].extend(inputProps, {
|
|
21582
|
+
style: this.props.style,
|
|
21844
21583
|
onChange: this.props.onChange,
|
|
21845
|
-
onTouchStart: captureScratchpadTouchStart$2
|
|
21846
|
-
|
|
21847
|
-
|
|
21848
|
-
|
|
21849
|
-
|
|
21850
|
-
|
|
21851
|
-
|
|
21852
|
-
buttonsVisible: this.props.buttonsVisible,
|
|
21853
|
-
convertDotToTimes: this.props.convertDotToTimes
|
|
21854
|
-
}, inputProps);
|
|
21855
|
-
}
|
|
21856
|
-
if (this.props.type === "text") {
|
|
21857
|
-
return ___default["default"].extend({
|
|
21858
|
-
autoCapitalize: "off",
|
|
21859
|
-
autoComplete: "off",
|
|
21860
|
-
autoCorrect: "off",
|
|
21861
|
-
spellCheck: "false"
|
|
21862
|
-
}, inputProps);
|
|
21863
|
-
}
|
|
21864
|
-
};
|
|
21865
|
-
_getComponentForInputType = () => {
|
|
21866
|
-
switch (this.props.type) {
|
|
21867
|
-
case "tex":
|
|
21868
|
-
return MathOutput;
|
|
21869
|
-
case "math":
|
|
21870
|
-
return MathInput;
|
|
21871
|
-
case "text":
|
|
21872
|
-
return TextInput;
|
|
21873
|
-
default:
|
|
21874
|
-
this.props.type;
|
|
21875
|
-
return null;
|
|
21876
|
-
}
|
|
21877
|
-
};
|
|
21878
|
-
_renderInput = () => {
|
|
21879
|
-
const inputProps = this._getPropsForInputType();
|
|
21880
|
-
const InputComponent = this._getComponentForInputType();
|
|
21881
|
-
return /*#__PURE__*/React__namespace.createElement(InputComponent, inputProps);
|
|
21584
|
+
onTouchStart: captureScratchpadTouchStart$2,
|
|
21585
|
+
autoCapitalize: "off",
|
|
21586
|
+
autoComplete: "off",
|
|
21587
|
+
autoCorrect: "off",
|
|
21588
|
+
spellCheck: "false"
|
|
21589
|
+
};
|
|
21590
|
+
return /*#__PURE__*/React__namespace.createElement(TextInput, inputProps);
|
|
21882
21591
|
};
|
|
21883
21592
|
render() {
|
|
21884
21593
|
const input = this._renderInput();
|
|
21885
|
-
|
|
21886
|
-
// Static rendering, which doesn't include the 'tooltip' logic that the
|
|
21887
|
-
// other types require, and is hence handled separately.
|
|
21888
|
-
if (this.props.type === "tex") {
|
|
21889
|
-
return input;
|
|
21890
|
-
}
|
|
21891
|
-
|
|
21892
|
-
// Else, we need to be able to show examples
|
|
21893
21594
|
const examplesContent = ___default["default"].map(this.props.examples, example => {
|
|
21894
21595
|
return "- " + example;
|
|
21895
21596
|
}).join("\n");
|
|
@@ -22025,6 +21726,161 @@ SimpleKeypadInput.propTypes = {
|
|
|
22025
21726
|
value: PropTypes__default["default"].oneOfType([PropTypes__default["default"].string, PropTypes__default["default"].number])
|
|
22026
21727
|
};
|
|
22027
21728
|
|
|
21729
|
+
/*
|
|
21730
|
+
* In this file, an `expression` is some portion of valid TeX enclosed in
|
|
21731
|
+
* curly brackets.
|
|
21732
|
+
*/
|
|
21733
|
+
|
|
21734
|
+
/*
|
|
21735
|
+
* Find the index at which an expression ends, i.e., has an unmatched
|
|
21736
|
+
* closing curly bracket. This method assumes that we start with a non-open
|
|
21737
|
+
* bracket character and end when we've seen more left than right brackets
|
|
21738
|
+
* (rather than assuming that we start with a bracket character and wait for
|
|
21739
|
+
* bracket equality).
|
|
21740
|
+
*/
|
|
21741
|
+
function findEndpoint(tex, currentIndex) {
|
|
21742
|
+
let bracketDepth = 0;
|
|
21743
|
+
for (let i = currentIndex, len = tex.length; i < len; i++) {
|
|
21744
|
+
const c = tex[i];
|
|
21745
|
+
if (c === "{") {
|
|
21746
|
+
bracketDepth++;
|
|
21747
|
+
} else if (c === "}") {
|
|
21748
|
+
bracketDepth--;
|
|
21749
|
+
}
|
|
21750
|
+
if (bracketDepth < 0) {
|
|
21751
|
+
return i;
|
|
21752
|
+
}
|
|
21753
|
+
}
|
|
21754
|
+
// If we never see unbalanced curly brackets, default to the
|
|
21755
|
+
// entire string
|
|
21756
|
+
return tex.length;
|
|
21757
|
+
}
|
|
21758
|
+
|
|
21759
|
+
/*
|
|
21760
|
+
* Parses an individual set of curly brackets into TeX.
|
|
21761
|
+
*/
|
|
21762
|
+
function parseNextExpression(tex, currentIndex, handler) {
|
|
21763
|
+
// Find the first '{' and grab subsequent TeX
|
|
21764
|
+
// Ex) tex: '{3}{7}', and we want the '3'
|
|
21765
|
+
const openBracketIndex = tex.indexOf("{", currentIndex);
|
|
21766
|
+
const nextExpIndex = openBracketIndex + 1;
|
|
21767
|
+
|
|
21768
|
+
// Truncate to only contain remaining TeX
|
|
21769
|
+
const endpoint = findEndpoint(tex, nextExpIndex);
|
|
21770
|
+
const expressionTeX = tex.substring(nextExpIndex, endpoint);
|
|
21771
|
+
const parsedExp = walkTex(expressionTeX, handler);
|
|
21772
|
+
return {
|
|
21773
|
+
endpoint: endpoint,
|
|
21774
|
+
expression: parsedExp
|
|
21775
|
+
};
|
|
21776
|
+
}
|
|
21777
|
+
function getNextFracIndex(tex, currentIndex) {
|
|
21778
|
+
const dfrac = "\\dfrac";
|
|
21779
|
+
const frac = "\\frac";
|
|
21780
|
+
const nextFrac = tex.indexOf(frac, currentIndex);
|
|
21781
|
+
const nextDFrac = tex.indexOf(dfrac, currentIndex);
|
|
21782
|
+
if (nextFrac > -1 && nextDFrac > -1) {
|
|
21783
|
+
return Math.min(nextFrac, nextDFrac);
|
|
21784
|
+
}
|
|
21785
|
+
if (nextFrac > -1) {
|
|
21786
|
+
return nextFrac;
|
|
21787
|
+
}
|
|
21788
|
+
if (nextDFrac > -1) {
|
|
21789
|
+
return nextDFrac;
|
|
21790
|
+
}
|
|
21791
|
+
return -1;
|
|
21792
|
+
}
|
|
21793
|
+
function walkTex(tex, handler) {
|
|
21794
|
+
if (!tex) {
|
|
21795
|
+
return "";
|
|
21796
|
+
}
|
|
21797
|
+
|
|
21798
|
+
// Ex) tex: '2 \dfrac {3}{7}'
|
|
21799
|
+
let parsedString = "";
|
|
21800
|
+
let currentIndex = 0;
|
|
21801
|
+
let nextFrac = getNextFracIndex(tex, currentIndex);
|
|
21802
|
+
|
|
21803
|
+
// For each \dfrac, find the two expressions (wrapped in {}) and recur
|
|
21804
|
+
while (nextFrac > -1) {
|
|
21805
|
+
// Gather first fragment, preceding \dfrac
|
|
21806
|
+
// Ex) parsedString: '2 '
|
|
21807
|
+
parsedString += tex.substring(currentIndex, nextFrac);
|
|
21808
|
+
|
|
21809
|
+
// Remove everything preceding \dfrac, which has been parsed
|
|
21810
|
+
currentIndex = nextFrac;
|
|
21811
|
+
|
|
21812
|
+
// Parse first expression and move index past it
|
|
21813
|
+
// Ex) firstParsedExpression.expression: '3'
|
|
21814
|
+
const firstParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
21815
|
+
currentIndex = firstParsedExpression.endpoint + 1;
|
|
21816
|
+
|
|
21817
|
+
// Parse second expression
|
|
21818
|
+
// Ex) secondParsedExpression.expression: '7'
|
|
21819
|
+
const secondParsedExpression = parseNextExpression(tex, currentIndex, handler);
|
|
21820
|
+
currentIndex = secondParsedExpression.endpoint + 1;
|
|
21821
|
+
|
|
21822
|
+
// Add expressions to running total of parsed expressions
|
|
21823
|
+
if (parsedString.length) {
|
|
21824
|
+
parsedString += " ";
|
|
21825
|
+
}
|
|
21826
|
+
|
|
21827
|
+
// Apply a custom handler based on the parsed subexpressions
|
|
21828
|
+
parsedString += handler(firstParsedExpression.expression, secondParsedExpression.expression);
|
|
21829
|
+
|
|
21830
|
+
// Find next DFrac, relative to currentIndex
|
|
21831
|
+
nextFrac = getNextFracIndex(tex, currentIndex);
|
|
21832
|
+
}
|
|
21833
|
+
|
|
21834
|
+
// Add remaining TeX, which is \dfrac-free
|
|
21835
|
+
parsedString += tex.slice(currentIndex);
|
|
21836
|
+
return parsedString;
|
|
21837
|
+
}
|
|
21838
|
+
|
|
21839
|
+
/*
|
|
21840
|
+
* Modify a TeX expression, returning another TeX expression. The resulting
|
|
21841
|
+
* expression will have its innermost fractions stubbed out with \fracs
|
|
21842
|
+
* (as opposed to \dfracs). All other content will remain untouched.
|
|
21843
|
+
*/
|
|
21844
|
+
function modifyTex(tex) {
|
|
21845
|
+
function isNestedFraction(tex) {
|
|
21846
|
+
return tex.indexOf("\\frac") > -1 || tex.indexOf("\\dfrac") > -1;
|
|
21847
|
+
}
|
|
21848
|
+
const handler = function (exp1, exp2) {
|
|
21849
|
+
let prefix;
|
|
21850
|
+
if (isNestedFraction(exp1) || isNestedFraction(exp2)) {
|
|
21851
|
+
prefix = "\\dfrac";
|
|
21852
|
+
} else {
|
|
21853
|
+
prefix = "\\frac";
|
|
21854
|
+
}
|
|
21855
|
+
return prefix + " {" + exp1 + "}{" + exp2 + "}";
|
|
21856
|
+
};
|
|
21857
|
+
return walkTex(tex, handler);
|
|
21858
|
+
}
|
|
21859
|
+
|
|
21860
|
+
/*
|
|
21861
|
+
* Parse a TeX expression into something interpretable by input-number.
|
|
21862
|
+
* The process is concerned with: (1) parsing fractions, i.e., \dfracs; and
|
|
21863
|
+
* (2) removing backslash-escaping from certain characters (right now, only
|
|
21864
|
+
* percent signs).
|
|
21865
|
+
*
|
|
21866
|
+
* The basic algorithm for handling \dfracs splits on \dfracs and then recurs
|
|
21867
|
+
* on the subsequent "expressions", i.e., the {} pairs that follow \dfrac. The
|
|
21868
|
+
* recursion is to allow for nested \dfrac elements.
|
|
21869
|
+
*
|
|
21870
|
+
* Backslash-escapes are removed with a simple search-and-replace.
|
|
21871
|
+
*/
|
|
21872
|
+
function parseTex(tex) {
|
|
21873
|
+
const handler = function (exp1, exp2) {
|
|
21874
|
+
return exp1 + "/" + exp2;
|
|
21875
|
+
};
|
|
21876
|
+
const texWithoutFracs = walkTex(tex, handler);
|
|
21877
|
+
return texWithoutFracs.replace("\\%", "%");
|
|
21878
|
+
}
|
|
21879
|
+
var TexWrangler = {
|
|
21880
|
+
parseTex,
|
|
21881
|
+
modifyTex
|
|
21882
|
+
};
|
|
21883
|
+
|
|
22028
21884
|
/* eslint-disable react/sort-comp */
|
|
22029
21885
|
const ParseTex$1 = TexWrangler.parseTex;
|
|
22030
21886
|
const answerTypes = {
|
|
@@ -22145,7 +22001,6 @@ class InputNumber extends React__namespace.Component {
|
|
|
22145
22001
|
value: this.props.currentValue,
|
|
22146
22002
|
onChange: this.handleChange,
|
|
22147
22003
|
style: inputStyles,
|
|
22148
|
-
type: "text",
|
|
22149
22004
|
examples: this.examples(),
|
|
22150
22005
|
shouldShowExamples: this.shouldShowExamples(),
|
|
22151
22006
|
onFocus: this._handleFocus,
|
|
@@ -22612,7 +22467,6 @@ class NumericInput extends React__namespace.Component {
|
|
|
22612
22467
|
value: this.props.currentValue,
|
|
22613
22468
|
onChange: this.handleChange,
|
|
22614
22469
|
labelText: labelText,
|
|
22615
|
-
type: "text",
|
|
22616
22470
|
examples: this.examples(),
|
|
22617
22471
|
shouldShowExamples: this.shouldShowExamples(),
|
|
22618
22472
|
onFocus: this._handleFocus,
|
|
@@ -29410,6 +29264,10 @@ function snap(snapStep, point) {
|
|
|
29410
29264
|
return [Math.round(requestedX / snapX) * snapX, Math.round(requestedY / snapY) * snapY];
|
|
29411
29265
|
}
|
|
29412
29266
|
|
|
29267
|
+
function useDraggable(params) {
|
|
29268
|
+
return mafs.useMovable(params);
|
|
29269
|
+
}
|
|
29270
|
+
|
|
29413
29271
|
// The hitbox size of 48px by 48px is preserved from the legacy interactive
|
|
29414
29272
|
// graph.
|
|
29415
29273
|
const hitboxSizePx = 48;
|
|
@@ -29565,7 +29423,7 @@ function useControlPoint(point, color, onMovePoint) {
|
|
|
29565
29423
|
} = useGraphConfig();
|
|
29566
29424
|
const [focused, setFocused] = React.useState(false);
|
|
29567
29425
|
const keyboardHandleRef = React.useRef(null);
|
|
29568
|
-
|
|
29426
|
+
useDraggable({
|
|
29569
29427
|
gestureTarget: keyboardHandleRef,
|
|
29570
29428
|
point,
|
|
29571
29429
|
onMove: onMovePoint,
|
|
@@ -29574,7 +29432,7 @@ function useControlPoint(point, color, onMovePoint) {
|
|
|
29574
29432
|
const visiblePointRef = React.useRef(null);
|
|
29575
29433
|
const {
|
|
29576
29434
|
dragging
|
|
29577
|
-
} =
|
|
29435
|
+
} = useDraggable({
|
|
29578
29436
|
gestureTarget: visiblePointRef,
|
|
29579
29437
|
point,
|
|
29580
29438
|
onMove: onMovePoint,
|
|
@@ -29628,7 +29486,7 @@ const Line$1 = props => {
|
|
|
29628
29486
|
const line = React.useRef(null);
|
|
29629
29487
|
const {
|
|
29630
29488
|
dragging
|
|
29631
|
-
} =
|
|
29489
|
+
} = useDraggable({
|
|
29632
29490
|
gestureTarget: line,
|
|
29633
29491
|
point: start,
|
|
29634
29492
|
onMove: newPoint => {
|
|
@@ -29976,7 +29834,7 @@ const StyledMovablePoint = props => {
|
|
|
29976
29834
|
const snapToValue = snapTo ?? "grid";
|
|
29977
29835
|
const {
|
|
29978
29836
|
dragging
|
|
29979
|
-
} =
|
|
29837
|
+
} = useDraggable({
|
|
29980
29838
|
gestureTarget: elementRef,
|
|
29981
29839
|
point,
|
|
29982
29840
|
onMove,
|
|
@@ -30018,7 +29876,7 @@ const PolygonGraph = props => {
|
|
|
30018
29876
|
const snapToValue = snapTo ?? "grid";
|
|
30019
29877
|
const {
|
|
30020
29878
|
dragging
|
|
30021
|
-
} =
|
|
29879
|
+
} = useDraggable({
|
|
30022
29880
|
gestureTarget: ref,
|
|
30023
29881
|
point: dragReferencePoint,
|
|
30024
29882
|
onMove: newPoint => {
|
|
@@ -30193,7 +30051,7 @@ function MovableCircle(props) {
|
|
|
30193
30051
|
const draggableRef = React.useRef(null);
|
|
30194
30052
|
const {
|
|
30195
30053
|
dragging
|
|
30196
|
-
} =
|
|
30054
|
+
} = useDraggable({
|
|
30197
30055
|
gestureTarget: draggableRef,
|
|
30198
30056
|
point: center,
|
|
30199
30057
|
onMove,
|
|
@@ -30324,10 +30182,24 @@ function SinusoidGraph(props) {
|
|
|
30324
30182
|
coords
|
|
30325
30183
|
} = graphState;
|
|
30326
30184
|
|
|
30327
|
-
//
|
|
30185
|
+
// The coefficients are used to calculate the sinusoid equation, plot the graph, and to indicate
|
|
30186
|
+
// to content creators the currently selected "correct answer" in the Content Editor.
|
|
30187
|
+
// While we should technically never have invalid coordinates, we want to ensure that
|
|
30188
|
+
// we have a fallback so that the graph can still be plotted without crashing.
|
|
30189
|
+
const coeffRef = React__namespace.useRef({
|
|
30190
|
+
amplitude: 1,
|
|
30191
|
+
angularFrequency: 1,
|
|
30192
|
+
phase: 1,
|
|
30193
|
+
verticalOffset: 0
|
|
30194
|
+
});
|
|
30328
30195
|
const coeffs = getSinusoidCoefficients(coords);
|
|
30196
|
+
|
|
30197
|
+
// If the coefficients are valid, update the reference
|
|
30198
|
+
if (coeffs !== undefined) {
|
|
30199
|
+
coeffRef.current = coeffs;
|
|
30200
|
+
}
|
|
30329
30201
|
return /*#__PURE__*/React__namespace.createElement(React__namespace.Fragment, null, /*#__PURE__*/React__namespace.createElement(mafs.Plot.OfX, {
|
|
30330
|
-
y: x => computeSine(x,
|
|
30202
|
+
y: x => computeSine(x, coeffRef.current),
|
|
30331
30203
|
color: wonderBlocksTokens.color.blue
|
|
30332
30204
|
}), coords.map((coord, i) => /*#__PURE__*/React__namespace.createElement(StyledMovablePoint, {
|
|
30333
30205
|
key: "point-" + i,
|
|
@@ -30354,6 +30226,11 @@ const getSinusoidCoefficients = coords => {
|
|
|
30354
30226
|
const p1 = coords[0];
|
|
30355
30227
|
const p2 = coords[1];
|
|
30356
30228
|
|
|
30229
|
+
// If the x-coordinates are the same, we are unable to calculate the coefficients
|
|
30230
|
+
if (p2[0] === p1[0]) {
|
|
30231
|
+
return;
|
|
30232
|
+
}
|
|
30233
|
+
|
|
30357
30234
|
// Resulting coefficients are canonical for this sine curve
|
|
30358
30235
|
const amplitude = p2[1] - p1[1];
|
|
30359
30236
|
const angularFrequency = Math.PI / (2 * (p2[0] - p1[0]));
|
|
@@ -31847,11 +31724,14 @@ function doMovePoint(state, action) {
|
|
|
31847
31724
|
}
|
|
31848
31725
|
case "sinusoid":
|
|
31849
31726
|
{
|
|
31850
|
-
// First, we need to
|
|
31851
|
-
// If they are, we don't want to move the point
|
|
31727
|
+
// First, we need to make sure to bound the new coordinates to the graph range
|
|
31852
31728
|
const destination = action.destination;
|
|
31729
|
+
const boundDestination = boundAndSnapToGrid(destination, state);
|
|
31730
|
+
|
|
31731
|
+
// Then, we need to verify that the new coordinates are not on the same
|
|
31732
|
+
// vertical line. If they are, then we don't want to move the point
|
|
31853
31733
|
const newCoords = [...state.coords];
|
|
31854
|
-
newCoords[action.index] =
|
|
31734
|
+
newCoords[action.index] = boundDestination;
|
|
31855
31735
|
if (newCoords[0][0] === newCoords[1][0]) {
|
|
31856
31736
|
return state;
|
|
31857
31737
|
}
|
|
@@ -31861,7 +31741,7 @@ function doMovePoint(state, action) {
|
|
|
31861
31741
|
coords: setAtIndex({
|
|
31862
31742
|
array: state.coords,
|
|
31863
31743
|
index: action.index,
|
|
31864
|
-
newValue:
|
|
31744
|
+
newValue: boundDestination
|
|
31865
31745
|
})
|
|
31866
31746
|
};
|
|
31867
31747
|
}
|
|
@@ -31869,7 +31749,10 @@ function doMovePoint(state, action) {
|
|
|
31869
31749
|
{
|
|
31870
31750
|
// Set up the new coords and check if the quadratic coefficients are valid
|
|
31871
31751
|
const newCoords = [...state.coords];
|
|
31872
|
-
|
|
31752
|
+
|
|
31753
|
+
// Bind the new destination to the graph range/snapStep and then get the quadratic coefficients
|
|
31754
|
+
const boundDestination = boundAndSnapToGrid(action.destination, state);
|
|
31755
|
+
newCoords[action.index] = boundDestination;
|
|
31873
31756
|
const QuadraticCoefficients = getQuadraticCoefficients(newCoords);
|
|
31874
31757
|
|
|
31875
31758
|
// If the new destination results in an invalid quadratic equation, we don't want to move the point
|
|
@@ -31882,7 +31765,7 @@ function doMovePoint(state, action) {
|
|
|
31882
31765
|
coords: setAtIndex({
|
|
31883
31766
|
array: state.coords,
|
|
31884
31767
|
index: action.index,
|
|
31885
|
-
newValue:
|
|
31768
|
+
newValue: boundDestination
|
|
31886
31769
|
})
|
|
31887
31770
|
};
|
|
31888
31771
|
}
|
|
@@ -32325,6 +32208,10 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
32325
32208
|
dispatch(changeRange([[xMinRange, xMaxRange], [yMinRange, yMaxRange]]));
|
|
32326
32209
|
}, [dispatch, xMinRange, xMaxRange, yMinRange, yMaxRange]);
|
|
32327
32210
|
const numSegments = graph.type === "segment" ? graph.numSegments : null;
|
|
32211
|
+
const numSides = graph.type === "polygon" ? graph.numSides : null;
|
|
32212
|
+
const snapTo = graph.type === "polygon" ? graph.snapTo : null;
|
|
32213
|
+
const showAngles = graph.type === "polygon" ? graph.showAngles : null;
|
|
32214
|
+
const showSides = graph.type === "polygon" ? graph.showSides : null;
|
|
32328
32215
|
const originalPropsRef = React.useRef(props);
|
|
32329
32216
|
const latestPropsRef = wonderBlocksCore.useLatestRef(props);
|
|
32330
32217
|
React.useEffect(() => {
|
|
@@ -32335,7 +32222,7 @@ const StatefulMafsGraph = /*#__PURE__*/React__namespace.forwardRef((props, ref)
|
|
|
32335
32222
|
if (latestPropsRef.current !== originalPropsRef.current) {
|
|
32336
32223
|
dispatch(reinitialize(latestPropsRef.current));
|
|
32337
32224
|
}
|
|
32338
|
-
}, [graph.type, numSegments, latestPropsRef]);
|
|
32225
|
+
}, [graph.type, numSegments, numSides, snapTo, showAngles, showSides, latestPropsRef]);
|
|
32339
32226
|
return /*#__PURE__*/React__namespace.createElement(MafsGraph, _extends({}, props, {
|
|
32340
32227
|
state: state,
|
|
32341
32228
|
dispatch: dispatch
|
|
@@ -33698,6 +33585,12 @@ class InteractiveGraph extends React__namespace.Component {
|
|
|
33698
33585
|
return InteractiveGraph.validate(this.getUserInput?.(), rubric, this);
|
|
33699
33586
|
}
|
|
33700
33587
|
render() {
|
|
33588
|
+
if (this.props.graph.type === "polygon" && this.props.graph.numSides === "unlimited") {
|
|
33589
|
+
return /*#__PURE__*/React__namespace.createElement(LegacyInteractiveGraph, _extends({
|
|
33590
|
+
ref: this.legacyGraphRef
|
|
33591
|
+
}, this.props));
|
|
33592
|
+
}
|
|
33593
|
+
|
|
33701
33594
|
// Mafs shim
|
|
33702
33595
|
if (this.props.apiOptions?.flags?.["mafs"]?.[this.props.graph.type]) {
|
|
33703
33596
|
const box = getInteractiveBoxFromSizeClass(this.props.containerSizeClass);
|
|
@@ -44497,7 +44390,7 @@ const init = function (options) {
|
|
|
44497
44390
|
|
|
44498
44391
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
44499
44392
|
const libName = "@khanacademy/perseus";
|
|
44500
|
-
const libVersion = "
|
|
44393
|
+
const libVersion = "24.0.0";
|
|
44501
44394
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
44502
44395
|
|
|
44503
44396
|
const apiVersion = {
|