@khanacademy/math-input 0.7.2 → 1.0.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.
Files changed (176) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/actions/index.d.ts +31 -0
  3. package/dist/actions/index.js.flow +40 -0
  4. package/dist/components/compute-layout-parameters.d.ts +38 -0
  5. package/dist/components/compute-layout-parameters.js.flow +49 -0
  6. package/dist/components/corner-decal.d.ts +12 -0
  7. package/dist/components/corner-decal.js.flow +15 -0
  8. package/dist/components/echo-manager.d.ts +26 -0
  9. package/dist/components/echo-manager.js.flow +29 -0
  10. package/dist/components/empty-keypad-button.d.ts +13 -0
  11. package/dist/components/empty-keypad-button.js.flow +23 -0
  12. package/dist/components/expression-keypad.d.ts +22 -0
  13. package/dist/components/expression-keypad.js.flow +32 -0
  14. package/dist/components/fraction-keypad.d.ts +21 -0
  15. package/dist/components/fraction-keypad.js.flow +30 -0
  16. package/dist/components/gesture-manager.d.ts +74 -0
  17. package/dist/components/gesture-manager.js.flow +82 -0
  18. package/dist/components/gesture-state-machine.d.ts +105 -0
  19. package/dist/components/gesture-state-machine.js.flow +118 -0
  20. package/dist/components/icon.d.ts +15 -0
  21. package/dist/components/icon.js.flow +18 -0
  22. package/dist/components/input/__tests__/test-math-wrapper.d.ts +8 -0
  23. package/dist/components/input/__tests__/test-math-wrapper.js.flow +14 -0
  24. package/dist/components/input/cursor-handle.d.ts +1 -1
  25. package/dist/components/input/cursor-handle.js.flow +1 -1
  26. package/dist/components/input/drag-listener.d.ts +13 -0
  27. package/dist/components/input/drag-listener.js.flow +19 -0
  28. package/dist/components/input/math-input.d.ts +5 -4
  29. package/dist/components/input/math-input.js.flow +5 -4
  30. package/dist/components/input/math-wrapper.d.ts +110 -0
  31. package/dist/components/input/math-wrapper.js.flow +125 -0
  32. package/dist/components/input/scroll-into-view.d.ts +11 -0
  33. package/dist/components/input/scroll-into-view.js.flow +20 -0
  34. package/dist/components/keypad/button-assets.d.ts +4 -3
  35. package/dist/components/keypad/button-assets.js.flow +3 -3
  36. package/dist/components/keypad/button.d.ts +1 -2
  37. package/dist/components/keypad/button.js.flow +1 -2
  38. package/dist/components/keypad/keypad-page-items.d.ts +15 -10
  39. package/dist/components/keypad/keypad-page-items.js.flow +20 -10
  40. package/dist/components/keypad-button.d.ts +52 -0
  41. package/dist/components/keypad-button.js.flow +79 -0
  42. package/dist/components/keypad-container.d.ts +40 -0
  43. package/dist/components/keypad-container.js.flow +58 -0
  44. package/dist/components/keypad.d.ts +31 -0
  45. package/dist/components/keypad.js.flow +40 -0
  46. package/dist/components/many-keypad-button.d.ts +15 -0
  47. package/dist/components/many-keypad-button.js.flow +17 -0
  48. package/dist/components/math-icon.d.ts +16 -0
  49. package/dist/components/math-icon.js.flow +19 -0
  50. package/dist/components/multi-symbol-grid.d.ts +14 -0
  51. package/dist/components/multi-symbol-grid.js.flow +16 -0
  52. package/dist/components/multi-symbol-popover.d.ts +12 -0
  53. package/dist/components/multi-symbol-popover.js.flow +15 -0
  54. package/dist/components/navigation-pad.d.ts +14 -0
  55. package/dist/components/navigation-pad.js.flow +16 -0
  56. package/dist/components/node-manager.d.ts +53 -0
  57. package/dist/components/node-manager.js.flow +65 -0
  58. package/dist/components/popover-manager.d.ts +13 -0
  59. package/dist/components/popover-manager.js.flow +15 -0
  60. package/dist/components/popover-state-machine.d.ts +75 -0
  61. package/dist/components/popover-state-machine.js.flow +83 -0
  62. package/dist/components/provided-keypad.d.ts +8 -7
  63. package/dist/components/provided-keypad.js.flow +8 -7
  64. package/dist/components/styles.d.ts +6 -0
  65. package/dist/components/styles.js.flow +13 -0
  66. package/dist/components/svg-icon.d.ts +12 -0
  67. package/dist/components/svg-icon.js.flow +15 -0
  68. package/dist/components/tabbar/icons.d.ts +3 -2
  69. package/dist/components/tabbar/icons.js.flow +3 -2
  70. package/dist/components/tabbar/item.d.ts +1 -2
  71. package/dist/components/tabbar/item.js.flow +1 -2
  72. package/dist/components/tabbar/tabbar.d.ts +3 -3
  73. package/dist/components/tabbar/tabbar.js.flow +3 -3
  74. package/dist/components/text-icon.d.ts +13 -0
  75. package/dist/components/text-icon.js.flow +16 -0
  76. package/dist/components/touchable-keypad-button.d.ts +30 -0
  77. package/dist/components/touchable-keypad-button.js.flow +35 -0
  78. package/dist/components/two-page-keypad.d.ts +20 -0
  79. package/dist/components/two-page-keypad.js.flow +30 -0
  80. package/dist/components/velocity-tracker.d.ts +48 -0
  81. package/dist/components/velocity-tracker.js.flow +54 -0
  82. package/dist/es/index.js +938 -1059
  83. package/dist/es/index.js.map +1 -1
  84. package/dist/fake-react-native-web/text.d.ts +2 -1
  85. package/dist/fake-react-native-web/text.js.flow +2 -1
  86. package/dist/fake-react-native-web/view.d.ts +3 -2
  87. package/dist/fake-react-native-web/view.js.flow +3 -2
  88. package/dist/index.d.ts +1 -1
  89. package/dist/index.js +988 -1089
  90. package/dist/index.js.flow +1 -4
  91. package/dist/index.js.map +1 -1
  92. package/dist/store/echo-reducer.d.ts +5 -0
  93. package/dist/store/echo-reducer.js.flow +14 -0
  94. package/dist/store/index.d.ts +46 -1
  95. package/dist/store/index.js.flow +64 -1
  96. package/dist/store/input-reducer.d.ts +7 -0
  97. package/dist/store/input-reducer.js.flow +16 -0
  98. package/dist/store/keypad-reducer.d.ts +9 -0
  99. package/dist/store/keypad-reducer.js.flow +18 -0
  100. package/dist/store/layout-reducer.d.ts +21 -0
  101. package/dist/store/layout-reducer.js.flow +30 -0
  102. package/dist/store/pager-reducer.d.ts +13 -0
  103. package/dist/store/pager-reducer.js.flow +22 -0
  104. package/dist/store/shared.d.ts +6 -0
  105. package/dist/store/shared.js.flow +13 -0
  106. package/dist/store/types.d.ts +57 -0
  107. package/dist/store/types.js.flow +63 -0
  108. package/dist/types.d.ts +50 -0
  109. package/dist/types.js.flow +61 -0
  110. package/package.json +1 -1
  111. package/src/actions/{index.js → index.ts} +5 -5
  112. package/src/components/__tests__/{gesture-state-machine_test.js → gesture-state-machine.test.ts} +5 -1
  113. package/src/components/__tests__/{two-page-keypad_test.js → two-page-keypad.test.tsx} +0 -2
  114. package/src/components/{corner-decal.js → corner-decal.tsx} +6 -5
  115. package/src/components/{echo-manager.js → echo-manager.tsx} +29 -24
  116. package/src/components/{empty-keypad-button.js → empty-keypad-button.tsx} +17 -10
  117. package/src/components/{expression-keypad.js → expression-keypad.tsx} +27 -25
  118. package/src/components/{fraction-keypad.js → fraction-keypad.tsx} +21 -16
  119. package/src/components/{gesture-manager.js → gesture-manager.tsx} +10 -4
  120. package/src/components/{gesture-state-machine.js → gesture-state-machine.ts} +49 -3
  121. package/src/components/{icon.js → icon.tsx} +12 -14
  122. package/src/components/input/cursor-handle.tsx +1 -1
  123. package/src/components/input/{drag-listener.js → drag-listener.ts} +4 -0
  124. package/src/components/input/math-input.tsx +10 -9
  125. package/src/components/input/{math-wrapper.js → math-wrapper.ts} +10 -6
  126. package/src/components/input/{scroll-into-view.js → scroll-into-view.ts} +5 -15
  127. package/src/components/keypad/button-assets.tsx +4 -5
  128. package/src/components/keypad/button.tsx +1 -2
  129. package/src/components/keypad/index.tsx +1 -1
  130. package/src/components/keypad/keypad-page-items.tsx +33 -10
  131. package/src/components/{keypad-button.js → keypad-button.tsx} +42 -37
  132. package/src/components/{keypad-container.js → keypad-container.tsx} +41 -23
  133. package/src/components/{keypad.js → keypad.tsx} +31 -23
  134. package/src/components/{many-keypad-button.js → many-keypad-button.tsx} +8 -6
  135. package/src/components/{math-icon.js → math-icon.tsx} +7 -6
  136. package/src/components/{multi-symbol-grid.js → multi-symbol-grid.tsx} +8 -8
  137. package/src/components/{multi-symbol-popover.js → multi-symbol-popover.tsx} +5 -6
  138. package/src/components/{navigation-pad.js → navigation-pad.tsx} +7 -6
  139. package/src/components/{node-manager.js → node-manager.ts} +16 -4
  140. package/src/components/{popover-manager.js → popover-manager.tsx} +13 -16
  141. package/src/components/{popover-state-machine.js → popover-state-machine.ts} +21 -2
  142. package/src/components/prop-types.js +1 -67
  143. package/src/components/provided-keypad.tsx +14 -12
  144. package/src/components/{svg-icon.js → svg-icon.tsx} +5 -6
  145. package/src/components/tabbar/icons.tsx +4 -2
  146. package/src/components/tabbar/item.tsx +1 -3
  147. package/src/components/tabbar/{tabbar.stories.js → tabbar.stories.tsx} +10 -1
  148. package/src/components/tabbar/tabbar.tsx +3 -3
  149. package/src/components/{text-icon.js → text-icon.tsx} +7 -6
  150. package/src/components/{touchable-keypad-button.js → touchable-keypad-button.tsx} +19 -16
  151. package/src/components/{two-page-keypad.js → two-page-keypad.tsx} +13 -9
  152. package/src/components/{velocity-tracker.js → velocity-tracker.ts} +14 -4
  153. package/src/fake-react-native-web/text.tsx +2 -1
  154. package/src/fake-react-native-web/view.tsx +3 -2
  155. package/src/index.ts +1 -4
  156. package/src/store/echo-reducer.ts +58 -0
  157. package/src/store/index.ts +14 -425
  158. package/src/store/input-reducer.ts +55 -0
  159. package/src/store/keypad-reducer.ts +62 -0
  160. package/src/store/layout-reducer.ts +133 -0
  161. package/src/store/pager-reducer.ts +141 -0
  162. package/src/store/shared.ts +12 -0
  163. package/src/store/types.ts +65 -0
  164. package/src/types.ts +69 -0
  165. package/tsconfig.tsbuildinfo +1 -1
  166. package/src/components/app.js +0 -73
  167. package/src/demo.js +0 -9
  168. package/src/native-app.js +0 -85
  169. /package/src/components/__tests__/{node-manager_test.js → node-manager.test.ts} +0 -0
  170. /package/src/components/{compute-layout-parameters.js → compute-layout-parameters.ts} +0 -0
  171. /package/src/components/input/__tests__/{context-tracking_test.js → context-tracking.test.ts} +0 -0
  172. /package/src/components/input/__tests__/{mathquill_test.js → mathquill.test.ts} +0 -0
  173. /package/src/components/input/__tests__/{test-math-wrapper.jsx → test-math-wrapper.ts} +0 -0
  174. /package/src/components/keypad/{button.stories.js → button.stories.tsx} +0 -0
  175. /package/src/components/{styles.js → styles.ts} +0 -0
  176. /package/src/components/tabbar/__tests__/{tabbar_test.js → tabbar.test.tsx} +0 -0
@@ -0,0 +1,58 @@
1
+ import {EchoAnimationTypes, KeyTypes} from "../consts";
2
+ import KeyConfigs from "../data/key-configs";
3
+
4
+ import type {EchoState} from "./types";
5
+
6
+ // Used to generate unique animation IDs for the echo animations. The actual
7
+ // values are irrelevant as long as they are unique.
8
+ let _lastAnimationId = 0;
9
+
10
+ const initialEchoState = {
11
+ echoes: [],
12
+ } as const;
13
+
14
+ const echoReducer = function (state = initialEchoState, action): EchoState {
15
+ switch (action.type) {
16
+ case "PressKey":
17
+ const keyConfig = KeyConfigs[action.key];
18
+
19
+ // Add in the echo animation if the user performs a math
20
+ // operation.
21
+ if (
22
+ keyConfig.type === KeyTypes.VALUE ||
23
+ keyConfig.type === KeyTypes.OPERATOR
24
+ ) {
25
+ return {
26
+ ...state,
27
+ echoes: [
28
+ ...state.echoes,
29
+ {
30
+ animationId: "" + _lastAnimationId++,
31
+ animationType: action.inPopover
32
+ ? EchoAnimationTypes.LONG_FADE_ONLY
33
+ : EchoAnimationTypes.FADE_ONLY,
34
+ borders: action.borders,
35
+ id: keyConfig.id,
36
+ initialBounds: action.initialBounds,
37
+ },
38
+ ],
39
+ };
40
+ }
41
+ return state;
42
+
43
+ case "RemoveEcho":
44
+ const remainingEchoes = state.echoes.filter((echo) => {
45
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'animationId' does not exist on type 'never'.
46
+ return echo.animationId !== action.animationId;
47
+ });
48
+ return {
49
+ ...state,
50
+ echoes: remainingEchoes,
51
+ };
52
+
53
+ default:
54
+ return state;
55
+ }
56
+ };
57
+
58
+ export default echoReducer;
@@ -1,261 +1,20 @@
1
1
  import * as Redux from "redux";
2
2
 
3
- import {tabletCutoffPx} from "../components/common-style";
4
- import {computeLayoutParameters} from "../components/compute-layout-parameters";
5
- import ExpressionKeypad from "../components/expression-keypad";
6
- import FractionKeypad from "../components/fraction-keypad";
7
3
  import GestureManager from "../components/gesture-manager";
8
- import * as CursorContexts from "../components/input/cursor-contexts";
9
- import VelocityTracker from "../components/velocity-tracker";
10
- import {
11
- DeviceOrientations,
12
- DeviceTypes,
13
- EchoAnimationTypes,
14
- KeyTypes,
15
- KeypadTypes,
16
- LayoutModes,
17
- } from "../consts";
18
- import KeyConfigs from "../data/key-configs";
19
4
  import Keys from "../data/keys";
20
5
 
21
- const keypadForType = {
22
- [KeypadTypes.FRACTION]: FractionKeypad,
23
- [KeypadTypes.EXPRESSION]: ExpressionKeypad,
24
- } as const;
6
+ import echoReducer from "./echo-reducer";
7
+ import inputReducer from "./input-reducer";
8
+ import keypadReducer from "./keypad-reducer";
9
+ import layoutReducer from "./layout-reducer";
10
+ import pagerReducer from "./pager-reducer";
11
+ import {defaultKeypadType, keypadForType} from "./shared";
25
12
 
26
- export const createStore = (): any => {
27
- const initialInputState: {
28
- keyHandler: any;
29
- cursor: any;
30
- } = {
31
- keyHandler: null,
32
- cursor: {
33
- context: CursorContexts.NONE,
34
- },
35
- };
36
-
37
- const inputReducer = function (state = initialInputState, action: any) {
38
- switch (action.type) {
39
- case "SetKeyHandler":
40
- return {
41
- ...state,
42
- keyHandler: action.keyHandler,
43
- };
44
-
45
- case "PressKey":
46
- const keyConfig = KeyConfigs[action.key];
47
- if (keyConfig.type !== KeyTypes.KEYPAD_NAVIGATION) {
48
- // This is probably an anti-pattern but it works for the
49
- // case where we don't actually control the state but we
50
- // still want to communicate with the other object
51
- return {
52
- ...state,
53
- cursor: state.keyHandler(keyConfig.id),
54
- };
55
- }
56
-
57
- // TODO(kevinb) get state from MathQuill and store it?
58
- return state;
59
-
60
- case "SetCursor":
61
- return {
62
- ...state,
63
- cursor: action.cursor,
64
- };
65
-
66
- default:
67
- return state;
68
- }
69
- };
70
-
71
- const defaultKeypadType = KeypadTypes.EXPRESSION;
72
-
73
- const initialKeypadState = {
74
- extraKeys: ["x", "y", Keys.THETA, Keys.PI],
75
- keypadType: defaultKeypadType,
76
- active: false,
77
- } as const;
78
-
79
- const keypadReducer = function (
80
- state = initialKeypadState,
81
- action: {
82
- type: string;
83
- },
84
- ) {
85
- switch (action.type) {
86
- case "DismissKeypad":
87
- return {
88
- ...state,
89
- active: false,
90
- };
91
-
92
- case "ActivateKeypad":
93
- return {
94
- ...state,
95
- active: true,
96
- };
97
-
98
- case "ConfigureKeypad":
99
- return {
100
- ...state,
101
- // Default `extraKeys` to the empty array.
102
- extraKeys: [],
103
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'configuration' does not exist on type '{ type: string; }'.
104
- ...action.configuration,
105
- };
106
-
107
- case "PressKey":
108
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'key' does not exist on type '{ type: string; }'.
109
- const keyConfig = KeyConfigs[action.key];
110
- // NOTE(charlie): Our keypad system operates by triggering key
111
- // presses with key IDs in a dumb manner, such that the keys
112
- // don't know what they can do--instead, the store is
113
- // responsible for interpreting key presses and triggering the
114
- // right actions when they occur. Hence, we figure off a
115
- // dismissal here rather than dispatching a dismiss action in
116
- // the first place.
117
- if (keyConfig.id === Keys.DISMISS) {
118
- return keypadReducer(state, {type: "DismissKeypad"});
119
- }
120
- return state;
121
-
122
- default:
123
- return state;
124
- }
125
- };
126
-
127
- // We default to the right-most page. This is done so-as to enforce a
128
- // consistent orientation between the view pager layout and the flattened
129
- // layout, where our default page appears on the far right.
130
- const getDefaultPage = (numPages) => numPages - 1;
131
-
132
- const initialPagerState = {
133
- animateToPosition: false,
134
- currentPage: getDefaultPage(keypadForType[defaultKeypadType].numPages),
135
- // The cumulative differential in the horizontal direction for the
136
- // current swipe.
137
- dx: 0,
138
- numPages: keypadForType[defaultKeypadType].numPages,
139
- pageWidthPx: 0,
140
- velocityTracker: new VelocityTracker(),
141
- } as const;
142
-
143
- const pagerReducer = function (
144
- state = initialPagerState,
145
- action: {
146
- type: string;
147
- },
148
- ) {
149
- switch (action.type) {
150
- case "ConfigureKeypad":
151
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'configuration' does not exist on type '{ type: string; }'.
152
- const {keypadType} = action.configuration;
153
- const {numPages} = keypadForType[keypadType];
154
- return {
155
- ...state,
156
- numPages,
157
- animateToPosition: false,
158
- currentPage: getDefaultPage(numPages),
159
- dx: 0,
160
- };
161
-
162
- case "SetPageSize":
163
- return {
164
- ...state,
165
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'pageWidthPx' does not exist on type '{ type: string; }'.
166
- pageWidthPx: action.pageWidthPx,
167
- };
168
-
169
- case "PressKey":
170
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'key' does not exist on type '{ type: string; }'.
171
- const keyConfig = KeyConfigs[action.key];
172
-
173
- // Reset the keypad page if the user performs a math operation.
174
- if (
175
- keyConfig.type === KeyTypes.VALUE ||
176
- keyConfig.type === KeyTypes.OPERATOR
177
- ) {
178
- return pagerReducer(state, {type: "ResetKeypadPage"});
179
- }
180
- return state;
181
-
182
- case "ResetKeypadPage":
183
- return {
184
- ...state,
185
- animateToPosition: true,
186
- // We start at the right-most page.
187
- currentPage: getDefaultPage(state.numPages),
188
- dx: 0,
189
- };
190
-
191
- case "PageKeypadRight":
192
- const nextPage = Math.min(
193
- state.currentPage + 1,
194
- state.numPages - 1,
195
- );
196
- return {
197
- ...state,
198
- animateToPosition: true,
199
- currentPage: nextPage,
200
- dx: 0,
201
- };
202
-
203
- case "PageKeypadLeft":
204
- const prevPage = Math.max(state.currentPage - 1, 0);
205
- return {
206
- ...state,
207
- animateToPosition: true,
208
- currentPage: prevPage,
209
- dx: 0,
210
- };
211
-
212
- case "OnSwipeChange":
213
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'dx' does not exist on type '{ type: string; }'.
214
- state.velocityTracker.push(action.dx);
215
-
216
- return {
217
- ...state,
218
- animateToPosition: false,
219
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'dx' does not exist on type '{ type: string; }'.
220
- dx: action.dx,
221
- };
222
-
223
- case "OnSwipeEnd":
224
- const {pageWidthPx, velocityTracker} = state;
225
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'dx' does not exist on type '{ type: string; }'.
226
- const {dx} = action;
227
- const velocity = velocityTracker.getVelocity();
228
-
229
- // NOTE(charlie): These will need refinement. The velocity comes
230
- // from Framer.
231
- const minFlingVelocity = 0.1;
232
- const minFlingDistance = 10;
233
-
234
- const shouldPageRight =
235
- dx < -pageWidthPx / 2 ||
236
- (velocity < -minFlingVelocity && dx < -minFlingDistance);
237
-
238
- const shouldPageLeft =
239
- dx > pageWidthPx / 2 ||
240
- (velocity > minFlingVelocity && dx > minFlingDistance);
241
-
242
- if (shouldPageRight) {
243
- return pagerReducer(state, {type: "PageKeypadRight"});
244
- } else if (shouldPageLeft) {
245
- return pagerReducer(state, {type: "PageKeypadLeft"});
246
- }
247
-
248
- return {
249
- ...state,
250
- animateToPosition: true,
251
- dx: 0,
252
- };
253
-
254
- default:
255
- return state;
256
- }
257
- };
13
+ import type {GestureState} from "./types";
258
14
 
15
+ export const createStore = () => {
16
+ // TODO(matthewc)[LC-752]: gestureReducer can't be moved from this file
17
+ // because it depends on `store` being in scope (see note below)
259
18
  const createGestureManager = (swipeEnabled: boolean) => {
260
19
  return new GestureManager(
261
20
  {
@@ -302,7 +61,10 @@ export const createStore = (): any => {
302
61
  ),
303
62
  } as const;
304
63
 
305
- const gestureReducer = function (state = initialGestureState, action: any) {
64
+ const gestureReducer = function (
65
+ state = initialGestureState,
66
+ action: any,
67
+ ): GestureState {
306
68
  switch (action.type) {
307
69
  case "DismissKeypad":
308
70
  // NOTE(charlie): In the past, we enforced the "gesture manager
@@ -342,179 +104,6 @@ export const createStore = (): any => {
342
104
  }
343
105
  };
344
106
 
345
- // Used to generate unique animation IDs for the echo animations. The actual
346
- // values are irrelevant as long as they are unique.
347
- let _lastAnimationId = 0;
348
-
349
- const initialEchoState = {
350
- echoes: [],
351
- } as const;
352
-
353
- const echoReducer = function (state = initialEchoState, action) {
354
- switch (action.type) {
355
- case "PressKey":
356
- const keyConfig = KeyConfigs[action.key];
357
-
358
- // Add in the echo animation if the user performs a math
359
- // operation.
360
- if (
361
- keyConfig.type === KeyTypes.VALUE ||
362
- keyConfig.type === KeyTypes.OPERATOR
363
- ) {
364
- return {
365
- ...state,
366
- echoes: [
367
- ...state.echoes,
368
- {
369
- animationId: "" + _lastAnimationId++,
370
- animationType: action.inPopover
371
- ? EchoAnimationTypes.LONG_FADE_ONLY
372
- : EchoAnimationTypes.FADE_ONLY,
373
- borders: action.borders,
374
- id: keyConfig.id,
375
- initialBounds: action.initialBounds,
376
- },
377
- ],
378
- };
379
- }
380
- return state;
381
-
382
- case "RemoveEcho":
383
- const remainingEchoes = state.echoes.filter((echo) => {
384
- // @ts-expect-error [FEI-5003] - TS2339 - Property 'animationId' does not exist on type 'never'.
385
- return echo.animationId !== action.animationId;
386
- });
387
- return {
388
- ...state,
389
- echoes: remainingEchoes,
390
- };
391
-
392
- default:
393
- return state;
394
- }
395
- };
396
-
397
- const initialLayoutState = {
398
- gridDimensions: {
399
- numRows: keypadForType[defaultKeypadType].rows,
400
- numColumns: keypadForType[defaultKeypadType].columns,
401
- numMaxVisibleRows: keypadForType[defaultKeypadType].maxVisibleRows,
402
- numPages: keypadForType[defaultKeypadType].numPages,
403
- },
404
- buttonDimensions: {
405
- widthPx: 48,
406
- heightPx: 48,
407
- },
408
- pageDimensions: {
409
- pageWidthPx: 0,
410
- pageHeightPx: 0,
411
- },
412
- layoutMode: LayoutModes.FULLSCREEN,
413
- paginationEnabled: false,
414
- navigationPadEnabled: false,
415
- } as const;
416
-
417
- /**
418
- * Compute the additional layout state based on the provided page and grid
419
- * dimensions.
420
- */
421
- const layoutParametersForDimensions = (
422
- pageDimensions:
423
- | {
424
- pageHeightPx: never;
425
- pageWidthPx: never;
426
- }
427
- | {
428
- pageHeightPx: number;
429
- pageWidthPx: number;
430
- },
431
- gridDimensions,
432
- ) => {
433
- const {pageWidthPx, pageHeightPx} = pageDimensions;
434
-
435
- // Determine the device type and orientation.
436
- const deviceOrientation =
437
- pageWidthPx > pageHeightPx
438
- ? DeviceOrientations.LANDSCAPE
439
- : DeviceOrientations.PORTRAIT;
440
- const deviceType =
441
- Math.min(pageWidthPx, pageHeightPx) > tabletCutoffPx
442
- ? DeviceTypes.TABLET
443
- : DeviceTypes.PHONE;
444
-
445
- // Using that information, make some decisions (or assumptions)
446
- // about the resulting layout.
447
- const navigationPadEnabled = deviceType === DeviceTypes.TABLET;
448
- const paginationEnabled =
449
- deviceType === DeviceTypes.PHONE &&
450
- deviceOrientation === DeviceOrientations.PORTRAIT;
451
-
452
- const deviceInfo = {deviceOrientation, deviceType} as const;
453
- const layoutOptions = {
454
- navigationPadEnabled,
455
- paginationEnabled,
456
- // HACK(charlie): It's not great that we're making assumptions about
457
- // the toolbar (which is rendered by webapp, and should always be
458
- // visible and anchored to the bottom of the page for phone and
459
- // tablet exercises). But this is primarily a heuristic (the goal is
460
- // to preserve a 'good' amount of space between the top of the
461
- // keypad and the top of the page) so we afford to have some margin
462
- // of error.
463
- toolbarEnabled: true,
464
- } as const;
465
-
466
- return {
467
- ...computeLayoutParameters(
468
- gridDimensions,
469
- pageDimensions,
470
- deviceInfo,
471
- layoutOptions,
472
- ),
473
- // Pass along some of the layout information, so that other
474
- // components in the heirarchy can adapt appropriately.
475
- navigationPadEnabled,
476
- paginationEnabled,
477
- };
478
- };
479
-
480
- const layoutReducer = function (state = initialLayoutState, action: any) {
481
- switch (action.type) {
482
- case "ConfigureKeypad":
483
- const {keypadType} = action.configuration;
484
- const gridDimensions = {
485
- numRows: keypadForType[keypadType].rows,
486
- numColumns: keypadForType[keypadType].columns,
487
- numMaxVisibleRows: keypadForType[keypadType].maxVisibleRows,
488
- numPages: keypadForType[keypadType].numPages,
489
- } as const;
490
-
491
- return {
492
- ...state,
493
- ...layoutParametersForDimensions(
494
- state.pageDimensions,
495
- gridDimensions,
496
- ),
497
- gridDimensions,
498
- };
499
-
500
- case "SetPageSize":
501
- const {pageWidthPx, pageHeightPx} = action;
502
- const pageDimensions = {pageWidthPx, pageHeightPx} as const;
503
-
504
- return {
505
- ...state,
506
- ...layoutParametersForDimensions(
507
- pageDimensions,
508
- state.gridDimensions,
509
- ),
510
- pageDimensions,
511
- };
512
-
513
- default:
514
- return state;
515
- }
516
- };
517
-
518
107
  const reducer = Redux.combineReducers({
519
108
  input: inputReducer,
520
109
  keypad: keypadReducer,
@@ -0,0 +1,55 @@
1
+ import * as CursorContexts from "../components/input/cursor-contexts";
2
+ import {KeyTypes} from "../consts";
3
+ import KeyConfigs from "../data/key-configs";
4
+
5
+ import type {Cursor, KeyHandler} from "../types";
6
+ import type {InputState} from "./types";
7
+
8
+ const initialInputState: {
9
+ keyHandler: KeyHandler | null;
10
+ cursor: Cursor;
11
+ } = {
12
+ keyHandler: null,
13
+ cursor: {
14
+ context: CursorContexts.NONE,
15
+ },
16
+ };
17
+
18
+ const inputReducer = function (
19
+ state = initialInputState,
20
+ action: any,
21
+ ): InputState {
22
+ switch (action.type) {
23
+ case "SetKeyHandler":
24
+ return {
25
+ ...state,
26
+ keyHandler: action.keyHandler,
27
+ };
28
+
29
+ case "PressKey":
30
+ const keyConfig = KeyConfigs[action.key];
31
+ if (keyConfig.type !== KeyTypes.KEYPAD_NAVIGATION) {
32
+ // This is probably an anti-pattern but it works for the
33
+ // case where we don't actually control the state but we
34
+ // still want to communicate with the other object
35
+ return {
36
+ ...state,
37
+ cursor: state.keyHandler?.(keyConfig.id),
38
+ };
39
+ }
40
+
41
+ // TODO(kevinb) get state from MathQuill and store it?
42
+ return state;
43
+
44
+ case "SetCursor":
45
+ return {
46
+ ...state,
47
+ cursor: action.cursor,
48
+ };
49
+
50
+ default:
51
+ return state;
52
+ }
53
+ };
54
+
55
+ export default inputReducer;
@@ -0,0 +1,62 @@
1
+ import KeyConfigs from "../data/key-configs";
2
+ import Keys from "../data/keys";
3
+
4
+ import {defaultKeypadType} from "./shared";
5
+
6
+ import type {KeypadState} from "./types";
7
+
8
+ const initialKeypadState = {
9
+ extraKeys: ["x", "y", Keys.THETA, Keys.PI],
10
+ keypadType: defaultKeypadType,
11
+ active: false,
12
+ } as const;
13
+
14
+ const keypadReducer = function (
15
+ state = initialKeypadState,
16
+ action: {
17
+ type: string;
18
+ },
19
+ ): KeypadState {
20
+ switch (action.type) {
21
+ case "DismissKeypad":
22
+ return {
23
+ ...state,
24
+ active: false,
25
+ };
26
+
27
+ case "ActivateKeypad":
28
+ return {
29
+ ...state,
30
+ active: true,
31
+ };
32
+
33
+ case "ConfigureKeypad":
34
+ return {
35
+ ...state,
36
+ // Default `extraKeys` to the empty array.
37
+ extraKeys: [],
38
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'configuration' does not exist on type '{ type: string; }'.
39
+ ...action.configuration,
40
+ };
41
+
42
+ case "PressKey":
43
+ // @ts-expect-error [FEI-5003] - TS2339 - Property 'key' does not exist on type '{ type: string; }'.
44
+ const keyConfig = KeyConfigs[action.key];
45
+ // NOTE(charlie): Our keypad system operates by triggering key
46
+ // presses with key IDs in a dumb manner, such that the keys
47
+ // don't know what they can do--instead, the store is
48
+ // responsible for interpreting key presses and triggering the
49
+ // right actions when they occur. Hence, we figure off a
50
+ // dismissal here rather than dispatching a dismiss action in
51
+ // the first place.
52
+ if (keyConfig.id === Keys.DISMISS) {
53
+ return keypadReducer(state, {type: "DismissKeypad"});
54
+ }
55
+ return state;
56
+
57
+ default:
58
+ return state;
59
+ }
60
+ };
61
+
62
+ export default keypadReducer;