@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/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.2.0";
51
+ const libVersion = "16.4.0";
52
52
  perseusCore.addLibraryVersionToPerseusDebug(libName, libVersion);
53
53
 
54
54
  function _extends() {
@@ -131,6 +131,49 @@ class View extends React__namespace.Component {
131
131
  }
132
132
  }
133
133
 
134
+ /**
135
+ * KeypadContext provides a way to the Keypad and Perseus Renderers to
136
+ * communicate.
137
+ *
138
+ * The StatefulKeypadContextProvider wraps the application
139
+ * while KeypadContext.Consumer wraps things that need this state:
140
+ * - mobile keypad usages
141
+ * - Perseus Renderers (Server/Item/Article)
142
+ */
143
+ // @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>'.
144
+ const KeypadContext = /*#__PURE__*/React__namespace.createContext({
145
+ setKeypadActive: keypadActive => {},
146
+ keypadActive: false,
147
+ setKeypadElement: keypadElement => {},
148
+ keypadElement: null,
149
+ setRenderer: renderer => {},
150
+ renderer: null,
151
+ setScrollableElement: scrollableElement => {},
152
+ scrollableElement: null
153
+ });
154
+ function StatefulKeypadContextProvider(props) {
155
+ // whether or not to display the keypad
156
+ const [keypadActive, setKeypadActive] = React.useState(false);
157
+ // used to communicate between the keypad and the Renderer
158
+ const [keypadElement, setKeypadElement] = React.useState();
159
+ // this is a KeypadContextRendererInterface from Perseus
160
+ const [renderer, setRenderer] = React.useState();
161
+ const [scrollableElement, setScrollableElement] = React.useState();
162
+ const memoizedValue = React.useMemo(() => ({
163
+ keypadActive,
164
+ setKeypadActive,
165
+ keypadElement,
166
+ setKeypadElement,
167
+ renderer,
168
+ setRenderer,
169
+ scrollableElement,
170
+ setScrollableElement
171
+ }), [keypadActive, setKeypadActive, keypadElement, setKeypadElement, renderer, setRenderer, scrollableElement, setScrollableElement]);
172
+ return /*#__PURE__*/React__namespace.createElement(KeypadContext.Provider, {
173
+ value: memoizedValue
174
+ }, props.children);
175
+ }
176
+
134
177
  /**
135
178
  * Common parameters used to style components.
136
179
  */
@@ -821,6 +864,30 @@ const DecimalSeparator = {
821
864
  // - Some languages/locales use different decimal separators than the ones
822
865
  // listed here. Much of the Arab world uses U+066C.
823
866
  const decimalSeparator = i18n.getDecimalSeparator() === "," ? DecimalSeparator.COMMA : DecimalSeparator.PERIOD;
867
+ const CDOT_ONLY = ["az", "cs", "da", "de", "hu", "hy", "kk", "ky", "lt", "lv", "nb", "sk", "sr", "sv", "uz"];
868
+ const TIMES_ONLY = ["fr", "tr", "pt-pt"];
869
+
870
+ /**
871
+ * convertDotToTimes (aka `times`) is an option the content creators have to
872
+ * use × (TIMES) rather than · (CDOT) for multiplication (for younger learners).
873
+ * Some locales _only_ use one or the other for all multiplication regardless
874
+ * of age.
875
+ *
876
+ * convertDotToTimesByLocale overrides convertDotToTimes for those locales.
877
+ *
878
+ * @param {boolean} convertDotToTimes - the setting set by content creators
879
+ * @returns {boolean} - true to convert to × (TIMES), false to use · (CDOT)
880
+ */
881
+ function convertDotToTimesByLocale(convertDotToTimes) {
882
+ const locale = i18n.getLocale();
883
+ if (CDOT_ONLY.includes(locale)) {
884
+ return false;
885
+ }
886
+ if (TIMES_ONLY.includes(locale)) {
887
+ return true;
888
+ }
889
+ return convertDotToTimes;
890
+ }
824
891
 
825
892
  function handleLeftArrow(mathField, cursor) {
826
893
  // If we're inside a function, and just after the left parentheses, we
@@ -1522,6 +1589,7 @@ class MathInput extends React__namespace.Component {
1522
1589
  if (!this._container.contains(evt.target)) {
1523
1590
  if (this.props.keypadElement && this.props.keypadElement.getDOMNode()) {
1524
1591
  const [x, y] = [evt.clientX, evt.clientY];
1592
+
1525
1593
  // We only want to blur if the click is above the keypad,
1526
1594
  // to the left of the keypad, or to the right of the keypad.
1527
1595
  // The reasoning for not blurring for any clicks below the keypad is
@@ -1846,7 +1914,7 @@ class MathInput extends React__namespace.Component {
1846
1914
  context: this.mathField.contextForCursor()
1847
1915
  });
1848
1916
  };
1849
- handleTouchStart = e => {
1917
+ handleTouchStart = (e, keypadActive, setKeypadActive) => {
1850
1918
  e.stopPropagation();
1851
1919
 
1852
1920
  // Hide the cursor handle on touch start, if the handle itself isn't
@@ -1865,6 +1933,11 @@ class MathInput extends React__namespace.Component {
1865
1933
  this._insertCursorAtClosestNode(touch.clientX, touch.clientY);
1866
1934
  }
1867
1935
 
1936
+ // If we're already focused, but the keypad isn't active, activate it.
1937
+ if (this.state.focused && !keypadActive) {
1938
+ setKeypadActive(true);
1939
+ }
1940
+
1868
1941
  // Trigger a focus event, if we're not already focused.
1869
1942
  if (!this.state.focused) {
1870
1943
  this.focus();
@@ -1874,7 +1947,7 @@ class MathInput extends React__namespace.Component {
1874
1947
  // We want to allow the user to be able to focus the input via click
1875
1948
  // when using ChromeOS third-party browsers that use mobile user agents,
1876
1949
  // but don't actually simulate touch events.
1877
- handleClick = e => {
1950
+ handleClick = (e, keypadActive, setKeypadActive) => {
1878
1951
  e.stopPropagation();
1879
1952
 
1880
1953
  // Hide the cursor handle on click
@@ -1891,6 +1964,11 @@ class MathInput extends React__namespace.Component {
1891
1964
  this._insertCursorAtClosestNode(e.clientX, e.clientY);
1892
1965
  }
1893
1966
 
1967
+ // If we're already focused, but the keypad isn't active, activate it.
1968
+ if (this.state.focused && !keypadActive) {
1969
+ setKeypadActive(true);
1970
+ }
1971
+
1894
1972
  // Trigger a focus event, if we're not already focused.
1895
1973
  if (!this.state.focused) {
1896
1974
  this.focus();
@@ -2114,34 +2192,44 @@ class MathInput extends React__namespace.Component {
2114
2192
  // TODO(diedra): Fix the bug that is causing Android to require a two finger tap
2115
2193
  // to the open the keyboard, and then remove the second half of this label.
2116
2194
  const ariaLabel = i18n__namespace._("Math input box") + " " + i18n__namespace._("Tap with one or two fingers to open keyboard");
2117
- return /*#__PURE__*/React__namespace.createElement(View, {
2118
- style: styles$7.input,
2119
- onTouchStart: this.handleTouchStart,
2120
- onTouchMove: this.handleTouchMove,
2121
- onTouchEnd: this.handleTouchEnd,
2122
- onClick: this.handleClick,
2123
- role: "textbox",
2124
- ariaLabel: ariaLabel
2125
- }, /*#__PURE__*/React__namespace.createElement("div", {
2126
- className: "keypad-input"
2127
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
2128
- ,
2129
- tabIndex: "0",
2130
- ref: node => {
2131
- this.inputRef = node;
2132
- },
2133
- onKeyUp: this.handleKeyUp
2134
- }, /*#__PURE__*/React__namespace.createElement("div", {
2135
- ref: node => {
2136
- this._mathContainer = ReactDOM__default["default"].findDOMNode(node);
2137
- },
2138
- style: innerStyle
2139
- })), focused && handle.visible && /*#__PURE__*/React__namespace.createElement(CursorHandle, _extends({}, handle, {
2140
- onTouchStart: this.onCursorHandleTouchStart,
2141
- onTouchMove: this.onCursorHandleTouchMove,
2142
- onTouchEnd: this.onCursorHandleTouchEnd,
2143
- onTouchCancel: this.onCursorHandleTouchCancel
2144
- })));
2195
+ return /*#__PURE__*/React__namespace.createElement(KeypadContext.Consumer, null, _ref => {
2196
+ let {
2197
+ keypadActive,
2198
+ setKeypadActive
2199
+ } = _ref;
2200
+ return /*#__PURE__*/React__namespace.createElement(View, {
2201
+ style: styles$7.input,
2202
+ onTouchStart: e => {
2203
+ this.handleTouchStart(e, keypadActive, setKeypadActive);
2204
+ },
2205
+ onTouchMove: this.handleTouchMove,
2206
+ onTouchEnd: this.handleTouchEnd,
2207
+ onClick: e => {
2208
+ this.handleClick(e, keypadActive, setKeypadActive);
2209
+ },
2210
+ role: "textbox",
2211
+ ariaLabel: ariaLabel
2212
+ }, /*#__PURE__*/React__namespace.createElement("div", {
2213
+ className: "keypad-input"
2214
+ // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
2215
+ ,
2216
+ tabIndex: "0",
2217
+ ref: node => {
2218
+ this.inputRef = node;
2219
+ },
2220
+ onKeyUp: this.handleKeyUp
2221
+ }, /*#__PURE__*/React__namespace.createElement("div", {
2222
+ ref: node => {
2223
+ this._mathContainer = ReactDOM__default["default"].findDOMNode(node);
2224
+ },
2225
+ style: innerStyle
2226
+ })), focused && handle.visible && /*#__PURE__*/React__namespace.createElement(CursorHandle, _extends({}, handle, {
2227
+ onTouchStart: this.onCursorHandleTouchStart,
2228
+ onTouchMove: this.onCursorHandleTouchMove,
2229
+ onTouchEnd: this.onCursorHandleTouchEnd,
2230
+ onTouchCancel: this.onCursorHandleTouchCancel
2231
+ })));
2232
+ });
2145
2233
  }
2146
2234
  }
2147
2235
  const fontSizePt = 18;
@@ -5162,7 +5250,7 @@ function SharedKeys(props) {
5162
5250
  coord: fractionCoord,
5163
5251
  secondary: true
5164
5252
  }), /*#__PURE__*/React__namespace.createElement(KeypadButton, {
5165
- keyConfig: convertDotToTimes ? KeyConfigs.TIMES : KeyConfigs.CDOT,
5253
+ keyConfig: convertDotToTimesByLocale(!!convertDotToTimes) ? KeyConfigs.TIMES : KeyConfigs.CDOT,
5166
5254
  onClickKey: onClickKey,
5167
5255
  coord: [4, 1],
5168
5256
  secondary: true
@@ -5349,49 +5437,6 @@ const styles$1 = aphrodite.StyleSheet.create({
5349
5437
  }
5350
5438
  });
5351
5439
 
5352
- /**
5353
- * KeypadContext provides a way to the Keypad and Perseus Renderers to
5354
- * communicate.
5355
- *
5356
- * The StatefulKeypadContextProvider wraps the application
5357
- * while KeypadContext.Consumer wraps things that need this state:
5358
- * - mobile keypad usages
5359
- * - Perseus Renderers (Server/Item/Article)
5360
- */
5361
- // @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>'.
5362
- const KeypadContext = /*#__PURE__*/React__namespace.createContext({
5363
- setKeypadActive: keypadActive => {},
5364
- keypadActive: false,
5365
- setKeypadElement: keypadElement => {},
5366
- keypadElement: null,
5367
- setRenderer: renderer => {},
5368
- renderer: null,
5369
- setScrollableElement: scrollableElement => {},
5370
- scrollableElement: null
5371
- });
5372
- function StatefulKeypadContextProvider(props) {
5373
- // whether or not to display the keypad
5374
- const [keypadActive, setKeypadActive] = React.useState(false);
5375
- // used to communicate between the keypad and the Renderer
5376
- const [keypadElement, setKeypadElement] = React.useState();
5377
- // this is a KeypadContextRendererInterface from Perseus
5378
- const [renderer, setRenderer] = React.useState();
5379
- const [scrollableElement, setScrollableElement] = React.useState();
5380
- const memoizedValue = React.useMemo(() => ({
5381
- keypadActive,
5382
- setKeypadActive,
5383
- keypadElement,
5384
- setKeypadElement,
5385
- renderer,
5386
- setRenderer,
5387
- scrollableElement,
5388
- setScrollableElement
5389
- }), [keypadActive, setKeypadActive, keypadElement, setKeypadElement, renderer, setRenderer, scrollableElement, setScrollableElement]);
5390
- return /*#__PURE__*/React__namespace.createElement(KeypadContext.Provider, {
5391
- value: memoizedValue
5392
- }, props.children);
5393
- }
5394
-
5395
5440
  function flatten(list) {
5396
5441
  const result = [];
5397
5442
  if (!list) {
@@ -5907,6 +5952,7 @@ exports.KeypadInput = MathInput;
5907
5952
  exports.KeypadType = KeypadType;
5908
5953
  exports.MobileKeypad = MobileKeypad;
5909
5954
  exports.StatefulKeypadContextProvider = StatefulKeypadContextProvider;
5955
+ exports.convertDotToTimesByLocale = convertDotToTimesByLocale;
5910
5956
  exports.createMathField = createMathField;
5911
5957
  exports.getCursorContext = getCursorContext;
5912
5958
  exports.keyTranslator = keyToMathquillMap;