@khanacademy/math-input 11.0.0 → 12.0.1

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/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "Khan Academy's new expression editor for the mobile web.",
4
4
  "author": "Khan Academy",
5
5
  "license": "MIT",
6
- "version": "11.0.0",
6
+ "version": "12.0.1",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -30,7 +30,7 @@
30
30
  "@khanacademy/wonder-blocks-core": "^6.0.0",
31
31
  "@khanacademy/wonder-blocks-i18n": "^2.0.2",
32
32
  "@khanacademy/wonder-blocks-popover": "^2.0.11",
33
- "@khanacademy/wonder-stuff-core": "^1.2.2",
33
+ "@khanacademy/wonder-stuff-core": "^1.5.1",
34
34
  "aphrodite": "^1.1.0",
35
35
  "jquery": "^2.1.1",
36
36
  "katex": "^0.11.1",
@@ -49,7 +49,7 @@
49
49
  "@khanacademy/wonder-blocks-color": "^2.0.1",
50
50
  "@khanacademy/wonder-blocks-core": "^6.0.0",
51
51
  "@khanacademy/wonder-blocks-i18n": "^2.0.2",
52
- "@khanacademy/wonder-stuff-core": "^1.2.2",
52
+ "@khanacademy/wonder-stuff-core": "^1.5.1",
53
53
  "aphrodite": "^1.1.0",
54
54
  "jquery": "^2.1.1",
55
55
  "katex": "^0.11.1",
@@ -185,7 +185,7 @@ class MathInput extends React.Component<Props, State> {
185
185
  // dismissal. This code needs to be generalized to handle
186
186
  // multi-touch.
187
187
  if (this.state.focused && this.didTouchOutside && !this.didScroll) {
188
- this.blur();
188
+ this.blur(true);
189
189
  }
190
190
 
191
191
  this.didTouchOutside = false;
@@ -332,9 +332,11 @@ class MathInput extends React.Component<Props, State> {
332
332
  }
333
333
  };
334
334
 
335
- blur: () => void = () => {
335
+ blur: (callPropsOnBlur: boolean) => void = (callPropsOnBlur: boolean) => {
336
336
  this.mathField.blur();
337
- this.props.onBlur && this.props.onBlur();
337
+ if (callPropsOnBlur) {
338
+ this.props.onBlur && this.props.onBlur();
339
+ }
338
340
  this.setState({focused: false, handle: {visible: false}});
339
341
  };
340
342
 
@@ -907,11 +909,17 @@ class MathInput extends React.Component<Props, State> {
907
909
  overrides.css. */}
908
910
  <div
909
911
  className="keypad-input"
910
- // @ts-expect-error - TS2322 - Type 'string' is not assignable to type 'number | undefined'.
911
- tabIndex={"0"}
912
+ tabIndex={0}
912
913
  ref={(node) => {
913
914
  this.inputRef = node;
914
915
  }}
916
+ onFocus={() => {
917
+ this.focus();
918
+ }}
919
+ onBlur={() => {
920
+ this._hideCursorHandle();
921
+ this.blur(false);
922
+ }}
915
923
  onKeyUp={this.handleKeyUp}
916
924
  >
917
925
  {/* NOTE(charlie): This element must be styled with inline
@@ -13,6 +13,7 @@ import type {
13
13
  KeyHandler,
14
14
  KeypadAPI,
15
15
  } from "../../types";
16
+ import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";
16
17
  import type {StyleType} from "@khanacademy/wonder-blocks-core";
17
18
 
18
19
  import Keypad from "./index";
@@ -32,6 +33,7 @@ type Props = {
32
33
  onElementMounted?: (arg1: any) => void;
33
34
  onDismiss?: () => void;
34
35
  style?: StyleType;
36
+ onAnalyticsEvent: AnalyticsEventHandlerFn;
35
37
  };
36
38
 
37
39
  type State = {
@@ -62,12 +64,18 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
62
64
  this._throttleResizeHandler,
63
65
  );
64
66
 
65
- this._containerResizeObserver = new ResizeObserver(
66
- this._throttleResizeHandler,
67
- );
68
-
69
- if (this._containerRef.current) {
70
- this._containerResizeObserver.observe(this._containerRef.current);
67
+ // LC-1213: some common older browsers (as of 2023-09-07)
68
+ // don't support ResizeObserver
69
+ if ("ResizeObserver" in window) {
70
+ this._containerResizeObserver = new window.ResizeObserver(
71
+ this._throttleResizeHandler,
72
+ );
73
+
74
+ if (this._containerRef.current) {
75
+ this._containerResizeObserver.observe(
76
+ this._containerRef.current,
77
+ );
78
+ }
71
79
  }
72
80
  }
73
81
 
@@ -99,13 +107,17 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
99
107
  };
100
108
 
101
109
  activate: () => void = () => {
102
- this.setState({active: true});
110
+ if (!this.state.active) {
111
+ this.setState({active: true});
112
+ }
103
113
  };
104
114
 
105
115
  dismiss: () => void = () => {
106
- this.setState({active: false}, () => {
107
- this.props.onDismiss?.();
108
- });
116
+ if (this.state.active) {
117
+ this.setState({active: false}, () => {
118
+ this.props.onDismiss?.();
119
+ });
120
+ }
109
121
  };
110
122
 
111
123
  configure: (configuration: KeypadConfiguration, cb: () => void) => void = (
@@ -190,8 +202,7 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
190
202
  }}
191
203
  >
192
204
  <Keypad
193
- // TODO(jeremy)
194
- onAnalyticsEvent={async () => {}}
205
+ onAnalyticsEvent={this.props.onAnalyticsEvent}
195
206
  extraKeys={keypadConfig?.extraKeys}
196
207
  onClickKey={(key) => this._handleClickKey(key)}
197
208
  cursorContext={cursor?.context}
@@ -79,12 +79,18 @@ class KeypadContainer extends React.Component<Props, State> {
79
79
  this._throttleResizeHandler,
80
80
  );
81
81
 
82
- this._containerResizeObserver = new ResizeObserver(
83
- this._throttleResizeHandler,
84
- );
82
+ // LC-1213: some common older browsers (as of 2023-09-07)
83
+ // don't support ResizeObserver
84
+ if ("ResizeObserver" in window) {
85
+ this._containerResizeObserver = new window.ResizeObserver(
86
+ this._throttleResizeHandler,
87
+ );
85
88
 
86
- if (this._containerRef.current) {
87
- this._containerResizeObserver.observe(this._containerRef.current);
89
+ if (this._containerRef.current) {
90
+ this._containerResizeObserver.observe(
91
+ this._containerRef.current,
92
+ );
93
+ }
88
94
  }
89
95
  }
90
96
 
@@ -18,12 +18,15 @@ import type {
18
18
  KeyHandler,
19
19
  KeypadAPI,
20
20
  } from "../../types";
21
+ import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";
21
22
  import type {StyleType} from "@khanacademy/wonder-blocks-core";
22
23
 
23
24
  type Props = {
24
25
  onElementMounted?: (arg1: any) => void;
25
26
  onDismiss?: () => void;
26
27
  style?: StyleType;
28
+
29
+ onAnalyticsEvent: AnalyticsEventHandlerFn;
27
30
  };
28
31
 
29
32
  class ProvidedKeypad extends React.Component<Props> implements KeypadAPI {
@@ -77,6 +80,13 @@ class ProvidedKeypad extends React.Component<Props> implements KeypadAPI {
77
80
  <Provider store={this.store}>
78
81
  <KeypadContainer
79
82
  onElementMounted={(element) => {
83
+ this.props.onAnalyticsEvent({
84
+ type: "math-input:keypad-opened",
85
+ payload: {
86
+ virtualKeypadVersion: "MATH_INPUT_KEYPAD_V1",
87
+ },
88
+ });
89
+
80
90
  // Append the dispatch methods that we want to expose
81
91
  // externally to the returned React element.
82
92
  const elementWithDispatchMethods = {
@@ -88,10 +98,18 @@ class ProvidedKeypad extends React.Component<Props> implements KeypadAPI {
88
98
  setKeyHandler: this.setKeyHandler,
89
99
  getDOMNode: this.getDOMNode,
90
100
  } as const;
91
- onElementMounted &&
92
- onElementMounted(elementWithDispatchMethods);
101
+ onElementMounted?.(elementWithDispatchMethods);
102
+ }}
103
+ onDismiss={() => {
104
+ this.props.onAnalyticsEvent({
105
+ type: "math-input:keypad-closed",
106
+ payload: {
107
+ virtualKeypadVersion: "MATH_INPUT_KEYPAD_V1",
108
+ },
109
+ });
110
+
111
+ onDismiss?.();
93
112
  }}
94
- onDismiss={onDismiss}
95
113
  style={style}
96
114
  />
97
115
  </Provider>
@@ -3,6 +3,7 @@ import * as React from "react";
3
3
  import {MobileKeypad} from "./keypad";
4
4
  import LegacyKeypad from "./keypad-legacy";
5
5
 
6
+ import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";
6
7
  import type {StyleType} from "@khanacademy/wonder-blocks-core";
7
8
 
8
9
  type Props = {
@@ -11,6 +12,7 @@ type Props = {
11
12
  style?: StyleType;
12
13
 
13
14
  useV2Keypad?: boolean;
15
+ onAnalyticsEvent: AnalyticsEventHandlerFn;
14
16
  };
15
17
 
16
18
  function KeypadSwitch(props: Props) {
@@ -18,6 +20,9 @@ function KeypadSwitch(props: Props) {
18
20
 
19
21
  const KeypadComponent = useV2Keypad ? MobileKeypad : LegacyKeypad;
20
22
 
23
+ // Note: Although we pass the "onAnalyticsEvent" to both keypad components,
24
+ // only the current one uses it. There's no point in instrumenting the
25
+ // legacy keypad given that it's on its way out the door.
21
26
  return <KeypadComponent {...rest} />;
22
27
  }
23
28
 
@@ -1,3 +1,4 @@
1
+ import {action} from "@storybook/addon-actions";
1
2
  import * as React from "react";
2
3
 
3
4
  import type {KeypadAPI} from "./types";
@@ -29,6 +30,8 @@ export const Basic = () => {
29
30
  // Whether the keypad is open or not
30
31
  const [keypadOpen, setKeypadOpen] = React.useState<boolean>(false);
31
32
 
33
+ const input = React.useRef<any>(null);
34
+
32
35
  const toggleKeypad = () => {
33
36
  if (keypadOpen) {
34
37
  keypadElement?.dismiss();
@@ -73,10 +76,11 @@ export const Basic = () => {
73
76
 
74
77
  <KeypadInput
75
78
  value={value}
79
+ ref={input}
76
80
  keypadElement={keypadElement}
77
81
  onChange={(newValue, callback) => {
78
82
  setValue(newValue);
79
- callback();
83
+ callback?.();
80
84
  }}
81
85
  onFocus={() => {
82
86
  keypadElement?.activate();
@@ -92,7 +96,11 @@ export const Basic = () => {
92
96
  setKeypadElement(node);
93
97
  }
94
98
  }}
99
+ onDismiss={() => {
100
+ input.current?.blur();
101
+ }}
95
102
  useV2Keypad={v2Keypad}
103
+ onAnalyticsEvent={async (e) => action("onAnalyticsEvent")(e)}
96
104
  />
97
105
  </div>
98
106
  );