@khanacademy/math-input 16.1.2 → 16.3.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/CHANGELOG.md +14 -0
- package/dist/components/input/math-input.d.ts +4 -6
- package/dist/es/index.js +105 -48
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +99 -39
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +12 -0
- package/package.json +1 -1
- package/src/components/__tests__/integration.test.tsx +17 -1
- package/src/components/input/math-input.tsx +84 -51
- package/src/components/keypad/__tests__/keypad.test.tsx +37 -0
- package/src/components/keypad/shared-keys.tsx +6 -1
- package/src/index.ts +1 -0
- package/src/utils.test.ts +33 -0
- package/src/utils.ts +45 -1
- package/tsconfig-build.tsbuildinfo +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export { default as DesktopKeypad } from "./components/keypad";
|
|
|
13
13
|
export { KeypadContext, StatefulKeypadContextProvider, } from "./components/keypad-context";
|
|
14
14
|
export { keypadElementPropType } from "./components/prop-types";
|
|
15
15
|
export type { KeypadAPI, KeypadConfiguration } from "./types";
|
|
16
|
+
export { convertDotToTimesByLocale } from "./utils";
|
|
16
17
|
export type { default as Keys } from "./data/keys";
|
|
17
18
|
export { KeyArray } from "./data/keys";
|
|
18
19
|
export { default as KeyConfigs } from "./data/key-configs";
|
package/dist/index.js
CHANGED
|
@@ -48,7 +48,7 @@ var PropTypes__default = /*#__PURE__*/_interopDefaultLegacy(PropTypes);
|
|
|
48
48
|
|
|
49
49
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
50
50
|
const libName = "@khanacademy/math-input";
|
|
51
|
-
const libVersion = "16.
|
|
51
|
+
const libVersion = "16.3.0";
|
|
52
52
|
perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
53
53
|
|
|
54
54
|
function _extends() {
|
|
@@ -821,6 +821,30 @@ const DecimalSeparator = {
|
|
|
821
821
|
// - Some languages/locales use different decimal separators than the ones
|
|
822
822
|
// listed here. Much of the Arab world uses U+066C.
|
|
823
823
|
const decimalSeparator = i18n.getDecimalSeparator() === "," ? DecimalSeparator.COMMA : DecimalSeparator.PERIOD;
|
|
824
|
+
const CDOT_ONLY = ["az", "cs", "da", "de", "hu", "hy", "kk", "ky", "lt", "lv", "nb", "sk", "sr", "sv", "uz"];
|
|
825
|
+
const TIMES_ONLY = ["fr", "tr", "pt-pt"];
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* convertDotToTimes (aka `times`) is an option the content creators have to
|
|
829
|
+
* use × (TIMES) rather than · (CDOT) for multiplication (for younger learners).
|
|
830
|
+
* Some locales _only_ use one or the other for all multiplication regardless
|
|
831
|
+
* of age.
|
|
832
|
+
*
|
|
833
|
+
* convertDotToTimesByLocale overrides convertDotToTimes for those locales.
|
|
834
|
+
*
|
|
835
|
+
* @param {boolean} convertDotToTimes - the setting set by content creators
|
|
836
|
+
* @returns {boolean} - true to convert to × (TIMES), false to use · (CDOT)
|
|
837
|
+
*/
|
|
838
|
+
function convertDotToTimesByLocale(convertDotToTimes) {
|
|
839
|
+
const locale = i18n.getLocale();
|
|
840
|
+
if (CDOT_ONLY.includes(locale)) {
|
|
841
|
+
return false;
|
|
842
|
+
}
|
|
843
|
+
if (TIMES_ONLY.includes(locale)) {
|
|
844
|
+
return true;
|
|
845
|
+
}
|
|
846
|
+
return convertDotToTimes;
|
|
847
|
+
}
|
|
824
848
|
|
|
825
849
|
function handleLeftArrow(mathField, cursor) {
|
|
826
850
|
// If we're inside a function, and just after the left parentheses, we
|
|
@@ -1399,6 +1423,8 @@ class MathInput extends React__namespace.Component {
|
|
|
1399
1423
|
|
|
1400
1424
|
// @ts-expect-error - TS2564 - Property 'blurOnTouchEndOutside' has no initializer and is not definitely assigned in the constructor.
|
|
1401
1425
|
|
|
1426
|
+
// @ts-expect-error - TS2564 - Property 'blurOnClickOutside' has no initializer and is not definitely assigned in the constructor.
|
|
1427
|
+
|
|
1402
1428
|
// @ts-expect-error - TS2564 - Property '_container' has no initializer and is not definitely assigned in the constructor.
|
|
1403
1429
|
|
|
1404
1430
|
// @ts-expect-error - TS2564 - Property '_containerBounds' has no initializer and is not definitely assigned in the constructor.
|
|
@@ -1446,6 +1472,16 @@ class MathInput extends React__namespace.Component {
|
|
|
1446
1472
|
this._container = ReactDOM__default["default"].findDOMNode(this);
|
|
1447
1473
|
this._root = this._container.querySelector(".mq-root-block");
|
|
1448
1474
|
this._root.addEventListener("scroll", this._handleScroll);
|
|
1475
|
+
const isWithinKeypadBounds = (x, y) => {
|
|
1476
|
+
const bounds = this._getKeypadBounds();
|
|
1477
|
+
|
|
1478
|
+
// If there are no bounds, then the keypad is not mounted, so we
|
|
1479
|
+
// assume that the event is not within the keypad bounds.
|
|
1480
|
+
if (!bounds) {
|
|
1481
|
+
return false;
|
|
1482
|
+
}
|
|
1483
|
+
return bounds.left <= x && bounds.right >= x && bounds.top <= y && bounds.bottom >= y || bounds.bottom < y;
|
|
1484
|
+
};
|
|
1449
1485
|
|
|
1450
1486
|
// Record the initial scroll displacement on touch start. This allows
|
|
1451
1487
|
// us to detect whether a touch event was a scroll and only blur the
|
|
@@ -1463,10 +1499,9 @@ class MathInput extends React__namespace.Component {
|
|
|
1463
1499
|
if (!this._container.contains(evt.target)) {
|
|
1464
1500
|
let touchDidStartInOrBelowKeypad = false;
|
|
1465
1501
|
if (this.props.keypadElement && this.props.keypadElement.getDOMNode()) {
|
|
1466
|
-
const bounds = this._getKeypadBounds();
|
|
1467
1502
|
for (let i = 0; i < evt.changedTouches.length; i++) {
|
|
1468
1503
|
const [x, y] = [evt.changedTouches[i].clientX, evt.changedTouches[i].clientY];
|
|
1469
|
-
if (
|
|
1504
|
+
if (isWithinKeypadBounds(x, y)) {
|
|
1470
1505
|
touchDidStartInOrBelowKeypad = true;
|
|
1471
1506
|
break;
|
|
1472
1507
|
}
|
|
@@ -1502,25 +1537,31 @@ class MathInput extends React__namespace.Component {
|
|
|
1502
1537
|
this.dragListener.detach();
|
|
1503
1538
|
}
|
|
1504
1539
|
};
|
|
1540
|
+
|
|
1541
|
+
// We want to allow the user to blur the input by clicking outside of it
|
|
1542
|
+
// when using ChromeOS third-party browsers that use mobile user agents,
|
|
1543
|
+
// but don't actually simulate touch events.
|
|
1544
|
+
this.blurOnClickOutside = evt => {
|
|
1545
|
+
if (this.state.focused) {
|
|
1546
|
+
if (!this._container.contains(evt.target)) {
|
|
1547
|
+
if (this.props.keypadElement && this.props.keypadElement.getDOMNode()) {
|
|
1548
|
+
const [x, y] = [evt.clientX, evt.clientY];
|
|
1549
|
+
// We only want to blur if the click is above the keypad,
|
|
1550
|
+
// to the left of the keypad, or to the right of the keypad.
|
|
1551
|
+
// The reasoning for not blurring for any clicks below the keypad is
|
|
1552
|
+
// that the keypad may be anchored above the 'Check answer' bottom bar,
|
|
1553
|
+
// in which case we don't want to dismiss the keypad on check.
|
|
1554
|
+
if (!isWithinKeypadBounds(x, y)) {
|
|
1555
|
+
this.blur();
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1505
1561
|
window.addEventListener("touchstart", this.recordTouchStartOutside);
|
|
1506
1562
|
window.addEventListener("touchend", this.blurOnTouchEndOutside);
|
|
1507
1563
|
window.addEventListener("touchcancel", this.blurOnTouchEndOutside);
|
|
1508
|
-
|
|
1509
|
-
// HACK(benkomalo): if the window resizes, the keypad bounds can
|
|
1510
|
-
// change. That's a bit peeking into the internals of the keypad
|
|
1511
|
-
// itself, since we know bounds can change only when the viewport
|
|
1512
|
-
// changes, but seems like a rare enough thing to get wrong that it's
|
|
1513
|
-
// not worth wiring up extra things for the technical "purity" of
|
|
1514
|
-
// having the keypad notify of changes to us.
|
|
1515
|
-
window.addEventListener("resize", this._clearKeypadBoundsCache);
|
|
1516
|
-
window.addEventListener("orientationchange", this._clearKeypadBoundsCache);
|
|
1517
|
-
}
|
|
1518
|
-
|
|
1519
|
-
// eslint-disable-next-line react/no-unsafe
|
|
1520
|
-
UNSAFE_componentWillReceiveProps(props) {
|
|
1521
|
-
if (this.props.keypadElement !== props.keypadElement) {
|
|
1522
|
-
this._clearKeypadBoundsCache();
|
|
1523
|
-
}
|
|
1564
|
+
window.addEventListener("click", this.blurOnClickOutside);
|
|
1524
1565
|
}
|
|
1525
1566
|
componentDidUpdate(prevProps, prevState) {
|
|
1526
1567
|
if (this.mathField.getContent() !== this.props.value) {
|
|
@@ -1535,18 +1576,8 @@ class MathInput extends React__namespace.Component {
|
|
|
1535
1576
|
window.removeEventListener("touchstart", this.recordTouchStartOutside);
|
|
1536
1577
|
window.removeEventListener("touchend", this.blurOnTouchEndOutside);
|
|
1537
1578
|
window.removeEventListener("touchcancel", this.blurOnTouchEndOutside);
|
|
1538
|
-
|
|
1539
|
-
window.removeEventListener("resize", this._clearKeypadBoundsCache());
|
|
1540
|
-
window.removeEventListener("orientationchange",
|
|
1541
|
-
// @ts-expect-error - TS2769 - No overload matches this call.
|
|
1542
|
-
this._clearKeypadBoundsCache());
|
|
1579
|
+
window.removeEventListener("click", this.blurOnClickOutside);
|
|
1543
1580
|
}
|
|
1544
|
-
_clearKeypadBoundsCache = () => {
|
|
1545
|
-
this._keypadBounds = null;
|
|
1546
|
-
};
|
|
1547
|
-
_cacheKeypadBounds = keypadNode => {
|
|
1548
|
-
this._keypadBounds = keypadNode.getBoundingClientRect();
|
|
1549
|
-
};
|
|
1550
1581
|
_updateInputPadding = () => {
|
|
1551
1582
|
this._container = ReactDOM__default["default"].findDOMNode(this);
|
|
1552
1583
|
this._root = this._container.querySelector(".mq-root-block");
|
|
@@ -1556,14 +1587,16 @@ class MathInput extends React__namespace.Component {
|
|
|
1556
1587
|
this._root.style.fontSize = `${fontSizePt}pt`;
|
|
1557
1588
|
};
|
|
1558
1589
|
|
|
1559
|
-
/**
|
|
1560
|
-
_getKeypadBounds
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1590
|
+
/** Returns the current bounds of the keypadElement */
|
|
1591
|
+
_getKeypadBounds() {
|
|
1592
|
+
const keypadNode = this.props.keypadElement?.getDOMNode();
|
|
1593
|
+
|
|
1594
|
+
// If the keypad is mounted, return its bounds. Otherwise, return null.
|
|
1595
|
+
if (keypadNode instanceof Element) {
|
|
1596
|
+
return keypadNode.getBoundingClientRect();
|
|
1564
1597
|
}
|
|
1565
|
-
return
|
|
1566
|
-
}
|
|
1598
|
+
return null;
|
|
1599
|
+
}
|
|
1567
1600
|
_updateCursorHandle = animateIntoPosition => {
|
|
1568
1601
|
const containerBounds = this._container.getBoundingClientRect();
|
|
1569
1602
|
const cursor = this._container.querySelector(".mq-cursor");
|
|
@@ -1861,6 +1894,32 @@ class MathInput extends React__namespace.Component {
|
|
|
1861
1894
|
this.focus();
|
|
1862
1895
|
}
|
|
1863
1896
|
};
|
|
1897
|
+
|
|
1898
|
+
// We want to allow the user to be able to focus the input via click
|
|
1899
|
+
// when using ChromeOS third-party browsers that use mobile user agents,
|
|
1900
|
+
// but don't actually simulate touch events.
|
|
1901
|
+
handleClick = e => {
|
|
1902
|
+
e.stopPropagation();
|
|
1903
|
+
|
|
1904
|
+
// Hide the cursor handle on click
|
|
1905
|
+
this._hideCursorHandle();
|
|
1906
|
+
|
|
1907
|
+
// Cache the container bounds, so as to avoid re-computing. If we don't
|
|
1908
|
+
// have any content, then it's not necessary, since the cursor can't be
|
|
1909
|
+
// moved anyway.
|
|
1910
|
+
if (this.mathField.getContent() !== "") {
|
|
1911
|
+
this._containerBounds = this._container.getBoundingClientRect();
|
|
1912
|
+
|
|
1913
|
+
// Make the cursor visible and set the handle-less cursor's
|
|
1914
|
+
// location.
|
|
1915
|
+
this._insertCursorAtClosestNode(e.clientX, e.clientY);
|
|
1916
|
+
}
|
|
1917
|
+
|
|
1918
|
+
// Trigger a focus event, if we're not already focused.
|
|
1919
|
+
if (!this.state.focused) {
|
|
1920
|
+
this.focus();
|
|
1921
|
+
}
|
|
1922
|
+
};
|
|
1864
1923
|
handleTouchMove = e => {
|
|
1865
1924
|
e.stopPropagation();
|
|
1866
1925
|
|
|
@@ -2084,7 +2143,7 @@ class MathInput extends React__namespace.Component {
|
|
|
2084
2143
|
onTouchStart: this.handleTouchStart,
|
|
2085
2144
|
onTouchMove: this.handleTouchMove,
|
|
2086
2145
|
onTouchEnd: this.handleTouchEnd,
|
|
2087
|
-
onClick:
|
|
2146
|
+
onClick: this.handleClick,
|
|
2088
2147
|
role: "textbox",
|
|
2089
2148
|
ariaLabel: ariaLabel
|
|
2090
2149
|
}, /*#__PURE__*/React__namespace.createElement("div", {
|
|
@@ -5127,7 +5186,7 @@ function SharedKeys(props) {
|
|
|
5127
5186
|
coord: fractionCoord,
|
|
5128
5187
|
secondary: true
|
|
5129
5188
|
}), /*#__PURE__*/React__namespace.createElement(KeypadButton, {
|
|
5130
|
-
keyConfig: convertDotToTimes ? KeyConfigs.TIMES : KeyConfigs.CDOT,
|
|
5189
|
+
keyConfig: convertDotToTimesByLocale(!!convertDotToTimes) ? KeyConfigs.TIMES : KeyConfigs.CDOT,
|
|
5131
5190
|
onClickKey: onClickKey,
|
|
5132
5191
|
coord: [4, 1],
|
|
5133
5192
|
secondary: true
|
|
@@ -5872,6 +5931,7 @@ exports.KeypadInput = MathInput;
|
|
|
5872
5931
|
exports.KeypadType = KeypadType;
|
|
5873
5932
|
exports.MobileKeypad = MobileKeypad;
|
|
5874
5933
|
exports.StatefulKeypadContextProvider = StatefulKeypadContextProvider;
|
|
5934
|
+
exports.convertDotToTimesByLocale = convertDotToTimesByLocale;
|
|
5875
5935
|
exports.createMathField = createMathField;
|
|
5876
5936
|
exports.getCursorContext = getCursorContext;
|
|
5877
5937
|
exports.keyTranslator = keyToMathquillMap;
|