@khanacademy/math-input 2.0.0 → 4.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 (264) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/components/input/__tests__/test-math-wrapper.d.ts +1 -1
  3. package/dist/components/input/__tests__/test-math-wrapper.js.flow +1 -1
  4. package/dist/components/input/key-handlers/handle-arrow.d.ts +3 -0
  5. package/dist/components/input/key-handlers/handle-arrow.js.flow +12 -0
  6. package/dist/components/input/key-handlers/handle-backspace.d.ts +7 -0
  7. package/dist/components/input/key-handlers/handle-backspace.js.flow +14 -0
  8. package/dist/components/input/key-handlers/handle-exponent.d.ts +3 -0
  9. package/dist/components/input/key-handlers/handle-exponent.js.flow +12 -0
  10. package/dist/components/input/key-handlers/handle-jump-out.d.ts +7 -0
  11. package/dist/components/input/key-handlers/handle-jump-out.js.flow +14 -0
  12. package/dist/components/input/math-input.d.ts +1 -1
  13. package/dist/components/input/math-input.js.flow +1 -1
  14. package/dist/components/input/math-wrapper.d.ts +7 -78
  15. package/dist/components/input/math-wrapper.js.flow +16 -78
  16. package/dist/components/input/mathquill-helpers.d.ts +46 -0
  17. package/dist/components/input/mathquill-helpers.js.flow +56 -0
  18. package/dist/components/input/mathquill-instance.d.ts +3 -0
  19. package/dist/components/input/mathquill-instance.js.flow +9 -0
  20. package/dist/components/input/mathquill-types.d.ts +25 -0
  21. package/dist/components/input/mathquill-types.js.flow +34 -0
  22. package/dist/components/key-translator.d.ts +4 -0
  23. package/dist/components/key-translator.js.flow +10 -0
  24. package/dist/components/keypad/button-assets.d.ts +2 -2
  25. package/dist/components/keypad/button-assets.js.flow +2 -2
  26. package/dist/components/keypad/button.d.ts +1 -2
  27. package/dist/components/keypad/button.js.flow +1 -1
  28. package/dist/components/keypad/{pre-algebra-page.d.ts → geometry-page/index.d.ts} +1 -1
  29. package/dist/components/keypad/{pre-algebra-page.js.flow → geometry-page/index.js.flow} +1 -1
  30. package/dist/components/keypad/index.d.ts +5 -4
  31. package/dist/components/keypad/index.js.flow +9 -4
  32. package/dist/components/keypad/keypad-page-items.d.ts +9 -3
  33. package/dist/components/keypad/keypad-page-items.js.flow +9 -3
  34. package/dist/components/keypad/{numeric-input-page.d.ts → numbers-page/index.d.ts} +3 -2
  35. package/dist/components/keypad/numbers-page/index.js.flow +17 -0
  36. package/dist/components/keypad/numbers-page/types.d.ts +4 -0
  37. package/dist/components/keypad/numbers-page/types.js.flow +10 -0
  38. package/dist/components/keypad/operators-page/advanced-relations-buttons.d.ts +7 -0
  39. package/dist/components/keypad/{numeric-input-page.js.flow → operators-page/advanced-relations-buttons.js.flow} +3 -4
  40. package/dist/components/keypad/operators-page/basic-relations-buttons.d.ts +7 -0
  41. package/dist/components/keypad/{trigonometry-page.js.flow → operators-page/basic-relations-buttons.js.flow} +3 -6
  42. package/dist/components/keypad/operators-page/index.d.ts +9 -0
  43. package/dist/components/keypad/operators-page/index.js.flow +17 -0
  44. package/dist/components/keypad/operators-page/logarithms-buttons.d.ts +7 -0
  45. package/dist/components/keypad/operators-page/logarithms-buttons.js.flow +12 -0
  46. package/dist/components/keypad/operators-page/pre-algebra-buttons.d.ts +7 -0
  47. package/dist/components/keypad/operators-page/pre-algebra-buttons.js.flow +12 -0
  48. package/dist/components/keypad/operators-page/types.d.ts +6 -0
  49. package/dist/components/keypad/operators-page/types.js.flow +12 -0
  50. package/dist/components/{compute-layout-parameters.d.ts → keypad-legacy/compute-layout-parameters.d.ts} +1 -1
  51. package/dist/components/{compute-layout-parameters.js.flow → keypad-legacy/compute-layout-parameters.js.flow} +1 -1
  52. package/dist/components/{echo-manager.d.ts → keypad-legacy/echo-manager.d.ts} +2 -11
  53. package/dist/components/{echo-manager.js.flow → keypad-legacy/echo-manager.js.flow} +2 -11
  54. package/dist/components/{expression-keypad.d.ts → keypad-legacy/expression-keypad.d.ts} +3 -4
  55. package/dist/components/{expression-keypad.js.flow → keypad-legacy/expression-keypad.js.flow} +3 -4
  56. package/dist/components/{fraction-keypad.d.ts → keypad-legacy/fraction-keypad.d.ts} +2 -2
  57. package/dist/components/{fraction-keypad.js.flow → keypad-legacy/fraction-keypad.js.flow} +2 -2
  58. package/dist/components/{gesture-manager.d.ts → keypad-legacy/gesture-manager.d.ts} +22 -10
  59. package/dist/components/{gesture-manager.js.flow → keypad-legacy/gesture-manager.js.flow} +28 -13
  60. package/dist/components/{gesture-state-machine.d.ts → keypad-legacy/gesture-state-machine.d.ts} +9 -9
  61. package/dist/components/{gesture-state-machine.js.flow → keypad-legacy/gesture-state-machine.js.flow} +10 -10
  62. package/dist/components/{icon.d.ts → keypad-legacy/icon.d.ts} +1 -1
  63. package/dist/components/{icon.js.flow → keypad-legacy/icon.js.flow} +1 -1
  64. package/dist/components/{keypad-button.d.ts → keypad-legacy/keypad-button.d.ts} +6 -6
  65. package/dist/components/{keypad-button.js.flow → keypad-legacy/keypad-button.js.flow} +7 -7
  66. package/dist/components/{keypad-container.d.ts → keypad-legacy/keypad-container.d.ts} +2 -2
  67. package/dist/components/{keypad-container.js.flow → keypad-legacy/keypad-container.js.flow} +3 -3
  68. package/dist/components/{keypad.d.ts → keypad-legacy/keypad.d.ts} +3 -3
  69. package/dist/components/{keypad.js.flow → keypad-legacy/keypad.js.flow} +3 -3
  70. package/dist/components/{multi-symbol-grid.d.ts → keypad-legacy/multi-symbol-grid.d.ts} +1 -1
  71. package/dist/components/{multi-symbol-grid.js.flow → keypad-legacy/multi-symbol-grid.js.flow} +1 -1
  72. package/dist/components/{multi-symbol-popover.d.ts → keypad-legacy/multi-symbol-popover.d.ts} +1 -1
  73. package/dist/components/{multi-symbol-popover.js.flow → keypad-legacy/multi-symbol-popover.js.flow} +1 -1
  74. package/dist/components/{node-manager.d.ts → keypad-legacy/node-manager.d.ts} +3 -4
  75. package/dist/components/{node-manager.js.flow → keypad-legacy/node-manager.js.flow} +3 -5
  76. package/dist/components/{popover-manager.d.ts → keypad-legacy/popover-manager.d.ts} +1 -1
  77. package/dist/components/{popover-manager.js.flow → keypad-legacy/popover-manager.js.flow} +1 -1
  78. package/dist/components/{popover-state-machine.d.ts → keypad-legacy/popover-state-machine.d.ts} +1 -1
  79. package/dist/components/{popover-state-machine.js.flow → keypad-legacy/popover-state-machine.js.flow} +1 -1
  80. package/dist/components/{provided-keypad.d.ts → keypad-legacy/provided-keypad.d.ts} +1 -1
  81. package/dist/components/{provided-keypad.js.flow → keypad-legacy/provided-keypad.js.flow} +1 -1
  82. package/dist/{store → components/keypad-legacy/store}/actions.d.ts +6 -17
  83. package/dist/{store → components/keypad-legacy/store}/actions.js.flow +7 -22
  84. package/dist/{store → components/keypad-legacy/store}/index.d.ts +0 -1
  85. package/dist/{store → components/keypad-legacy/store}/index.js.flow +0 -1
  86. package/dist/components/keypad-legacy/store/shared.d.ts +7 -0
  87. package/dist/components/keypad-legacy/store/shared.js.flow +14 -0
  88. package/dist/{store → components/keypad-legacy/store}/types.d.ts +5 -15
  89. package/dist/{store → components/keypad-legacy/store}/types.js.flow +5 -15
  90. package/dist/components/keypad-legacy/touchable-keypad-button.d.ts +37 -0
  91. package/dist/components/keypad-legacy/touchable-keypad-button.js.flow +59 -0
  92. package/dist/components/{two-page-keypad.d.ts → keypad-legacy/two-page-keypad.d.ts} +0 -1
  93. package/dist/components/{two-page-keypad.js.flow → keypad-legacy/two-page-keypad.js.flow} +0 -1
  94. package/dist/data/key-configs.d.ts +4 -5
  95. package/dist/data/key-configs.js.flow +3 -6
  96. package/dist/data/keys.d.ts +2 -56
  97. package/dist/data/keys.js.flow +116 -57
  98. package/dist/enums.d.ts +2 -9
  99. package/dist/enums.js.flow +2 -11
  100. package/dist/es/index.js +6393 -5116
  101. package/dist/es/index.js.map +1 -1
  102. package/dist/index.d.ts +5 -3
  103. package/dist/index.js +6868 -5330
  104. package/dist/index.js.flow +6 -3
  105. package/dist/index.js.map +1 -1
  106. package/dist/strings.js +26 -10
  107. package/dist/types.d.ts +19 -17
  108. package/dist/types.js.flow +28 -23
  109. package/package.json +1 -1
  110. package/src/components/input/__tests__/context-tracking.test.ts +43 -44
  111. package/src/components/input/__tests__/mathquill.test.ts +133 -135
  112. package/src/components/input/key-handlers/handle-arrow.ts +70 -0
  113. package/src/components/input/key-handlers/handle-backspace.ts +275 -0
  114. package/src/components/input/key-handlers/handle-exponent.ts +52 -0
  115. package/src/components/input/key-handlers/handle-jump-out.ts +103 -0
  116. package/src/components/input/math-input.tsx +12 -13
  117. package/src/components/input/math-wrapper.ts +88 -837
  118. package/src/components/input/mathquill-helpers.ts +268 -0
  119. package/src/components/input/mathquill-instance.ts +5 -0
  120. package/src/components/input/mathquill-types.ts +55 -0
  121. package/src/components/key-translator.ts +209 -0
  122. package/src/components/keypad/button-assets.tsx +452 -116
  123. package/src/components/keypad/button.stories.tsx +61 -13
  124. package/src/components/keypad/button.tsx +1 -1
  125. package/src/components/keypad/{trigonometry-page.tsx → geometry-page/index.tsx} +4 -5
  126. package/src/components/keypad/index.tsx +19 -14
  127. package/src/components/keypad/keypad-mathquill.stories.tsx +69 -0
  128. package/src/components/keypad/keypad-page-items.tsx +36 -22
  129. package/src/components/keypad/keypad-pages.stories.tsx +5 -5
  130. package/src/components/keypad/keypad.stories.tsx +75 -17
  131. package/src/components/keypad/{numeric-input-page.tsx → numbers-page/index.tsx} +47 -11
  132. package/src/components/keypad/numbers-page/types.ts +4 -0
  133. package/src/components/keypad/operators-page/advanced-relations-buttons.tsx +32 -0
  134. package/src/components/keypad/operators-page/basic-relations-buttons.tsx +32 -0
  135. package/src/components/keypad/{pre-algebra-page.tsx → operators-page/index.tsx} +26 -30
  136. package/src/components/keypad/operators-page/logarithms-buttons.tsx +32 -0
  137. package/src/components/keypad/operators-page/pre-algebra-buttons.tsx +36 -0
  138. package/src/components/keypad/operators-page/types.ts +6 -0
  139. package/src/components/{__tests__ → keypad-legacy/__tests__}/two-page-keypad.test.tsx +0 -2
  140. package/src/components/{compute-layout-parameters.ts → keypad-legacy/compute-layout-parameters.ts} +2 -3
  141. package/src/components/{corner-decal.tsx → keypad-legacy/corner-decal.tsx} +2 -3
  142. package/src/components/{echo-manager.tsx → keypad-legacy/echo-manager.tsx} +8 -21
  143. package/src/components/{empty-keypad-button.tsx → keypad-legacy/empty-keypad-button.tsx} +8 -6
  144. package/src/components/{expression-keypad.tsx → keypad-legacy/expression-keypad.tsx} +8 -17
  145. package/src/components/{fraction-keypad.tsx → keypad-legacy/fraction-keypad.tsx} +6 -6
  146. package/src/components/{gesture-manager.ts → keypad-legacy/gesture-manager.ts} +34 -11
  147. package/src/components/{gesture-state-machine.ts → keypad-legacy/gesture-state-machine.ts} +14 -14
  148. package/src/components/{icon.tsx → keypad-legacy/icon.tsx} +3 -3
  149. package/src/components/{keypad-button.tsx → keypad-legacy/keypad-button.tsx} +26 -26
  150. package/src/components/{keypad-container.tsx → keypad-legacy/keypad-container.tsx} +6 -6
  151. package/src/components/{keypad.tsx → keypad-legacy/keypad.tsx} +5 -5
  152. package/src/components/{many-keypad-button.tsx → keypad-legacy/many-keypad-button.tsx} +13 -6
  153. package/src/components/{math-icon.tsx → keypad-legacy/math-icon.tsx} +2 -2
  154. package/src/components/{multi-symbol-grid.tsx → keypad-legacy/multi-symbol-grid.tsx} +4 -4
  155. package/src/components/{multi-symbol-popover.tsx → keypad-legacy/multi-symbol-popover.tsx} +3 -4
  156. package/src/components/{navigation-pad.tsx → keypad-legacy/navigation-pad.tsx} +5 -5
  157. package/src/components/{node-manager.ts → keypad-legacy/node-manager.ts} +2 -10
  158. package/src/components/{popover-manager.tsx → keypad-legacy/popover-manager.tsx} +2 -2
  159. package/src/components/{popover-state-machine.ts → keypad-legacy/popover-state-machine.ts} +1 -1
  160. package/src/components/{provided-keypad.tsx → keypad-legacy/provided-keypad.tsx} +4 -5
  161. package/src/{store → components/keypad-legacy/store}/actions.ts +7 -36
  162. package/src/{store → components/keypad-legacy/store}/echo-reducer.ts +3 -7
  163. package/src/{store → components/keypad-legacy/store}/index.ts +7 -20
  164. package/src/{store → components/keypad-legacy/store}/input-reducer.ts +4 -5
  165. package/src/{store → components/keypad-legacy/store}/keypad-reducer.ts +3 -4
  166. package/src/{store → components/keypad-legacy/store}/layout-reducer.ts +3 -3
  167. package/src/{store → components/keypad-legacy/store}/shared.ts +3 -3
  168. package/src/{store → components/keypad-legacy/store}/types.ts +15 -19
  169. package/src/components/{styles.ts → keypad-legacy/styles.ts} +1 -1
  170. package/src/components/{text-icon.tsx → keypad-legacy/text-icon.tsx} +2 -2
  171. package/src/components/{touchable-keypad-button.tsx → keypad-legacy/touchable-keypad-button.tsx} +35 -21
  172. package/src/components/{two-page-keypad.tsx → keypad-legacy/two-page-keypad.tsx} +5 -6
  173. package/src/components/tabbar/icons.tsx +0 -2
  174. package/src/data/key-configs.ts +751 -309
  175. package/src/data/keys.ts +118 -70
  176. package/src/enums.ts +10 -9
  177. package/src/index.ts +6 -3
  178. package/src/math-input.stories.tsx +3 -3
  179. package/src/types.ts +21 -16
  180. package/tsconfig-build.tsbuildinfo +1 -1
  181. package/dist/components/keypad/trigonometry-page.d.ts +0 -8
  182. package/dist/components/touchable-keypad-button.d.ts +0 -30
  183. package/dist/components/touchable-keypad-button.js.flow +0 -35
  184. package/dist/components/velocity-tracker.d.ts +0 -48
  185. package/dist/components/velocity-tracker.js.flow +0 -54
  186. package/dist/store/pager-reducer.d.ts +0 -4
  187. package/dist/store/pager-reducer.js.flow +0 -13
  188. package/dist/store/shared.d.ts +0 -7
  189. package/dist/store/shared.js.flow +0 -14
  190. package/src/components/velocity-tracker.ts +0 -86
  191. package/src/store/pager-reducer.ts +0 -125
  192. /package/dist/components/{corner-decal.d.ts → keypad-legacy/corner-decal.d.ts} +0 -0
  193. /package/dist/components/{corner-decal.js.flow → keypad-legacy/corner-decal.js.flow} +0 -0
  194. /package/dist/components/{empty-keypad-button.d.ts → keypad-legacy/empty-keypad-button.d.ts} +0 -0
  195. /package/dist/components/{empty-keypad-button.js.flow → keypad-legacy/empty-keypad-button.js.flow} +0 -0
  196. /package/dist/components/{many-keypad-button.d.ts → keypad-legacy/many-keypad-button.d.ts} +0 -0
  197. /package/dist/components/{many-keypad-button.js.flow → keypad-legacy/many-keypad-button.js.flow} +0 -0
  198. /package/dist/components/{math-icon.d.ts → keypad-legacy/math-icon.d.ts} +0 -0
  199. /package/dist/components/{math-icon.js.flow → keypad-legacy/math-icon.js.flow} +0 -0
  200. /package/dist/components/{navigation-pad.d.ts → keypad-legacy/navigation-pad.d.ts} +0 -0
  201. /package/dist/components/{navigation-pad.js.flow → keypad-legacy/navigation-pad.js.flow} +0 -0
  202. /package/dist/{store → components/keypad-legacy/store}/echo-reducer.d.ts +0 -0
  203. /package/dist/{store → components/keypad-legacy/store}/echo-reducer.js.flow +0 -0
  204. /package/dist/{store → components/keypad-legacy/store}/input-reducer.d.ts +0 -0
  205. /package/dist/{store → components/keypad-legacy/store}/input-reducer.js.flow +0 -0
  206. /package/dist/{store → components/keypad-legacy/store}/keypad-reducer.d.ts +0 -0
  207. /package/dist/{store → components/keypad-legacy/store}/keypad-reducer.js.flow +0 -0
  208. /package/dist/{store → components/keypad-legacy/store}/layout-reducer.d.ts +0 -0
  209. /package/dist/{store → components/keypad-legacy/store}/layout-reducer.js.flow +0 -0
  210. /package/dist/components/{styles.d.ts → keypad-legacy/styles.d.ts} +0 -0
  211. /package/dist/components/{styles.js.flow → keypad-legacy/styles.js.flow} +0 -0
  212. /package/dist/components/{svg-icon.d.ts → keypad-legacy/svg-icon.d.ts} +0 -0
  213. /package/dist/components/{svg-icon.js.flow → keypad-legacy/svg-icon.js.flow} +0 -0
  214. /package/dist/components/{text-icon.d.ts → keypad-legacy/text-icon.d.ts} +0 -0
  215. /package/dist/components/{text-icon.js.flow → keypad-legacy/text-icon.js.flow} +0 -0
  216. /package/dist/components/{z-indexes.d.ts → keypad-legacy/z-indexes.d.ts} +0 -0
  217. /package/dist/components/{z-indexes.js.flow → keypad-legacy/z-indexes.js.flow} +0 -0
  218. /package/src/components/{__tests__ → keypad-legacy/__tests__}/gesture-state-machine.test.ts +0 -0
  219. /package/src/components/{__tests__ → keypad-legacy/__tests__}/node-manager.test.ts +0 -0
  220. /package/src/components/{iconography → keypad-legacy/iconography}/arrow.js +0 -0
  221. /package/src/components/{iconography → keypad-legacy/iconography}/backspace.js +0 -0
  222. /package/src/components/{iconography → keypad-legacy/iconography}/cdot.js +0 -0
  223. /package/src/components/{iconography → keypad-legacy/iconography}/cos.js +0 -0
  224. /package/src/components/{iconography → keypad-legacy/iconography}/cube-root.js +0 -0
  225. /package/src/components/{iconography → keypad-legacy/iconography}/dismiss.js +0 -0
  226. /package/src/components/{iconography → keypad-legacy/iconography}/divide.js +0 -0
  227. /package/src/components/{iconography → keypad-legacy/iconography}/down.js +0 -0
  228. /package/src/components/{iconography → keypad-legacy/iconography}/equal.js +0 -0
  229. /package/src/components/{iconography → keypad-legacy/iconography}/exp-2.js +0 -0
  230. /package/src/components/{iconography → keypad-legacy/iconography}/exp-3.js +0 -0
  231. /package/src/components/{iconography → keypad-legacy/iconography}/exp.js +0 -0
  232. /package/src/components/{iconography → keypad-legacy/iconography}/frac.js +0 -0
  233. /package/src/components/{iconography → keypad-legacy/iconography}/geq.js +0 -0
  234. /package/src/components/{iconography → keypad-legacy/iconography}/gt.js +0 -0
  235. /package/src/components/{iconography → keypad-legacy/iconography}/index.js +0 -0
  236. /package/src/components/{iconography → keypad-legacy/iconography}/jump-into-numerator.js +0 -0
  237. /package/src/components/{iconography → keypad-legacy/iconography}/jump-out-base.js +0 -0
  238. /package/src/components/{iconography → keypad-legacy/iconography}/jump-out-denominator.js +0 -0
  239. /package/src/components/{iconography → keypad-legacy/iconography}/jump-out-exponent.js +0 -0
  240. /package/src/components/{iconography → keypad-legacy/iconography}/jump-out-numerator.js +0 -0
  241. /package/src/components/{iconography → keypad-legacy/iconography}/jump-out-parentheses.js +0 -0
  242. /package/src/components/{iconography → keypad-legacy/iconography}/left-paren.js +0 -0
  243. /package/src/components/{iconography → keypad-legacy/iconography}/left.js +0 -0
  244. /package/src/components/{iconography → keypad-legacy/iconography}/leq.js +0 -0
  245. /package/src/components/{iconography → keypad-legacy/iconography}/ln.js +0 -0
  246. /package/src/components/{iconography → keypad-legacy/iconography}/log-n.js +0 -0
  247. /package/src/components/{iconography → keypad-legacy/iconography}/log.js +0 -0
  248. /package/src/components/{iconography → keypad-legacy/iconography}/lt.js +0 -0
  249. /package/src/components/{iconography → keypad-legacy/iconography}/minus.js +0 -0
  250. /package/src/components/{iconography → keypad-legacy/iconography}/neq.js +0 -0
  251. /package/src/components/{iconography → keypad-legacy/iconography}/parens.js +0 -0
  252. /package/src/components/{iconography → keypad-legacy/iconography}/percent.js +0 -0
  253. /package/src/components/{iconography → keypad-legacy/iconography}/period.js +0 -0
  254. /package/src/components/{iconography → keypad-legacy/iconography}/plus.js +0 -0
  255. /package/src/components/{iconography → keypad-legacy/iconography}/radical.js +0 -0
  256. /package/src/components/{iconography → keypad-legacy/iconography}/right-paren.js +0 -0
  257. /package/src/components/{iconography → keypad-legacy/iconography}/right.js +0 -0
  258. /package/src/components/{iconography → keypad-legacy/iconography}/sin.js +0 -0
  259. /package/src/components/{iconography → keypad-legacy/iconography}/sqrt.js +0 -0
  260. /package/src/components/{iconography → keypad-legacy/iconography}/tan.js +0 -0
  261. /package/src/components/{iconography → keypad-legacy/iconography}/times.js +0 -0
  262. /package/src/components/{iconography → keypad-legacy/iconography}/up.js +0 -0
  263. /package/src/components/{svg-icon.tsx → keypad-legacy/svg-icon.tsx} +0 -0
  264. /package/src/components/{z-indexes.ts → keypad-legacy/z-indexes.ts} +0 -0
@@ -1,133 +1,88 @@
1
- /**
2
- * This file contains a wrapper around MathQuill so that we can provide a
3
- * more regular interface for the functionality we need while insulating us
4
- * from MathQuill changes.
5
- */
1
+ // Notes about MathQuill
2
+ //
3
+ // MathQuill's stores its layout as nested linked lists. Each node in the
4
+ // list has MQ.L '-1' and MQ.R '1' properties that define links to
5
+ // the left and right nodes respectively. They also have
6
+ //
7
+ // ctrlSeq: contains the latex code snippet that defines that node.
8
+ // jQ: jQuery object for the DOM node(s) for this MathQuill node.
9
+ // ends: pointers to the nodes at the ends of the container.
10
+ // parent: parent node.
11
+ // blocks: an array containing one or more nodes that make up the node.
12
+ // sub?: subscript node if there is one as is the case in log_n
13
+ //
14
+ // All of the code below is super fragile. Please be especially careful
15
+ // when upgrading MathQuill.
6
16
 
7
17
  import $ from "jquery";
8
- import MathQuill from "mathquill";
9
-
10
- import Keys from "../../data/keys";
11
- import {DecimalSeparator} from "../../enums";
12
- import {decimalSeparator} from "../../utils";
13
-
14
- import {CursorContext} from "./cursor-contexts";
15
18
 
16
- // Keeping `window` in place for test suite and GitHub Pages.
17
- // If it does not exist, fall back to CommonJS require. - jsatk
18
-
19
- const decimalSymbol = decimalSeparator === DecimalSeparator.COMMA ? "," : ".";
20
-
21
- enum ActionType {
22
- WRITE = "write",
23
- CMD = "cmd",
24
- KEYSTROKE = "keystroke",
25
- MQ_END = 0,
19
+ import Key from "../../data/keys";
20
+ import {Cursor} from "../../types";
21
+ import keyTranslator from "../key-translator";
22
+
23
+ import handleArrow from "./key-handlers/handle-arrow";
24
+ import handleBackspace from "./key-handlers/handle-backspace";
25
+ import handleExponent from "./key-handlers/handle-exponent";
26
+ import handleJumpOut from "./key-handlers/handle-jump-out";
27
+ import {
28
+ getCursor,
29
+ contextForCursor,
30
+ maybeFindCommand,
31
+ } from "./mathquill-helpers";
32
+ import MQ from "./mathquill-instance";
33
+ import {
34
+ MathFieldInterface,
35
+ MathFieldCursor,
36
+ MathQuillUpdaterCallback,
37
+ } from "./mathquill-types";
38
+
39
+ function buildNormalFunctionCallback(command: string) {
40
+ return function (mathField: MathFieldInterface) {
41
+ mathField.write(`\\${command}\\left(\\right)`);
42
+ mathField.keystroke("Left");
43
+ };
26
44
  }
27
45
 
28
- // A mapping from keys that can be pressed on a keypad to the way in which
29
- // MathQuill should modify its input in response to that key-press. Any keys
30
- // that do not provide explicit actions (like the numeral keys) will merely
31
- // write their contents to MathQuill.
32
- const KeyActions: {[K in Keys]?: {str: string; fn: ActionType}} = {
33
- [Keys.PLUS]: {str: "+", fn: ActionType.WRITE},
34
- [Keys.MINUS]: {str: "-", fn: ActionType.WRITE},
35
- [Keys.NEGATIVE]: {str: "-", fn: ActionType.WRITE},
36
- [Keys.TIMES]: {str: "\\times", fn: ActionType.WRITE},
37
- [Keys.DIVIDE]: {str: "\\div", fn: ActionType.WRITE},
38
- [Keys.DECIMAL]: {
39
- str: decimalSymbol,
40
- fn: ActionType.WRITE,
46
+ const customKeyTranslator: Record<Key, MathQuillUpdaterCallback> = {
47
+ ...keyTranslator,
48
+ // note(Matthew): in all likelihood, this should be moved
49
+ // to the shared key2MathQuill translator. During this refactor
50
+ // I tried to keep logic the same while deduplicating code.
51
+ // Perseus' Expression MathInput treats this stuff differently
52
+ // (or doesn't do anything with them at all), so I kept it that way
53
+ BACKSPACE: handleBackspace,
54
+ EXP: handleExponent,
55
+ EXP_2: handleExponent,
56
+ EXP_3: handleExponent,
57
+ FRAC: (mathQuill) => {
58
+ mathQuill.cmd("\\frac");
41
59
  },
42
- [Keys.EQUAL]: {str: "=", fn: ActionType.WRITE},
43
- [Keys.NEQ]: {str: "\\neq", fn: ActionType.WRITE},
44
- [Keys.CDOT]: {str: "\\cdot", fn: ActionType.WRITE},
45
- [Keys.PERCENT]: {str: "%", fn: ActionType.WRITE},
46
- [Keys.LEFT_PAREN]: {str: "(", fn: ActionType.CMD},
47
- [Keys.RIGHT_PAREN]: {str: ")", fn: ActionType.CMD},
48
- [Keys.SQRT]: {str: "sqrt", fn: ActionType.CMD},
49
- [Keys.PI]: {str: "pi", fn: ActionType.CMD},
50
- [Keys.THETA]: {str: "theta", fn: ActionType.CMD},
51
- [Keys.RADICAL]: {str: "nthroot", fn: ActionType.CMD},
52
- [Keys.LT]: {str: "<", fn: ActionType.WRITE},
53
- [Keys.LEQ]: {str: "\\leq", fn: ActionType.WRITE},
54
- [Keys.GT]: {str: ">", fn: ActionType.WRITE},
55
- [Keys.GEQ]: {str: "\\geq", fn: ActionType.WRITE},
56
- [Keys.UP]: {str: "Up", fn: ActionType.KEYSTROKE},
57
- [Keys.DOWN]: {str: "Down", fn: ActionType.KEYSTROKE},
58
- // The `FRAC_EXCLUSIVE` variant is handled manually, since we may need to do
59
- // some additional navigation depending on the cursor position.
60
- [Keys.FRAC_INCLUSIVE]: {str: "/", fn: ActionType.CMD},
61
- };
62
-
63
- const NormalCommands = {
64
- [Keys.LOG]: "log",
65
- [Keys.LN]: "ln",
66
- [Keys.SIN]: "sin",
67
- [Keys.COS]: "cos",
68
- [Keys.TAN]: "tan",
69
- };
70
-
71
- const ArithmeticOperators = ["+", "-", "\\cdot", "\\times", "\\div"];
72
- const EqualityOperators = ["=", "\\neq", "<", "\\leq", ">", "\\geq"];
73
-
74
- const Numerals = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
75
- const GreekLetters = ["\\theta", "\\pi"];
76
- const Letters = [
77
- "A",
78
- "B",
79
- "C",
80
- "D",
81
- "E",
82
- "F",
83
- "G",
84
- "H",
85
- "I",
86
- "J",
87
- "K",
88
- "L",
89
- "M",
90
- "N",
91
- "O",
92
- "P",
93
- "Q",
94
- "R",
95
- "S",
96
- "T",
97
- "U",
98
- "V",
99
- "W",
100
- "X",
101
- "Y",
102
- "Z",
103
- ];
104
-
105
- // We only consider numerals, variables, and Greek Letters to be proper
106
- // leaf nodes.
107
- const ValidLeaves = [
108
- ...Numerals,
109
- ...GreekLetters,
110
- ...Letters.map((letter) => letter.toLowerCase()),
111
- ...Letters.map((letter) => letter.toUpperCase()),
112
- ];
113
-
114
- const KeysForJumpContext = {
115
- [CursorContext.IN_PARENS]: Keys.JUMP_OUT_PARENTHESES,
116
- [CursorContext.IN_SUPER_SCRIPT]: Keys.JUMP_OUT_EXPONENT,
117
- [CursorContext.IN_SUB_SCRIPT]: Keys.JUMP_OUT_BASE,
118
- [CursorContext.BEFORE_FRACTION]: Keys.JUMP_INTO_NUMERATOR,
119
- [CursorContext.IN_NUMERATOR]: Keys.JUMP_OUT_NUMERATOR,
120
- [CursorContext.IN_DENOMINATOR]: Keys.JUMP_OUT_DENOMINATOR,
60
+ JUMP_OUT_PARENTHESES: handleJumpOut,
61
+ JUMP_OUT_EXPONENT: handleJumpOut,
62
+ JUMP_OUT_BASE: handleJumpOut,
63
+ JUMP_INTO_NUMERATOR: handleJumpOut,
64
+ JUMP_OUT_NUMERATOR: handleJumpOut,
65
+ JUMP_OUT_DENOMINATOR: handleJumpOut,
66
+ LEFT: handleArrow,
67
+ RIGHT: handleArrow,
68
+ LOG: buildNormalFunctionCallback("log"),
69
+ LN: buildNormalFunctionCallback("ln"),
70
+ SIN: buildNormalFunctionCallback("sin"),
71
+ COS: buildNormalFunctionCallback("cos"),
72
+ TAN: buildNormalFunctionCallback("tan"),
121
73
  };
122
74
 
75
+ /**
76
+ * This file contains a wrapper around MathQuill so that we can provide a
77
+ * more regular interface for the functionality we need while insulating us
78
+ * from MathQuill changes.
79
+ */
123
80
  class MathWrapper {
124
- MQ: any; // MathQuill interface
125
- mathField: any; // MathQuill input
81
+ mathField: MathFieldInterface; // MathQuill input
126
82
  callbacks: any;
127
83
 
128
84
  constructor(element, options = {}, callbacks = {}) {
129
- this.MQ = MathQuill.getInterface(2);
130
- this.mathField = this.MQ.MathField(element, {
85
+ this.mathField = MQ.MathField(element, {
131
86
  // use a span instead of a textarea so that we don't bring up the
132
87
  // native keyboard on mobile when selecting the input
133
88
  substituteTextarea: function () {
@@ -156,73 +111,18 @@ class MathWrapper {
156
111
  controller.blurred = true;
157
112
  }
158
113
 
159
- _writeNormalFunction(name: string) {
160
- this.mathField.write(`\\${name}\\left(\\right)`);
161
- this.mathField.keystroke("Left");
162
- }
163
-
164
114
  /**
165
115
  * Handle a key press and return the resulting cursor state.
166
116
  *
167
117
  * @param {Key} key - an enum representing the key that was pressed
168
118
  * @returns {object} a cursor object, consisting of a cursor context
169
119
  */
170
- pressKey(key: string) {
171
- const cursor = this.mathField.__controller.cursor;
172
-
173
- if (key in KeyActions) {
174
- const {str, fn} = KeyActions[key];
120
+ pressKey(key: Key): Cursor {
121
+ const cursor = this.getCursor();
122
+ const translator = customKeyTranslator[key];
175
123
 
176
- if (str && fn) {
177
- this.mathField[fn](str);
178
- }
179
- } else if (Object.keys(NormalCommands).includes(key)) {
180
- this._writeNormalFunction(NormalCommands[key]);
181
- } else if (key === Keys.FRAC_EXCLUSIVE) {
182
- // If there's nothing to the left of the cursor, then we want to
183
- // leave the cursor to the left of the fraction after creating it.
184
- const shouldNavigateLeft = cursor[this.MQ.L] === ActionType.MQ_END;
185
- this.mathField.cmd("\\frac");
186
- if (shouldNavigateLeft) {
187
- this.mathField.keystroke("Left");
188
- }
189
- } else if (key === Keys.FRAC) {
190
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
191
- const shouldNavigateLeft = cursor[this.MQ.L] === ActionType.MQ_END;
192
- this.mathField.cmd("\\frac");
193
- } else if (key === Keys.LOG_N) {
194
- this.mathField.write("log_{ }\\left(\\right)");
195
- this.mathField.keystroke("Left"); // into parentheses
196
- this.mathField.keystroke("Left"); // out of parentheses
197
- this.mathField.keystroke("Left"); // into index
198
- } else if (key === Keys.CUBE_ROOT) {
199
- this.mathField.write("\\sqrt[3]{}");
200
- this.mathField.keystroke("Left"); // under the root
201
- } else if (
202
- key === Keys.EXP ||
203
- key === Keys.EXP_2 ||
204
- key === Keys.EXP_3
205
- ) {
206
- this._handleExponent(cursor, key);
207
- } else if (
208
- key === Keys.JUMP_OUT_PARENTHESES ||
209
- key === Keys.JUMP_OUT_EXPONENT ||
210
- key === Keys.JUMP_OUT_BASE ||
211
- key === Keys.JUMP_INTO_NUMERATOR ||
212
- key === Keys.JUMP_OUT_NUMERATOR ||
213
- key === Keys.JUMP_OUT_DENOMINATOR
214
- ) {
215
- this._handleJumpOut(cursor, key);
216
- } else if (key === Keys.BACKSPACE) {
217
- this._handleBackspace(cursor);
218
- } else if (key === Keys.LEFT) {
219
- this._handleLeftArrow(cursor);
220
- } else if (key === Keys.RIGHT) {
221
- this._handleRightArrow(cursor);
222
- } else if (/^[a-zA-Z]$/.test(key)) {
223
- this.mathField[ActionType.WRITE](key);
224
- } else if (/^NUM_\d/.test(key)) {
225
- this.mathField[ActionType.WRITE](key[4]);
124
+ if (translator) {
125
+ translator(this.mathField, key);
226
126
  }
227
127
 
228
128
  if (!cursor.selection) {
@@ -273,7 +173,7 @@ class MathWrapper {
273
173
  // Unless that would leave us mid-command, in which case, we
274
174
  // need to adjust and place the cursor inside the parens
275
175
  // following the command.
276
- const command = this._maybeFindCommand(cursor[this.MQ.L]);
176
+ const command = maybeFindCommand(cursor[MQ.L]);
277
177
  if (command && command.endNode) {
278
178
  // NOTE(charlie): endNode should definitely be \left(.
279
179
  cursor.insLeftOf(command.endNode);
@@ -289,8 +189,16 @@ class MathWrapper {
289
189
  }
290
190
  }
291
191
 
192
+ // note(Matthew): extracted this logic to share it elsewhere,
193
+ // but it's part of the public MathWrapper API
292
194
  getCursor() {
293
- return this.mathField.__controller.cursor;
195
+ return getCursor(this.mathField);
196
+ }
197
+
198
+ // note(Matthew): extracted this logic to keep this file focused,
199
+ // but it's part of the public MathWrapper API
200
+ contextForCursor(cursor: MathFieldCursor) {
201
+ return contextForCursor(cursor);
294
202
  }
295
203
 
296
204
  getSelection() {
@@ -309,663 +217,6 @@ class MathWrapper {
309
217
  const cursor = this.getCursor();
310
218
  return cursor.parent.id === 1 && cursor[1] === 0 && cursor[-1] === 0;
311
219
  }
312
-
313
- // Notes about MathQuill
314
- //
315
- // MathQuill's stores its layout as nested linked lists. Each node in the
316
- // list has this.MQ.L '-1' and this.MQ.R '1' properties that define links to
317
- // the left and right nodes respectively. They also have
318
- //
319
- // ctrlSeq: contains the latex code snippet that defines that node.
320
- // jQ: jQuery object for the DOM node(s) for this MathQuill node.
321
- // ends: pointers to the nodes at the ends of the container.
322
- // parent: parent node.
323
- // blocks: an array containing one or more nodes that make up the node.
324
- // sub?: subscript node if there is one as is the case in log_n
325
- //
326
- // All of the code below is super fragile. Please be especially careful
327
- // when upgrading MathQuill.
328
-
329
- _handleBackspaceInNthRoot(cursor) {
330
- const isAtLeftEnd = cursor[this.MQ.L] === ActionType.MQ_END;
331
-
332
- const isRootEmpty = this._isInsideEmptyNode(
333
- cursor.parent.parent.blocks[0].ends,
334
- );
335
-
336
- if (isAtLeftEnd) {
337
- this._selectNode(cursor.parent.parent, cursor);
338
-
339
- if (isRootEmpty) {
340
- this.mathField.keystroke("Backspace");
341
- }
342
- } else {
343
- this.mathField.keystroke("Backspace");
344
- }
345
- }
346
-
347
- /**
348
- * Advances the cursor to the next logical position.
349
- *
350
- * @param {cursor} cursor
351
- * @private
352
- */
353
- _handleJumpOut(cursor, key) {
354
- const context = this.contextForCursor(cursor);
355
-
356
- // Validate that the current cursor context matches the key's intent.
357
- if (KeysForJumpContext[context] !== key) {
358
- // If we don't have a valid cursor context, yet the user was able
359
- // to trigger a jump-out key, that's a broken invariant. Rather
360
- // than throw an error (which would kick the user out of the
361
- // exercise), we do nothing, as a fallback strategy. The user can
362
- // still move the cursor manually.
363
- return;
364
- }
365
-
366
- switch (context) {
367
- case CursorContext.IN_PARENS:
368
- // Insert at the end of the parentheses, and then navigate right
369
- // once more to get 'beyond' the parentheses.
370
- cursor.insRightOf(cursor.parent.parent);
371
- break;
372
-
373
- case CursorContext.BEFORE_FRACTION:
374
- // Find the nearest fraction to the right of the cursor.
375
- let fractionNode;
376
- let visitor = cursor;
377
- while (visitor[this.MQ.R] !== ActionType.MQ_END) {
378
- if (this._isFraction(visitor[this.MQ.R])) {
379
- fractionNode = visitor[this.MQ.R];
380
- }
381
- visitor = visitor[this.MQ.R];
382
- }
383
-
384
- // Jump into it!
385
- cursor.insLeftOf(fractionNode);
386
- this.mathField.keystroke("Right");
387
- break;
388
-
389
- case CursorContext.IN_NUMERATOR:
390
- // HACK(charlie): I can't find a better way to do this. The goal
391
- // is to place the cursor at the start of the matching
392
- // denominator. So, we identify the appropriate node, and
393
- // continue rightwards until we find ourselves inside of it.
394
- // It's possible that there are cases in which we don't reach
395
- // the denominator, though I can't think of any.
396
- const siblingDenominator = cursor.parent.parent.blocks[1];
397
- while (cursor.parent !== siblingDenominator) {
398
- this.mathField.keystroke("Right");
399
- }
400
- break;
401
-
402
- case CursorContext.IN_DENOMINATOR:
403
- cursor.insRightOf(cursor.parent.parent);
404
- break;
405
-
406
- case CursorContext.IN_SUB_SCRIPT:
407
- // Insert just beyond the superscript.
408
- cursor.insRightOf(cursor.parent.parent);
409
-
410
- // Navigate right once more, if we're right before parens. This
411
- // is to handle the standard case in which the subscript is the
412
- // base of a custom log.
413
- if (this._isParens(cursor[this.MQ.R])) {
414
- this.mathField.keystroke("Right");
415
- }
416
- break;
417
-
418
- case CursorContext.IN_SUPER_SCRIPT:
419
- // Insert just beyond the superscript.
420
- cursor.insRightOf(cursor.parent.parent);
421
- break;
422
-
423
- default:
424
- throw new Error(
425
- `Attempted to 'Jump Out' from node, but found no ` +
426
- `appropriate cursor context: ${context}`,
427
- );
428
- }
429
- }
430
-
431
- /**
432
- * Selects and deletes part of the expression based on the cursor location.
433
- * See inline comments for precise behavior of different cases.
434
- *
435
- * @param {cursor} cursor
436
- * @private
437
- */
438
- _handleBackspace(cursor) {
439
- if (!cursor.selection) {
440
- const parent = cursor.parent;
441
- const grandparent = parent.parent;
442
- const leftNode = cursor[this.MQ.L];
443
-
444
- if (this._isFraction(leftNode)) {
445
- this._selectNode(leftNode, cursor);
446
- } else if (this._isSquareRoot(leftNode)) {
447
- this._selectNode(leftNode, cursor);
448
- } else if (this._isNthRoot(leftNode)) {
449
- this._selectNode(leftNode, cursor);
450
- } else if (this._isNthRootIndex(parent)) {
451
- this._handleBackspaceInRootIndex(cursor);
452
- } else if (leftNode.ctrlSeq === "\\left(") {
453
- this._handleBackspaceOutsideParens(cursor);
454
- } else if (grandparent.ctrlSeq === "\\left(") {
455
- this._handleBackspaceInsideParens(cursor);
456
- } else if (this._isInsideLogIndex(cursor)) {
457
- this._handleBackspaceInLogIndex(cursor);
458
- } else if (
459
- leftNode.ctrlSeq === "\\ge " ||
460
- leftNode.ctrlSeq === "\\le "
461
- ) {
462
- this._handleBackspaceAfterLigaturedSymbol(cursor);
463
- } else if (
464
- this._isNthRoot(grandparent) &&
465
- leftNode === ActionType.MQ_END
466
- ) {
467
- this._handleBackspaceInNthRoot(cursor);
468
- } else {
469
- this.mathField.keystroke("Backspace");
470
- }
471
- } else {
472
- this.mathField.keystroke("Backspace");
473
- }
474
- }
475
-
476
- _handleLeftArrow(cursor) {
477
- // If we're inside a function, and just after the left parentheses, we
478
- // need to skip the entire function name, rather than move the cursor
479
- // inside of it. For example, when hitting left from within the
480
- // parentheses in `cos()`, we want to place the cursor to the left of
481
- // the entire expression, rather than between the `s` and the left
482
- // parenthesis.
483
- // From the cursor's perspective, this requires that our left node is
484
- // the ActionType.MQ_END node, that our grandparent is the left parenthesis, and
485
- // the nodes to the left of our grandparent comprise a valid function
486
- // name.
487
- if (cursor[this.MQ.L] === ActionType.MQ_END) {
488
- const parent = cursor.parent;
489
- const grandparent = parent.parent;
490
- if (grandparent.ctrlSeq === "\\left(") {
491
- const command = this._maybeFindCommandBeforeParens(grandparent);
492
- if (command) {
493
- cursor.insLeftOf(command.startNode);
494
- return;
495
- }
496
- }
497
- }
498
-
499
- // Otherwise, we default to the standard MathQull left behavior.
500
- this.mathField.keystroke("Left");
501
- }
502
-
503
- _handleRightArrow(cursor) {
504
- const command = this._maybeFindCommand(cursor[this.MQ.R]);
505
- if (command) {
506
- // Similarly, if a function is to our right, then we need to place
507
- // the cursor at the start of its parenthetical content, which is
508
- // done by putting it to the left of ites parentheses and then
509
- // moving right once.
510
- cursor.insLeftOf(command.endNode);
511
- this.mathField.keystroke("Right");
512
- } else {
513
- // Otherwise, we default to the standard MathQull right behavior.
514
- this.mathField.keystroke("Right");
515
- }
516
- }
517
-
518
- _handleExponent(cursor, key) {
519
- // If there's an invalid operator preceding the cursor (anything that
520
- // knowingly cannot be raised to a power), add an empty set of
521
- // parentheses and apply the exponent to that.
522
- const invalidPrefixes = [...ArithmeticOperators, ...EqualityOperators];
523
-
524
- const precedingNode = cursor[this.MQ.L];
525
- const shouldPrefixWithParens =
526
- precedingNode === ActionType.MQ_END ||
527
- invalidPrefixes.includes(precedingNode.ctrlSeq.trim());
528
- if (shouldPrefixWithParens) {
529
- this.mathField.write("\\left(\\right)");
530
- }
531
-
532
- // Insert the appropriate exponent operator.
533
- switch (key) {
534
- case Keys.EXP:
535
- this.mathField.cmd("^");
536
- break;
537
-
538
- case Keys.EXP_2:
539
- case Keys.EXP_3:
540
- this.mathField.write(`^${key === Keys.EXP_2 ? 2 : 3}`);
541
-
542
- // If we enter a square or a cube, we should leave the cursor
543
- // within the newly inserted parens, if they exist. This takes
544
- // exactly four left strokes, since the cursor by default would
545
- // end up to the right of the exponent.
546
- if (shouldPrefixWithParens) {
547
- this.mathField.keystroke("Left");
548
- this.mathField.keystroke("Left");
549
- this.mathField.keystroke("Left");
550
- this.mathField.keystroke("Left");
551
- }
552
- break;
553
-
554
- default:
555
- throw new Error(`Invalid exponent key: ${key}`);
556
- }
557
- }
558
-
559
- /**
560
- * Return the start node, end node, and full name of the command of which
561
- * the initial node is a part, or `null` if the node is not part of a
562
- * command.
563
- *
564
- * @param {node} initialNode - the node to included as part of the command
565
- * @returns {null|object} - `null` or an object containing the start node
566
- * (`startNode`), end node (`endNode`), and full
567
- * name (`name`) of the command
568
- * @private
569
- */
570
- _maybeFindCommand(initialNode) {
571
- if (!initialNode) {
572
- return null;
573
- }
574
-
575
- // MathQuill stores commands as separate characters so that
576
- // users can delete commands one character at a time. We iterate over
577
- // the nodes from right to left until we hit a sequence starting with a
578
- // '\\', which signifies the start of a command; then we iterate from
579
- // left to right until we hit a '\\left(', which signifies the end of a
580
- // command. If we encounter any character that doesn't belong in a
581
- // command, we return null. We match a single character at a time.
582
- // Ex) ['\\l', 'o', 'g ', '\\left(', ...]
583
- const commandCharRegex = /^[a-z]$/;
584
- const commandStartRegex = /^\\[a-z]$/;
585
- const commandEndSeq = "\\left(";
586
-
587
- // Note: We allowlist the set of valid commands, since relying solely on
588
- // a command being prefixed with a backslash leads to undesired
589
- // behavior. For example, Greek symbols, left parentheses, and square
590
- // roots all get treated as commands.
591
- const validCommands = ["\\log", "\\ln", "\\cos", "\\sin", "\\tan"];
592
-
593
- let name = "";
594
- let startNode;
595
- let endNode;
596
-
597
- // Collect the portion of the command from the current node, leftwards
598
- // until the start of the command.
599
- let node = initialNode;
600
- while (node !== 0) {
601
- const ctrlSeq = node.ctrlSeq.trim();
602
- if (commandCharRegex.test(ctrlSeq)) {
603
- name = ctrlSeq + name;
604
- } else if (commandStartRegex.test(ctrlSeq)) {
605
- name = ctrlSeq + name;
606
- startNode = node;
607
- break;
608
- } else {
609
- break;
610
- }
611
-
612
- node = node[this.MQ.L];
613
- }
614
-
615
- // If we hit the start of a command, then grab the rest of it by
616
- // iterating rightwards to compute the full name of the command, along
617
- // with its terminal node.
618
- if (startNode) {
619
- // Next, iterate from the start to the right.
620
- node = initialNode[this.MQ.R];
621
- while (node !== 0) {
622
- const ctrlSeq = node.ctrlSeq.trim();
623
- if (commandCharRegex.test(ctrlSeq)) {
624
- // If we have a single character, add it to the command
625
- // name.
626
- name = name + ctrlSeq;
627
- } else if (ctrlSeq === commandEndSeq) {
628
- // If we hit the command end delimiter (the left
629
- // parentheses surrounding its arguments), stop.
630
- endNode = node;
631
- break;
632
- }
633
-
634
- node = node[this.MQ.R];
635
- }
636
- if (validCommands.includes(name)) {
637
- return {name, startNode, endNode};
638
- } else {
639
- return null;
640
- }
641
- } else {
642
- return null;
643
- }
644
- }
645
-
646
- /**
647
- * Return the start node, end node, and full name of the command to the left
648
- * of `\\left(`, or `null` if there is no command.
649
- *
650
- * @param {node} leftParenNode - node where .ctrlSeq == `\\left(`
651
- * @returns {null|object} - `null` or an object containing the start node
652
- * (`startNode`), end node (`endNode`), and full
653
- * name (`name`) of the command
654
- * @private
655
- */
656
- _maybeFindCommandBeforeParens(leftParenNode) {
657
- return this._maybeFindCommand(leftParenNode[this.MQ.L]);
658
- }
659
-
660
- _selectNode(node, cursor) {
661
- cursor.insLeftOf(node);
662
- cursor.startSelection();
663
- cursor.insRightOf(node);
664
- cursor.select();
665
- cursor.endSelection();
666
- }
667
-
668
- _isFraction(node) {
669
- return node.jQ && node.jQ.hasClass("mq-fraction");
670
- }
671
-
672
- _isNumerator(node) {
673
- return node.jQ && node.jQ.hasClass("mq-numerator");
674
- }
675
-
676
- _isDenominator(node) {
677
- return node.jQ && node.jQ.hasClass("mq-denominator");
678
- }
679
-
680
- _isSubScript(node) {
681
- // NOTE(charlie): MyScript has a structure whereby its superscripts seem
682
- // to be represented as a parent node with 'mq-sup-only' containing a
683
- // single child with 'mq-sup'.
684
- return (
685
- node.jQ &&
686
- (node.jQ.hasClass("mq-sub-only") || node.jQ.hasClass("mq-sub"))
687
- );
688
- }
689
-
690
- _isSuperScript(node) {
691
- // NOTE(charlie): MyScript has a structure whereby its superscripts seem
692
- // to be represented as a parent node with 'mq-sup-only' containing a
693
- // single child with 'mq-sup'.
694
- return (
695
- node.jQ &&
696
- (node.jQ.hasClass("mq-sup-only") || node.jQ.hasClass("mq-sup"))
697
- );
698
- }
699
-
700
- _isParens(node) {
701
- return node && node.ctrlSeq === "\\left(";
702
- }
703
-
704
- _isLeaf(node) {
705
- return (
706
- node && node.ctrlSeq && ValidLeaves.includes(node.ctrlSeq.trim())
707
- );
708
- }
709
-
710
- _isSquareRoot(node) {
711
- return (
712
- node.blocks &&
713
- node.blocks[0].jQ &&
714
- node.blocks[0].jQ.hasClass("mq-sqrt-stem")
715
- );
716
- }
717
-
718
- _isNthRoot(node) {
719
- return (
720
- node.blocks &&
721
- node.blocks[0].jQ &&
722
- node.blocks[0].jQ.hasClass("mq-nthroot")
723
- );
724
- }
725
-
726
- _isNthRootIndex(node) {
727
- return node.jQ && node.jQ.hasClass("mq-nthroot");
728
- }
729
-
730
- _isInsideLogIndex(cursor) {
731
- const grandparent = cursor.parent.parent;
732
-
733
- if (grandparent && grandparent.jQ.hasClass("mq-supsub")) {
734
- const command = this._maybeFindCommandBeforeParens(grandparent);
735
-
736
- if (command && command.name === "\\log") {
737
- return true;
738
- }
739
- }
740
-
741
- return false;
742
- }
743
-
744
- _isInsideEmptyNode(cursor) {
745
- return (
746
- cursor[this.MQ.L] === ActionType.MQ_END &&
747
- cursor[this.MQ.R] === ActionType.MQ_END
748
- );
749
- }
750
-
751
- _handleBackspaceInRootIndex(cursor) {
752
- if (this._isInsideEmptyNode(cursor)) {
753
- // When deleting the index in a nthroot, we change from the nthroot
754
- // to a sqrt, e.g. \sqrt[|]{35x-5} => |\sqrt{35x-5}. If there's no
755
- // content under the root, then we delete the whole thing.
756
-
757
- const grandparent = cursor.parent.parent;
758
- const latex = grandparent.latex();
759
- const reinsertionPoint = grandparent[this.MQ.L];
760
-
761
- this._selectNode(grandparent, cursor);
762
-
763
- const rootIsEmpty = grandparent.blocks[1].jQ.text() === "";
764
-
765
- if (rootIsEmpty) {
766
- // If there is not content under the root then simply delete
767
- // the whole thing.
768
- this.mathField.keystroke("Backspace");
769
- } else {
770
- // Replace the nthroot with a sqrt if there was content under
771
- // the root.
772
-
773
- // Start by deleting the selection.
774
- this.mathField.keystroke("Backspace");
775
-
776
- // Replace the nth-root with a sqrt.
777
- this.mathField.write(latex.replace(/^\\sqrt\[\]/, "\\sqrt"));
778
-
779
- // Adjust the cursor to be to the left the sqrt.
780
- if (reinsertionPoint === ActionType.MQ_END) {
781
- this.mathField.moveToDirEnd(this.MQ.L);
782
- } else {
783
- cursor.insRightOf(reinsertionPoint);
784
- }
785
- }
786
- } else {
787
- if (cursor[this.MQ.L] !== ActionType.MQ_END) {
788
- // If the cursor is not at the leftmost position inside the
789
- // root's index, delete a character.
790
- this.mathField.keystroke("Backspace");
791
- } else {
792
- // TODO(kevinb) verify that we want this behavior after testing
793
- // Do nothing because we haven't completely deleted the
794
- // index of the radical.
795
- }
796
- }
797
- }
798
-
799
- _handleBackspaceInLogIndex(cursor) {
800
- if (this._isInsideEmptyNode(cursor)) {
801
- const grandparent = cursor.parent.parent;
802
- const command = this._maybeFindCommandBeforeParens(grandparent);
803
-
804
- cursor.insLeftOf(command?.startNode);
805
- cursor.startSelection();
806
-
807
- if (grandparent[this.MQ.R] !== ActionType.MQ_END) {
808
- cursor.insRightOf(grandparent[this.MQ.R]);
809
- } else {
810
- cursor.insRightOf(grandparent);
811
- }
812
-
813
- cursor.select();
814
- cursor.endSelection();
815
-
816
- const isLogBodyEmpty =
817
- grandparent[this.MQ.R].contentjQ.text() === "";
818
-
819
- if (isLogBodyEmpty) {
820
- // If there's no content inside the log's parens then delete the
821
- // whole thing.
822
- this.mathField.keystroke("Backspace");
823
- }
824
- } else {
825
- this.mathField.keystroke("Backspace");
826
- }
827
- }
828
-
829
- _handleBackspaceOutsideParens(cursor) {
830
- // In this case the node with '\\left(' for its ctrlSeq
831
- // is the parent of the expression contained within the
832
- // parentheses.
833
- //
834
- // Handle selecting an expression before deleting:
835
- // (x+1)| => |(x+1)|
836
- // \log(x+1)| => |\log(x+1)|
837
-
838
- const leftNode = cursor[this.MQ.L];
839
- const rightNode = cursor[this.MQ.R];
840
- const command = this._maybeFindCommandBeforeParens(leftNode);
841
-
842
- if (command && command.startNode) {
843
- // There's a command before the parens so we select it as well as
844
- // the parens.
845
- cursor.insLeftOf(command.startNode);
846
- cursor.startSelection();
847
- if (rightNode === ActionType.MQ_END) {
848
- cursor.insAtRightEnd(cursor.parent);
849
- } else {
850
- cursor.insLeftOf(rightNode);
851
- }
852
- cursor.select();
853
- cursor.endSelection();
854
- } else {
855
- cursor.startSelection();
856
- cursor.insLeftOf(leftNode); // left of \\left(
857
- cursor.select();
858
- cursor.endSelection();
859
- }
860
- }
861
-
862
- _handleBackspaceInsideParens(cursor) {
863
- // Handle situations when the cursor is inside parens or a
864
- // command that uses parens, e.g. \log() or \tan()
865
- //
866
- // MathQuill represents log(x+1) in roughly the following way
867
- // [l, o, g, \\left[parent:[x, +, 1]]]
868
- //
869
- // If the cursor is inside the parentheses it's next to one of:
870
- // x, +, or 1. This makes sub_sub_expr its parent and sub_expr
871
- // it's parent.
872
- //
873
- // Interestingly parent doesn't have any nodes to the left or
874
- // right of it (even though the corresponding DOM node has
875
- // ( and ) characters on either side.
876
- //
877
- // The grandparent's ctrlSeq is `\\left(`. The `\\right)` isn't
878
- // stored anywhere. NOTE(kevinb): I believe this is because
879
- // MathQuill knows what the close paren should be and does the
880
- // right thing at render time.
881
- //
882
- // This conditional branch handles the following cases:
883
- // - \log(x+1|) => \log(x+|)
884
- // - \log(|x+1) => |\log(x+1)|
885
- // - \log(|) => |
886
-
887
- if (cursor[this.MQ.L] !== ActionType.MQ_END) {
888
- // This command contains math and there's some math to
889
- // the left of the cursor that we should delete normally
890
- // before doing anything special.
891
- this.mathField.keystroke("Backspace");
892
- return;
893
- }
894
-
895
- const grandparent = cursor.parent.parent;
896
-
897
- // If the cursors is inside the parens at the start but the command
898
- // has a subscript as is the case in log_n then move the cursor into
899
- // the subscript, e.g. \log_{5}(|x+1) => \log_{5|}(x+1)
900
-
901
- if (grandparent[this.MQ.L].sub) {
902
- // if there is a subscript
903
- if (grandparent[this.MQ.L].sub.jQ.text()) {
904
- // and it contains text
905
- // move the cursor to the right end of the subscript
906
- cursor.insAtRightEnd(grandparent[this.MQ.L].sub);
907
- return;
908
- }
909
- }
910
-
911
- // Determine if the parens are empty before we modify the
912
- // cursor's position.
913
- const isEmpty = this._isInsideEmptyNode(cursor);
914
-
915
- // Insert the cursor to the left of the command if there is one
916
- // or before the '\\left(` if there isn't
917
- const command = this._maybeFindCommandBeforeParens(grandparent);
918
-
919
- cursor.insLeftOf((command && command.startNode) || grandparent);
920
- cursor.startSelection();
921
- cursor.insRightOf(grandparent);
922
- cursor.select();
923
- cursor.endSelection();
924
-
925
- // Delete the selection, but only if the parens were empty to
926
- // begin with.
927
- if (isEmpty) {
928
- this.mathField.keystroke("Backspace");
929
- }
930
- }
931
-
932
- _handleBackspaceAfterLigaturedSymbol(cursor) {
933
- this.mathField.keystroke("Backspace");
934
- this.mathField.keystroke("Backspace");
935
- }
936
-
937
- contextForCursor(cursor) {
938
- // First, try to find any fraction to the right, unimpeded.
939
- let visitor = cursor;
940
- while (visitor[this.MQ.R] !== ActionType.MQ_END) {
941
- if (this._isFraction(visitor[this.MQ.R])) {
942
- return CursorContext.BEFORE_FRACTION;
943
- } else if (!this._isLeaf(visitor[this.MQ.R])) {
944
- break;
945
- }
946
- visitor = visitor[this.MQ.R];
947
- }
948
-
949
- // If that didn't work, check if the parent or grandparent is a special
950
- // context, so that we can jump outwards.
951
- if (this._isParens(cursor.parent && cursor.parent.parent)) {
952
- return CursorContext.IN_PARENS;
953
- } else if (this._isNumerator(cursor.parent)) {
954
- return CursorContext.IN_NUMERATOR;
955
- } else if (this._isDenominator(cursor.parent)) {
956
- return CursorContext.IN_DENOMINATOR;
957
- } else if (this._isSubScript(cursor.parent)) {
958
- return CursorContext.IN_SUB_SCRIPT;
959
- } else if (this._isSuperScript(cursor.parent)) {
960
- return CursorContext.IN_SUPER_SCRIPT;
961
- } else {
962
- return CursorContext.NONE;
963
- }
964
- }
965
-
966
- _isAtTopLevel(cursor) {
967
- return !cursor.parent.parent;
968
- }
969
220
  }
970
221
 
971
222
  export default MathWrapper;