@khanacademy/math-input 16.2.0 → 16.4.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 +12 -0
- package/dist/components/input/math-input.d.ts +2 -2
- package/dist/es/index.js +96 -54
- package/dist/es/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +121 -75
- package/dist/index.js.map +1 -1
- package/dist/utils.d.ts +12 -0
- package/package.json +1 -1
- package/src/components/input/math-input.tsx +71 -40
- 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @khanacademy/math-input
|
|
2
2
|
|
|
3
|
+
## 16.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#871](https://github.com/Khan/perseus/pull/871) [`610ebba2`](https://github.com/Khan/perseus/commit/610ebba29ab8b2ee4ddf4879f8c8b87993f29b20) Thanks [@SonicScrewdriver](https://github.com/SonicScrewdriver)! - Ensured that tapping an already focused math input will reopen the keypad if it is closed.
|
|
8
|
+
|
|
9
|
+
## 16.3.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [#863](https://github.com/Khan/perseus/pull/863) [`f910bd72`](https://github.com/Khan/perseus/commit/f910bd72fc5cbf88a1a00d57f8aefa8eea2c755d) Thanks [@handeyeco](https://github.com/handeyeco)! - Localize the multiplication symbol in MathInput
|
|
14
|
+
|
|
3
15
|
## 16.2.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
|
@@ -82,8 +82,8 @@ declare class MathInput extends React.Component<Props, State> {
|
|
|
82
82
|
* @param {number} y - the y coordinate in the viewport
|
|
83
83
|
*/
|
|
84
84
|
_insertCursorAtClosestNode: (arg1: number, arg2: number) => void;
|
|
85
|
-
handleTouchStart: (
|
|
86
|
-
handleClick: (e: React.MouseEvent<HTMLDivElement
|
|
85
|
+
handleTouchStart: (e: React.TouchEvent<HTMLDivElement>, keypadActive: boolean, setKeypadActive: (keypadActive: boolean) => void) => void;
|
|
86
|
+
handleClick: (e: React.MouseEvent<HTMLDivElement>, keypadActive: boolean, setKeypadActive: (keypadActive: boolean) => void) => void;
|
|
87
87
|
handleTouchMove: (arg1: React.TouchEvent<HTMLDivElement>) => void;
|
|
88
88
|
handleTouchEnd: (arg1: React.TouchEvent<HTMLDivElement>) => void;
|
|
89
89
|
/**
|
package/dist/es/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { addLibraryVersionToPerseusDebug } from '@khanacademy/perseus-core';
|
|
2
2
|
import Color from '@khanacademy/wonder-blocks-color';
|
|
3
3
|
import * as i18n from '@khanacademy/wonder-blocks-i18n';
|
|
4
|
-
import { getDecimalSeparator } from '@khanacademy/wonder-blocks-i18n';
|
|
4
|
+
import { getDecimalSeparator, getLocale } from '@khanacademy/wonder-blocks-i18n';
|
|
5
5
|
import { entries } from '@khanacademy/wonder-stuff-core';
|
|
6
6
|
import { StyleSheet, css } from 'aphrodite';
|
|
7
7
|
import * as React from 'react';
|
|
8
|
-
import {
|
|
8
|
+
import { useState, useMemo, useEffect } from 'react';
|
|
9
9
|
import ReactDOM from 'react-dom';
|
|
10
10
|
import $ from 'jquery';
|
|
11
11
|
import MathQuill from 'mathquill';
|
|
@@ -17,7 +17,7 @@ import PropTypes from 'prop-types';
|
|
|
17
17
|
|
|
18
18
|
// This file is processed by a Rollup plugin (replace) to inject the production
|
|
19
19
|
const libName = "@khanacademy/math-input";
|
|
20
|
-
const libVersion = "16.
|
|
20
|
+
const libVersion = "16.4.0";
|
|
21
21
|
addLibraryVersionToPerseusDebug(libName, libVersion);
|
|
22
22
|
|
|
23
23
|
function _extends() {
|
|
@@ -100,6 +100,49 @@ View.styles = StyleSheet.create({
|
|
|
100
100
|
}
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* KeypadContext provides a way to the Keypad and Perseus Renderers to
|
|
105
|
+
* communicate.
|
|
106
|
+
*
|
|
107
|
+
* The StatefulKeypadContextProvider wraps the application
|
|
108
|
+
* while KeypadContext.Consumer wraps things that need this state:
|
|
109
|
+
* - mobile keypad usages
|
|
110
|
+
* - Perseus Renderers (Server/Item/Article)
|
|
111
|
+
*/
|
|
112
|
+
// @ts-expect-error - TS2322 - Type 'Context<{ setKeypadElement: (keypadElement: HTMLElement | null | undefined) => void; keypadElement: null; setRenderer: (renderer: RendererInterface | null | undefined) => void; renderer: null; setScrollableElement: (scrollableElement: HTMLElement | ... 1 more ... | undefined) => void; scrollableElement: null; }>' is not assignable to type 'Context<KeypadContext>'.
|
|
113
|
+
const KeypadContext = /*#__PURE__*/React.createContext({
|
|
114
|
+
setKeypadActive: keypadActive => {},
|
|
115
|
+
keypadActive: false,
|
|
116
|
+
setKeypadElement: keypadElement => {},
|
|
117
|
+
keypadElement: null,
|
|
118
|
+
setRenderer: renderer => {},
|
|
119
|
+
renderer: null,
|
|
120
|
+
setScrollableElement: scrollableElement => {},
|
|
121
|
+
scrollableElement: null
|
|
122
|
+
});
|
|
123
|
+
function StatefulKeypadContextProvider(props) {
|
|
124
|
+
// whether or not to display the keypad
|
|
125
|
+
const [keypadActive, setKeypadActive] = useState(false);
|
|
126
|
+
// used to communicate between the keypad and the Renderer
|
|
127
|
+
const [keypadElement, setKeypadElement] = useState();
|
|
128
|
+
// this is a KeypadContextRendererInterface from Perseus
|
|
129
|
+
const [renderer, setRenderer] = useState();
|
|
130
|
+
const [scrollableElement, setScrollableElement] = useState();
|
|
131
|
+
const memoizedValue = useMemo(() => ({
|
|
132
|
+
keypadActive,
|
|
133
|
+
setKeypadActive,
|
|
134
|
+
keypadElement,
|
|
135
|
+
setKeypadElement,
|
|
136
|
+
renderer,
|
|
137
|
+
setRenderer,
|
|
138
|
+
scrollableElement,
|
|
139
|
+
setScrollableElement
|
|
140
|
+
}), [keypadActive, setKeypadActive, keypadElement, setKeypadElement, renderer, setRenderer, scrollableElement, setScrollableElement]);
|
|
141
|
+
return /*#__PURE__*/React.createElement(KeypadContext.Provider, {
|
|
142
|
+
value: memoizedValue
|
|
143
|
+
}, props.children);
|
|
144
|
+
}
|
|
145
|
+
|
|
103
146
|
/**
|
|
104
147
|
* Common parameters used to style components.
|
|
105
148
|
*/
|
|
@@ -789,6 +832,30 @@ const DecimalSeparator = {
|
|
|
789
832
|
// - Some languages/locales use different decimal separators than the ones
|
|
790
833
|
// listed here. Much of the Arab world uses U+066C.
|
|
791
834
|
const decimalSeparator = getDecimalSeparator() === "," ? DecimalSeparator.COMMA : DecimalSeparator.PERIOD;
|
|
835
|
+
const CDOT_ONLY = ["az", "cs", "da", "de", "hu", "hy", "kk", "ky", "lt", "lv", "nb", "sk", "sr", "sv", "uz"];
|
|
836
|
+
const TIMES_ONLY = ["fr", "tr", "pt-pt"];
|
|
837
|
+
|
|
838
|
+
/**
|
|
839
|
+
* convertDotToTimes (aka `times`) is an option the content creators have to
|
|
840
|
+
* use × (TIMES) rather than · (CDOT) for multiplication (for younger learners).
|
|
841
|
+
* Some locales _only_ use one or the other for all multiplication regardless
|
|
842
|
+
* of age.
|
|
843
|
+
*
|
|
844
|
+
* convertDotToTimesByLocale overrides convertDotToTimes for those locales.
|
|
845
|
+
*
|
|
846
|
+
* @param {boolean} convertDotToTimes - the setting set by content creators
|
|
847
|
+
* @returns {boolean} - true to convert to × (TIMES), false to use · (CDOT)
|
|
848
|
+
*/
|
|
849
|
+
function convertDotToTimesByLocale(convertDotToTimes) {
|
|
850
|
+
const locale = getLocale();
|
|
851
|
+
if (CDOT_ONLY.includes(locale)) {
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
if (TIMES_ONLY.includes(locale)) {
|
|
855
|
+
return true;
|
|
856
|
+
}
|
|
857
|
+
return convertDotToTimes;
|
|
858
|
+
}
|
|
792
859
|
|
|
793
860
|
function handleLeftArrow(mathField, cursor) {
|
|
794
861
|
// If we're inside a function, and just after the left parentheses, we
|
|
@@ -1670,7 +1737,7 @@ class MathInput extends React.Component {
|
|
|
1670
1737
|
context: this.mathField.contextForCursor()
|
|
1671
1738
|
});
|
|
1672
1739
|
};
|
|
1673
|
-
this.handleTouchStart = e => {
|
|
1740
|
+
this.handleTouchStart = (e, keypadActive, setKeypadActive) => {
|
|
1674
1741
|
e.stopPropagation();
|
|
1675
1742
|
|
|
1676
1743
|
// Hide the cursor handle on touch start, if the handle itself isn't
|
|
@@ -1689,6 +1756,11 @@ class MathInput extends React.Component {
|
|
|
1689
1756
|
this._insertCursorAtClosestNode(touch.clientX, touch.clientY);
|
|
1690
1757
|
}
|
|
1691
1758
|
|
|
1759
|
+
// If we're already focused, but the keypad isn't active, activate it.
|
|
1760
|
+
if (this.state.focused && !keypadActive) {
|
|
1761
|
+
setKeypadActive(true);
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1692
1764
|
// Trigger a focus event, if we're not already focused.
|
|
1693
1765
|
if (!this.state.focused) {
|
|
1694
1766
|
this.focus();
|
|
@@ -1697,7 +1769,7 @@ class MathInput extends React.Component {
|
|
|
1697
1769
|
// We want to allow the user to be able to focus the input via click
|
|
1698
1770
|
// when using ChromeOS third-party browsers that use mobile user agents,
|
|
1699
1771
|
// but don't actually simulate touch events.
|
|
1700
|
-
this.handleClick = e => {
|
|
1772
|
+
this.handleClick = (e, keypadActive, setKeypadActive) => {
|
|
1701
1773
|
e.stopPropagation();
|
|
1702
1774
|
|
|
1703
1775
|
// Hide the cursor handle on click
|
|
@@ -1714,6 +1786,11 @@ class MathInput extends React.Component {
|
|
|
1714
1786
|
this._insertCursorAtClosestNode(e.clientX, e.clientY);
|
|
1715
1787
|
}
|
|
1716
1788
|
|
|
1789
|
+
// If we're already focused, but the keypad isn't active, activate it.
|
|
1790
|
+
if (this.state.focused && !keypadActive) {
|
|
1791
|
+
setKeypadActive(true);
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1717
1794
|
// Trigger a focus event, if we're not already focused.
|
|
1718
1795
|
if (!this.state.focused) {
|
|
1719
1796
|
this.focus();
|
|
@@ -2012,6 +2089,7 @@ class MathInput extends React.Component {
|
|
|
2012
2089
|
if (!this._container.contains(evt.target)) {
|
|
2013
2090
|
if (this.props.keypadElement && this.props.keypadElement.getDOMNode()) {
|
|
2014
2091
|
const [x, y] = [evt.clientX, evt.clientY];
|
|
2092
|
+
|
|
2015
2093
|
// We only want to blur if the click is above the keypad,
|
|
2016
2094
|
// to the left of the keypad, or to the right of the keypad.
|
|
2017
2095
|
// The reasoning for not blurring for any clicks below the keypad is
|
|
@@ -2077,12 +2155,19 @@ class MathInput extends React.Component {
|
|
|
2077
2155
|
// TODO(diedra): Fix the bug that is causing Android to require a two finger tap
|
|
2078
2156
|
// to the open the keyboard, and then remove the second half of this label.
|
|
2079
2157
|
const ariaLabel = i18n._("Math input box") + " " + i18n._("Tap with one or two fingers to open keyboard");
|
|
2080
|
-
return /*#__PURE__*/React.createElement(
|
|
2158
|
+
return /*#__PURE__*/React.createElement(KeypadContext.Consumer, null, ({
|
|
2159
|
+
keypadActive,
|
|
2160
|
+
setKeypadActive
|
|
2161
|
+
}) => /*#__PURE__*/React.createElement(View, {
|
|
2081
2162
|
style: styles$7.input,
|
|
2082
|
-
onTouchStart:
|
|
2163
|
+
onTouchStart: e => {
|
|
2164
|
+
this.handleTouchStart(e, keypadActive, setKeypadActive);
|
|
2165
|
+
},
|
|
2083
2166
|
onTouchMove: this.handleTouchMove,
|
|
2084
2167
|
onTouchEnd: this.handleTouchEnd,
|
|
2085
|
-
onClick:
|
|
2168
|
+
onClick: e => {
|
|
2169
|
+
this.handleClick(e, keypadActive, setKeypadActive);
|
|
2170
|
+
},
|
|
2086
2171
|
role: "textbox",
|
|
2087
2172
|
ariaLabel: ariaLabel
|
|
2088
2173
|
}, /*#__PURE__*/React.createElement("div", {
|
|
@@ -2104,7 +2189,7 @@ class MathInput extends React.Component {
|
|
|
2104
2189
|
onTouchMove: this.onCursorHandleTouchMove,
|
|
2105
2190
|
onTouchEnd: this.onCursorHandleTouchEnd,
|
|
2106
2191
|
onTouchCancel: this.onCursorHandleTouchCancel
|
|
2107
|
-
})));
|
|
2192
|
+
}))));
|
|
2108
2193
|
}
|
|
2109
2194
|
}
|
|
2110
2195
|
MathInput.defaultProps = {
|
|
@@ -4879,7 +4964,7 @@ function SharedKeys(props) {
|
|
|
4879
4964
|
coord: fractionCoord,
|
|
4880
4965
|
secondary: true
|
|
4881
4966
|
}), /*#__PURE__*/React.createElement(KeypadButton, {
|
|
4882
|
-
keyConfig: convertDotToTimes ? KeyConfigs.TIMES : KeyConfigs.CDOT,
|
|
4967
|
+
keyConfig: convertDotToTimesByLocale(!!convertDotToTimes) ? KeyConfigs.TIMES : KeyConfigs.CDOT,
|
|
4883
4968
|
onClickKey: onClickKey,
|
|
4884
4969
|
coord: [4, 1],
|
|
4885
4970
|
secondary: true
|
|
@@ -5067,49 +5152,6 @@ const styles$1 = StyleSheet.create({
|
|
|
5067
5152
|
}
|
|
5068
5153
|
});
|
|
5069
5154
|
|
|
5070
|
-
/**
|
|
5071
|
-
* KeypadContext provides a way to the Keypad and Perseus Renderers to
|
|
5072
|
-
* communicate.
|
|
5073
|
-
*
|
|
5074
|
-
* The StatefulKeypadContextProvider wraps the application
|
|
5075
|
-
* while KeypadContext.Consumer wraps things that need this state:
|
|
5076
|
-
* - mobile keypad usages
|
|
5077
|
-
* - Perseus Renderers (Server/Item/Article)
|
|
5078
|
-
*/
|
|
5079
|
-
// @ts-expect-error - TS2322 - Type 'Context<{ setKeypadElement: (keypadElement: HTMLElement | null | undefined) => void; keypadElement: null; setRenderer: (renderer: RendererInterface | null | undefined) => void; renderer: null; setScrollableElement: (scrollableElement: HTMLElement | ... 1 more ... | undefined) => void; scrollableElement: null; }>' is not assignable to type 'Context<KeypadContext>'.
|
|
5080
|
-
const KeypadContext = /*#__PURE__*/React.createContext({
|
|
5081
|
-
setKeypadActive: keypadActive => {},
|
|
5082
|
-
keypadActive: false,
|
|
5083
|
-
setKeypadElement: keypadElement => {},
|
|
5084
|
-
keypadElement: null,
|
|
5085
|
-
setRenderer: renderer => {},
|
|
5086
|
-
renderer: null,
|
|
5087
|
-
setScrollableElement: scrollableElement => {},
|
|
5088
|
-
scrollableElement: null
|
|
5089
|
-
});
|
|
5090
|
-
function StatefulKeypadContextProvider(props) {
|
|
5091
|
-
// whether or not to display the keypad
|
|
5092
|
-
const [keypadActive, setKeypadActive] = useState(false);
|
|
5093
|
-
// used to communicate between the keypad and the Renderer
|
|
5094
|
-
const [keypadElement, setKeypadElement] = useState();
|
|
5095
|
-
// this is a KeypadContextRendererInterface from Perseus
|
|
5096
|
-
const [renderer, setRenderer] = useState();
|
|
5097
|
-
const [scrollableElement, setScrollableElement] = useState();
|
|
5098
|
-
const memoizedValue = useMemo(() => ({
|
|
5099
|
-
keypadActive,
|
|
5100
|
-
setKeypadActive,
|
|
5101
|
-
keypadElement,
|
|
5102
|
-
setKeypadElement,
|
|
5103
|
-
renderer,
|
|
5104
|
-
setRenderer,
|
|
5105
|
-
scrollableElement,
|
|
5106
|
-
setScrollableElement
|
|
5107
|
-
}), [keypadActive, setKeypadActive, keypadElement, setKeypadElement, renderer, setRenderer, scrollableElement, setScrollableElement]);
|
|
5108
|
-
return /*#__PURE__*/React.createElement(KeypadContext.Provider, {
|
|
5109
|
-
value: memoizedValue
|
|
5110
|
-
}, props.children);
|
|
5111
|
-
}
|
|
5112
|
-
|
|
5113
5155
|
function flatten(list) {
|
|
5114
5156
|
const result = [];
|
|
5115
5157
|
if (!list) {
|
|
@@ -5619,5 +5661,5 @@ let KeypadType = /*#__PURE__*/function (KeypadType) {
|
|
|
5619
5661
|
return KeypadType;
|
|
5620
5662
|
}({});
|
|
5621
5663
|
|
|
5622
|
-
export { CursorContext, Keypad as DesktopKeypad, KeyArray, KeyConfigs, KeypadContext, MathInput as KeypadInput, KeypadType, MobileKeypad, StatefulKeypadContextProvider, createMathField, getCursorContext, keyToMathquillMap as keyTranslator, keypadElementPropType, libVersion, mathQuillInstance };
|
|
5664
|
+
export { CursorContext, Keypad as DesktopKeypad, KeyArray, KeyConfigs, KeypadContext, MathInput as KeypadInput, KeypadType, MobileKeypad, StatefulKeypadContextProvider, convertDotToTimesByLocale, createMathField, getCursorContext, keyToMathquillMap as keyTranslator, keypadElementPropType, libVersion, mathQuillInstance };
|
|
5623
5665
|
//# sourceMappingURL=index.js.map
|