@khanacademy/math-input 13.1.0 → 14.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/dist/types.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { CursorContext } from "./components/input/cursor-contexts";
2
2
  import type Key from "./data/keys";
3
3
  import type { BorderDirection, EchoAnimationType, IconType, KeyType, KeypadType } from "./enums";
4
+ import type { KeypadContextRendererInterface } from "@khanacademy/perseus-core";
4
5
  import type * as React from "react";
5
6
  import type ReactDOM from "react-dom";
6
7
  export type Border = Partial<ReadonlyArray<BorderDirection>>;
@@ -74,4 +75,14 @@ export interface KeypadAPI {
74
75
  setKeyHandler: (keyHandler: KeyHandler) => void;
75
76
  getDOMNode: () => ReturnType<typeof ReactDOM.findDOMNode>;
76
77
  }
78
+ export type KeypadContextType = {
79
+ setKeypadActive: (keypadActive: boolean) => void;
80
+ keypadActive: boolean;
81
+ setKeypadElement: (keypadElement?: KeypadAPI) => void;
82
+ keypadElement: KeypadAPI | null | undefined;
83
+ setRenderer: (renderer?: KeypadContextRendererInterface | null | undefined) => void;
84
+ renderer: KeypadContextRendererInterface | null | undefined;
85
+ setScrollableElement: (scrollableElement?: HTMLElement | null | undefined) => void;
86
+ scrollableElement: HTMLElement | null | undefined;
87
+ };
77
88
  export {};
@@ -0,0 +1 @@
1
+ export declare const libVersion = "__lib_version__";
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": "13.1.0",
6
+ "version": "14.0.1",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -20,7 +20,7 @@
20
20
  "source": "src/index.ts",
21
21
  "scripts": {},
22
22
  "dependencies": {
23
- "@khanacademy/perseus-core": "1.1.0",
23
+ "@khanacademy/perseus-core": "1.1.1",
24
24
  "mathquill": "git+https://git@github.com/Khan/mathquill.git#32d9f351aaa68537170b3120a52e99b8def3a2c3",
25
25
  "performance-now": "^0.2.0"
26
26
  },
@@ -34,7 +34,7 @@
34
34
  "aphrodite": "^1.1.0",
35
35
  "jquery": "^2.1.1",
36
36
  "katex": "^0.11.1",
37
- "perseus-build-settings": "^0.2.0",
37
+ "perseus-build-settings": "^0.2.1",
38
38
  "prop-types": "15.6.1",
39
39
  "react": "^16.8.0",
40
40
  "react-dom": "^16.8.0",
@@ -12,10 +12,10 @@ import React, {useState} from "react";
12
12
 
13
13
  import {KeypadType} from "../../enums";
14
14
  import MathInput from "../input/math-input";
15
- import KeypadContext from "../keypad-context";
15
+ import {KeypadContext, StatefulKeypadContextProvider} from "../keypad-context";
16
16
  import KeypadSwitch from "../keypad-switch";
17
17
 
18
- import type {KeypadAPI, KeypadConfiguration} from "../../types";
18
+ import type {KeypadConfiguration} from "../../types";
19
19
 
20
20
  const MQ = MathQuill.getInterface(2);
21
21
 
@@ -73,25 +73,11 @@ function KeypadWithContext() {
73
73
  }
74
74
 
75
75
  function ConnectedMathInput({keypadConfiguration = defaultConfiguration}) {
76
- const [keypadElement, setKeypadElement] = useState<KeypadAPI | null>();
77
- const [renderer, setRenderer] = useState<any>(null);
78
- const [scrollableElement, setScrollableElement] =
79
- useState<HTMLElement | null>();
80
-
81
76
  return (
82
- <KeypadContext.Provider
83
- value={{
84
- setKeypadElement,
85
- keypadElement,
86
- setRenderer,
87
- renderer,
88
- setScrollableElement,
89
- scrollableElement,
90
- }}
91
- >
77
+ <StatefulKeypadContextProvider>
92
78
  <InputWithContext keypadConfiguration={keypadConfiguration} />
93
79
  <KeypadWithContext />
94
- </KeypadContext.Provider>
80
+ </StatefulKeypadContextProvider>
95
81
  );
96
82
  }
97
83
 
@@ -170,7 +170,6 @@ exports[`keypad should snapshot expanded: first render 1`] = `
170
170
  <div
171
171
  aria-label="Keypad"
172
172
  class="default_xu2jcg-o_O-keypadGrid_ztxlrb-o_O-expressionGrid_1fuqhx9"
173
- role="grid"
174
173
  tabindex="0"
175
174
  >
176
175
  <div
@@ -1223,7 +1222,6 @@ exports[`keypad should snapshot unexpanded: first render 1`] = `
1223
1222
  <div
1224
1223
  aria-label="Keypad"
1225
1224
  class="default_xu2jcg-o_O-keypadGrid_ztxlrb-o_O-expressionGrid_1fuqhx9"
1226
- role="grid"
1227
1225
  tabindex="0"
1228
1226
  >
1229
1227
  <div
@@ -151,7 +151,6 @@ export default function Keypad(props: Props) {
151
151
  <View style={styles.keypadInnerContainer}>
152
152
  <View
153
153
  style={[styles.keypadGrid, gridStyle]}
154
- role="grid"
155
154
  tabIndex={0}
156
155
  aria-label="Keypad"
157
156
  >
@@ -4,6 +4,7 @@ import ReactDOM from "react-dom";
4
4
 
5
5
  import {View} from "../../fake-react-native-web/index";
6
6
 
7
+ import Keypad from "./keypad";
7
8
  import {expandedViewThreshold} from "./utils";
8
9
 
9
10
  import type Key from "../../data/keys";
@@ -16,8 +17,6 @@ import type {
16
17
  import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";
17
18
  import type {StyleType} from "@khanacademy/wonder-blocks-core";
18
19
 
19
- import Keypad from "./index";
20
-
21
20
  /**
22
21
  * This is the v2 equivalent of v1's ProvidedKeypad. It follows the same
23
22
  * external API so that it can be hot-swapped with the v1 keypad and
@@ -34,10 +33,11 @@ type Props = {
34
33
  onDismiss?: () => void;
35
34
  style?: StyleType;
36
35
  onAnalyticsEvent: AnalyticsEventHandlerFn;
36
+ setKeypadActive: (keypadActive: boolean) => void;
37
+ keypadActive: boolean;
37
38
  };
38
39
 
39
40
  type State = {
40
- active: boolean;
41
41
  containerWidth: number;
42
42
  hasBeenActivated: boolean;
43
43
  keypadConfig?: KeypadConfiguration;
@@ -52,7 +52,6 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
52
52
  hasMounted = false;
53
53
 
54
54
  state: State = {
55
- active: false,
56
55
  containerWidth: 0,
57
56
  hasBeenActivated: false,
58
57
  };
@@ -109,16 +108,15 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
109
108
  };
110
109
 
111
110
  activate: () => void = () => {
111
+ this.props.setKeypadActive(true);
112
112
  this.setState({
113
- active: true,
114
113
  hasBeenActivated: true,
115
114
  });
116
115
  };
117
116
 
118
117
  dismiss: () => void = () => {
119
- this.setState({active: false}, () => {
120
- this.props.onDismiss?.();
121
- });
118
+ this.props.setKeypadActive(false);
119
+ this.props.onDismiss?.();
122
120
  };
123
121
 
124
122
  configure: (configuration: KeypadConfiguration, cb: () => void) => void = (
@@ -162,14 +160,14 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
162
160
  }
163
161
 
164
162
  render(): React.ReactNode {
165
- const {style} = this.props;
166
- const {active, hasBeenActivated, containerWidth, cursor, keypadConfig} =
163
+ const {keypadActive, style} = this.props;
164
+ const {hasBeenActivated, containerWidth, cursor, keypadConfig} =
167
165
  this.state;
168
166
 
169
167
  const containerStyle = [
170
168
  // internal styles
171
169
  styles.keypadContainer,
172
- active && styles.activeKeypadContainer,
170
+ keypadActive && styles.activeKeypadContainer,
173
171
  // styles passed as props
174
172
  ...(Array.isArray(style) ? style : [style]),
175
173
  ];
@@ -179,7 +177,7 @@ class MobileKeypad extends React.Component<Props, State> implements KeypadAPI {
179
177
  // during the initial render.
180
178
  // Done inline (dynamicStyle) since stylesheets might not be loaded yet.
181
179
  let dynamicStyle = {};
182
- if (!active && !hasBeenActivated) {
180
+ if (!keypadActive && !hasBeenActivated) {
183
181
  dynamicStyle = {visibility: "hidden"};
184
182
  }
185
183
 
@@ -0,0 +1,61 @@
1
+ /**
2
+ * KeypadContext provides a way to the Keypad and Perseus Renderers to
3
+ * communicate.
4
+ *
5
+ * The StatefulKeypadContextProvider wraps the application
6
+ * while KeypadContext.Consumer wraps things that need this state:
7
+ * - mobile keypad usages
8
+ * - Perseus Renderers (Server/Item/Article)
9
+ */
10
+ import * as React from "react";
11
+ import {useState} from "react";
12
+
13
+ import type {KeypadAPI, KeypadContextType} from "../types";
14
+ import type {KeypadContextRendererInterface} from "@khanacademy/perseus-core";
15
+
16
+ // @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>'.
17
+ export const KeypadContext: React.Context<KeypadContextType> =
18
+ React.createContext({
19
+ setKeypadActive: (keypadActive) => {},
20
+ keypadActive: false,
21
+ setKeypadElement: (keypadElement) => {},
22
+ keypadElement: null,
23
+ setRenderer: (renderer) => {},
24
+ renderer: null,
25
+ setScrollableElement: (scrollableElement) => {},
26
+ scrollableElement: null,
27
+ });
28
+
29
+ type Props = React.PropsWithChildren<unknown>;
30
+
31
+ export function StatefulKeypadContextProvider(props: Props) {
32
+ // whether or not to display the keypad
33
+ const [keypadActive, setKeypadActive] = useState<boolean>(false);
34
+ // used to communicate between the keypad and the Renderer
35
+ const [keypadElement, setKeypadElement] = useState<KeypadAPI | null>();
36
+ // this is a KeypadContextRendererInterface from Perseus
37
+ const [renderer, setRenderer] =
38
+ useState<KeypadContextRendererInterface | null>();
39
+ const [scrollableElement, setScrollableElement] =
40
+ useState<HTMLElement | null>();
41
+
42
+ return (
43
+ <KeypadContext.Provider
44
+ value={{
45
+ setKeypadActive,
46
+ keypadActive,
47
+ setKeypadElement,
48
+ keypadElement,
49
+ setRenderer,
50
+ renderer,
51
+ // The scrollableElement options can likely be removed after
52
+ // the exercises-package is officially deprecated. They don't appear
53
+ // to be used anywhere except for the exercises-package and tests.
54
+ setScrollableElement,
55
+ scrollableElement,
56
+ }}
57
+ >
58
+ {props.children}
59
+ </KeypadContext.Provider>
60
+ );
61
+ }
@@ -22,6 +22,8 @@ import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";
22
22
  import type {StyleType} from "@khanacademy/wonder-blocks-core";
23
23
 
24
24
  type Props = {
25
+ setKeypadActive: (keypadActive: boolean) => void;
26
+ keypadActive: boolean;
25
27
  onElementMounted?: (arg1: any) => void;
26
28
  onDismiss?: () => void;
27
29
  style?: StyleType;
@@ -37,12 +39,22 @@ class ProvidedKeypad extends React.Component<Props> implements KeypadAPI {
37
39
  this.store = createStore();
38
40
  }
39
41
 
42
+ componentDidUpdate(prevProps) {
43
+ if (this.props.keypadActive && !prevProps.keypadActive) {
44
+ this.store.dispatch(activateKeypad());
45
+ }
46
+
47
+ if (!this.props.keypadActive && prevProps.keypadActive) {
48
+ this.store.dispatch(dismissKeypad());
49
+ }
50
+ }
51
+
40
52
  activate: () => void = () => {
41
- this.store.dispatch(activateKeypad());
53
+ this.props.setKeypadActive(true);
42
54
  };
43
55
 
44
56
  dismiss: () => void = () => {
45
- this.store.dispatch(dismissKeypad());
57
+ this.props.setKeypadActive(false);
46
58
  };
47
59
 
48
60
  configure: (configuration: KeypadConfiguration, cb: () => void) => void = (
@@ -73,43 +85,47 @@ class ProvidedKeypad extends React.Component<Props> implements KeypadAPI {
73
85
  return ReactDOM.findDOMNode(this);
74
86
  };
75
87
 
88
+ onElementMounted: (element: any) => void = (element) => {
89
+ this.props.onAnalyticsEvent({
90
+ type: "math-input:keypad-opened",
91
+ payload: {
92
+ virtualKeypadVersion: "MATH_INPUT_KEYPAD_V1",
93
+ },
94
+ });
95
+
96
+ // Append the dispatch methods that we want to expose
97
+ // externally to the returned React element.
98
+ const elementWithDispatchMethods = {
99
+ ...element,
100
+ activate: this.activate,
101
+ dismiss: this.dismiss,
102
+ configure: this.configure,
103
+ setCursor: this.setCursor,
104
+ setKeyHandler: this.setKeyHandler,
105
+ getDOMNode: this.getDOMNode,
106
+ } as const;
107
+ this.props.onElementMounted?.(elementWithDispatchMethods);
108
+ };
109
+
110
+ onDismiss: () => void = () => {
111
+ this.props.onAnalyticsEvent({
112
+ type: "math-input:keypad-closed",
113
+ payload: {
114
+ virtualKeypadVersion: "MATH_INPUT_KEYPAD_V1",
115
+ },
116
+ });
117
+
118
+ this.props.onDismiss?.();
119
+ };
120
+
76
121
  render(): React.ReactNode {
77
- const {onElementMounted, onDismiss, style} = this.props;
122
+ const {style} = this.props;
78
123
 
79
124
  return (
80
125
  <Provider store={this.store}>
81
126
  <KeypadContainer
82
- onElementMounted={(element) => {
83
- this.props.onAnalyticsEvent({
84
- type: "math-input:keypad-opened",
85
- payload: {
86
- virtualKeypadVersion: "MATH_INPUT_KEYPAD_V1",
87
- },
88
- });
89
-
90
- // Append the dispatch methods that we want to expose
91
- // externally to the returned React element.
92
- const elementWithDispatchMethods = {
93
- ...element,
94
- activate: this.activate,
95
- dismiss: this.dismiss,
96
- configure: this.configure,
97
- setCursor: this.setCursor,
98
- setKeyHandler: this.setKeyHandler,
99
- getDOMNode: this.getDOMNode,
100
- } as const;
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?.();
112
- }}
127
+ onElementMounted={this.onElementMounted}
128
+ onDismiss={this.onDismiss}
113
129
  style={style}
114
130
  />
115
131
  </Provider>
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
 
3
3
  import {MobileKeypad} from "./keypad";
4
+ import {KeypadContext} from "./keypad-context";
4
5
  import LegacyKeypad from "./keypad-legacy";
5
6
 
6
7
  import type {AnalyticsEventHandlerFn} from "@khanacademy/perseus-core";
@@ -23,7 +24,19 @@ function KeypadSwitch(props: Props) {
23
24
  // Note: Although we pass the "onAnalyticsEvent" to both keypad components,
24
25
  // only the current one uses it. There's no point in instrumenting the
25
26
  // legacy keypad given that it's on its way out the door.
26
- return <KeypadComponent {...rest} />;
27
+ return (
28
+ <KeypadContext.Consumer>
29
+ {({setKeypadActive, keypadActive}) => {
30
+ return (
31
+ <KeypadComponent
32
+ {...rest}
33
+ keypadActive={keypadActive}
34
+ setKeypadActive={setKeypadActive}
35
+ />
36
+ );
37
+ }}
38
+ </KeypadContext.Consumer>
39
+ );
27
40
  }
28
41
 
29
42
  export default KeypadSwitch;
@@ -1,9 +1,13 @@
1
1
  import {action} from "@storybook/addon-actions";
2
2
  import * as React from "react";
3
3
 
4
- import type {KeypadAPI} from "./types";
5
-
6
- import {KeypadInput, KeypadType, MobileKeypad} from "./index";
4
+ import {
5
+ KeypadInput,
6
+ KeypadType,
7
+ MobileKeypad,
8
+ StatefulKeypadContextProvider,
9
+ KeypadContext,
10
+ } from "./index";
7
11
 
8
12
  export default {
9
13
  title: "math-input/Full Mobile MathInput",
@@ -19,32 +23,19 @@ export default {
19
23
  },
20
24
  };
21
25
 
22
- export const Basic = () => {
26
+ const Basic = ({keypadElement, setKeypadElement}) => {
23
27
  const [value, setValue] = React.useState("");
24
- // Reference to the keypad
25
- const [keypadElement, setKeypadElement] = React.useState<KeypadAPI>();
26
28
  // Whether to use Expression or Fraction keypad
27
29
  const [expression, setExpression] = React.useState<boolean>(false);
28
30
  // Whether to use CDOT or TIMES
29
31
  const [times, setTimes] = React.useState<boolean>(true);
30
32
  // Whether to use v1 or v2 keypad
31
33
  const [v2Keypad, setV2Keypad] = React.useState<boolean>(true);
32
- // Whether the keypad is open or not
33
- const [keypadOpen, setKeypadOpen] = React.useState<boolean>(false);
34
34
 
35
35
  const input = React.useRef<KeypadInput>(null);
36
36
 
37
37
  const timesLabel = times ? "CDOT" : "TIMES";
38
38
 
39
- const toggleKeypad = () => {
40
- if (keypadOpen) {
41
- keypadElement?.dismiss();
42
- } else {
43
- keypadElement?.activate();
44
- }
45
- setKeypadOpen(!keypadOpen);
46
- };
47
-
48
39
  React.useEffect(() => {
49
40
  keypadElement?.configure(
50
41
  {
@@ -73,9 +64,6 @@ export const Basic = () => {
73
64
  <button onClick={() => setV2Keypad(!v2Keypad)}>
74
65
  {`Use ${v2Keypad ? "Legacy" : "New"} Keypad`}
75
66
  </button>
76
- <button onClick={() => toggleKeypad()}>
77
- {`Toggle Keypad`}
78
- </button>
79
67
  <button onClick={() => setTimes(!times)}>
80
68
  {`Toggle to ` + timesLabel}
81
69
  </button>
@@ -111,3 +99,18 @@ export const Basic = () => {
111
99
  </div>
112
100
  );
113
101
  };
102
+
103
+ export function Wrapped() {
104
+ return (
105
+ <StatefulKeypadContextProvider>
106
+ <KeypadContext.Consumer>
107
+ {({keypadElement, setKeypadElement}) => (
108
+ <Basic
109
+ keypadElement={keypadElement}
110
+ setKeypadElement={setKeypadElement}
111
+ />
112
+ )}
113
+ </KeypadContext.Consumer>
114
+ </StatefulKeypadContextProvider>
115
+ );
116
+ }
package/src/index.ts CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  import "../less/main.less";
6
6
 
7
+ export {libVersion} from "./version";
8
+
7
9
  // MathInput input field (MathQuill wrapper)
8
10
  export {default as KeypadInput} from "./components/input/math-input";
9
11
 
@@ -30,7 +32,10 @@ export {default as MobileKeypad} from "./components/keypad-switch";
30
32
  export {default as DesktopKeypad} from "./components/keypad";
31
33
 
32
34
  // Context used to pass data/refs around
33
- export {default as KeypadContext} from "./components/keypad-context";
35
+ export {
36
+ KeypadContext,
37
+ StatefulKeypadContextProvider,
38
+ } from "./components/keypad-context";
34
39
 
35
40
  // External API of the "Provided" keypad component
36
41
  export {keypadElementPropType} from "./components/prop-types";
package/src/types.ts CHANGED
@@ -7,6 +7,7 @@ import type {
7
7
  KeyType,
8
8
  KeypadType,
9
9
  } from "./enums";
10
+ import type {KeypadContextRendererInterface} from "@khanacademy/perseus-core";
10
11
  import type * as React from "react";
11
12
  import type ReactDOM from "react-dom";
12
13
 
@@ -105,3 +106,18 @@ export interface KeypadAPI {
105
106
  setKeyHandler: (keyHandler: KeyHandler) => void;
106
107
  getDOMNode: () => ReturnType<typeof ReactDOM.findDOMNode>;
107
108
  }
109
+
110
+ export type KeypadContextType = {
111
+ setKeypadActive: (keypadActive: boolean) => void;
112
+ keypadActive: boolean;
113
+ setKeypadElement: (keypadElement?: KeypadAPI) => void;
114
+ keypadElement: KeypadAPI | null | undefined;
115
+ setRenderer: (
116
+ renderer?: KeypadContextRendererInterface | null | undefined,
117
+ ) => void;
118
+ renderer: KeypadContextRendererInterface | null | undefined;
119
+ setScrollableElement: (
120
+ scrollableElement?: HTMLElement | null | undefined,
121
+ ) => void;
122
+ scrollableElement: HTMLElement | null | undefined;
123
+ };
package/src/version.ts ADDED
@@ -0,0 +1,10 @@
1
+ // This file is processed by a Rollup plugin (replace) to inject the production
2
+ // version number during the release build.
3
+ // In dev, you'll never see the version number.
4
+
5
+ import {addLibraryVersionToPerseusDebug} from "@khanacademy/perseus-core";
6
+
7
+ const libName = "@khanacademy/math-input";
8
+ export const libVersion = "__lib_version__";
9
+
10
+ addLibraryVersionToPerseusDebug(libName, libVersion);
@@ -6,5 +6,6 @@
6
6
  },
7
7
  "references": [
8
8
  {"path": "../perseus-core/tsconfig-build.json"},
9
+ {"path": "../perseus-core/tsconfig-build.json"},
9
10
  ]
10
11
  }